Skip to content

Commit 6df6e84

Browse files
authored
Merge pull request #1425 from EliahKagan/strange-symlink-targets
Always fall back to creating file symlinks on Windows
2 parents 0c5d1ff + f964cb9 commit 6df6e84

10 files changed

+87
-12
lines changed

gix-fs/src/symlink.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,7 @@ pub fn create(original: &Path, link: &Path) -> io::Result<()> {
4141
use std::os::windows::fs::{symlink_dir, symlink_file};
4242
// TODO: figure out if links to links count as files or whatever they point at
4343
let orig_abs = link.parent().expect("dir for link").join(original);
44-
let is_dir = match std::fs::metadata(orig_abs) {
45-
Ok(m) => m.is_dir(),
46-
Err(err) if err.kind() == io::ErrorKind::NotFound => false,
47-
Err(err) => return Err(err),
48-
};
49-
if is_dir {
44+
if orig_abs.is_dir() {
5045
symlink_dir(original, link)
5146
} else {
5247
symlink_file(original, link)

gix-index/tests/fixtures/make_traverse_literal_separators.sh

100644100755
File mode changed.
Binary file not shown.

gix-worktree-state/tests/fixtures/make_dangling_symlink.sh

100644100755
File mode changed.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
# On Windows, the target is an invalid file name.
7+
qmarks_oid=$(echo -n "???" | git hash-object -w --stdin)
8+
9+
git update-index --index-info <<EOF
10+
120000 $qmarks_oid dangling-qmarks-symlink
11+
EOF
12+
13+
git commit -m "dangling symlinks with Windows invalid target in index"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
# On Windows, the target is a reserved legacy DOS device name.
7+
con_oid=$(echo -n "CON" | git hash-object -w --stdin)
8+
9+
git update-index --index-info <<EOF
10+
120000 $con_oid dangling-con-symlink
11+
EOF
12+
13+
git commit -m "dangling symlinks with Widnows reserved target in index"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
target_oid=$(echo -n "." | git hash-object -w --stdin)
7+
8+
git update-index --index-info <<EOF
9+
120000 $target_oid symlink
10+
EOF
11+
12+
git commit -m "symlink in index, points to directory"

gix-worktree-state/tests/state/checkout.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,29 +268,71 @@ fn symlinks_become_files_if_disabled() -> crate::Result {
268268
}
269269

270270
#[test]
271-
fn dangling_symlinks_can_be_created() -> crate::Result {
271+
fn symlinks_to_directories_are_usable() -> crate::Result {
272272
let opts = opts_from_probe();
273273
if !opts.fs.symlink {
274-
eprintln!("Skipping dangling symlink test on filesystem that doesn't support it");
274+
eprintln!("Skipping directory symlink test on filesystem that doesn't support it");
275275
return Ok(());
276276
}
277277

278278
let (_source_tree, destination, _index, outcome) =
279-
checkout_index_in_tmp_dir(opts.clone(), "make_dangling_symlink", None)?;
279+
checkout_index_in_tmp_dir(opts.clone(), "make_dir_symlink", None)?;
280280
let worktree_files = dir_structure(&destination);
281281
let worktree_files_stripped = stripped_prefix(&destination, &worktree_files);
282282

283-
assert_eq!(worktree_files_stripped, paths(["dangling"]));
283+
assert_eq!(worktree_files_stripped, paths(["symlink"]));
284284
let symlink_path = &worktree_files[0];
285285
assert!(symlink_path
286286
.symlink_metadata()
287-
.expect("dangling symlink is on disk")
287+
.expect("symlink is on disk")
288288
.is_symlink());
289-
assert_eq!(std::fs::read_link(symlink_path)?, Path::new("non-existing-target"));
289+
assert!(symlink_path
290+
.metadata()
291+
.expect("metadata accessible through symlink")
292+
.is_dir());
293+
assert_eq!(std::fs::read_link(symlink_path)?, Path::new("."));
290294
assert!(outcome.collisions.is_empty());
291295
Ok(())
292296
}
293297

298+
#[test]
299+
fn dangling_symlinks_can_be_created() -> crate::Result {
300+
let opts = opts_from_probe();
301+
if !opts.fs.symlink {
302+
eprintln!("Skipping dangling symlink test on filesystem that doesn't support it");
303+
return Ok(());
304+
}
305+
306+
for (fixture, symlink_name, target_name) in [
307+
("make_dangling_symlink", "dangling", "non-existing-target"),
308+
(
309+
"make_dangling_symlink_to_windows_invalid",
310+
"dangling-qmarks-symlink",
311+
"???",
312+
),
313+
(
314+
"make_dangling_symlink_to_windows_reserved",
315+
"dangling-con-symlink",
316+
"CON",
317+
),
318+
] {
319+
let (_source_tree, destination, _index, outcome) = checkout_index_in_tmp_dir(opts.clone(), fixture, None)?;
320+
let worktree_files = dir_structure(&destination);
321+
let worktree_files_stripped = stripped_prefix(&destination, &worktree_files);
322+
323+
assert_eq!(worktree_files_stripped, paths([symlink_name]));
324+
let symlink_path = &worktree_files[0];
325+
assert!(symlink_path
326+
.symlink_metadata()
327+
.expect("dangling symlink is on disk")
328+
.is_symlink());
329+
assert_eq!(std::fs::read_link(symlink_path)?, Path::new(target_name));
330+
assert!(outcome.collisions.is_empty());
331+
}
332+
333+
Ok(())
334+
}
335+
294336
#[test]
295337
fn allow_or_disallow_symlinks() -> crate::Result {
296338
let mut opts = opts_from_probe();

0 commit comments

Comments
 (0)