Skip to content

Commit

Permalink
Merge pull request GitoxideLabs#1425 from EliahKagan/strange-symlink-…
Browse files Browse the repository at this point in the history
…targets

Always fall back to creating file symlinks on Windows
  • Loading branch information
Byron authored Jun 27, 2024
2 parents 0c5d1ff + f964cb9 commit 6df6e84
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 12 deletions.
7 changes: 1 addition & 6 deletions gix-fs/src/symlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,7 @@ pub fn create(original: &Path, link: &Path) -> io::Result<()> {
use std::os::windows::fs::{symlink_dir, symlink_file};
// TODO: figure out if links to links count as files or whatever they point at
let orig_abs = link.parent().expect("dir for link").join(original);
let is_dir = match std::fs::metadata(orig_abs) {
Ok(m) => m.is_dir(),
Err(err) if err.kind() == io::ErrorKind::NotFound => false,
Err(err) => return Err(err),
};
if is_dir {
if orig_abs.is_dir() {
symlink_dir(original, link)
} else {
symlink_file(original, link)
Expand Down
Empty file modified gix-index/tests/fixtures/make_traverse_literal_separators.sh
100644 → 100755
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file modified gix-worktree-state/tests/fixtures/make_dangling_symlink.sh
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -eu -o pipefail

git init -q

# On Windows, the target is an invalid file name.
qmarks_oid=$(echo -n "???" | git hash-object -w --stdin)

git update-index --index-info <<EOF
120000 $qmarks_oid dangling-qmarks-symlink
EOF

git commit -m "dangling symlinks with Windows invalid target in index"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -eu -o pipefail

git init -q

# On Windows, the target is a reserved legacy DOS device name.
con_oid=$(echo -n "CON" | git hash-object -w --stdin)

git update-index --index-info <<EOF
120000 $con_oid dangling-con-symlink
EOF

git commit -m "dangling symlinks with Widnows reserved target in index"
12 changes: 12 additions & 0 deletions gix-worktree-state/tests/fixtures/make_dir_symlink.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -eu -o pipefail

git init -q

target_oid=$(echo -n "." | git hash-object -w --stdin)

git update-index --index-info <<EOF
120000 $target_oid symlink
EOF

git commit -m "symlink in index, points to directory"
54 changes: 48 additions & 6 deletions gix-worktree-state/tests/state/checkout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,29 +268,71 @@ fn symlinks_become_files_if_disabled() -> crate::Result {
}

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

let (_source_tree, destination, _index, outcome) =
checkout_index_in_tmp_dir(opts.clone(), "make_dangling_symlink", None)?;
checkout_index_in_tmp_dir(opts.clone(), "make_dir_symlink", None)?;
let worktree_files = dir_structure(&destination);
let worktree_files_stripped = stripped_prefix(&destination, &worktree_files);

assert_eq!(worktree_files_stripped, paths(["dangling"]));
assert_eq!(worktree_files_stripped, paths(["symlink"]));
let symlink_path = &worktree_files[0];
assert!(symlink_path
.symlink_metadata()
.expect("dangling symlink is on disk")
.expect("symlink is on disk")
.is_symlink());
assert_eq!(std::fs::read_link(symlink_path)?, Path::new("non-existing-target"));
assert!(symlink_path
.metadata()
.expect("metadata accessible through symlink")
.is_dir());
assert_eq!(std::fs::read_link(symlink_path)?, Path::new("."));
assert!(outcome.collisions.is_empty());
Ok(())
}

#[test]
fn dangling_symlinks_can_be_created() -> crate::Result {
let opts = opts_from_probe();
if !opts.fs.symlink {
eprintln!("Skipping dangling symlink test on filesystem that doesn't support it");
return Ok(());
}

for (fixture, symlink_name, target_name) in [
("make_dangling_symlink", "dangling", "non-existing-target"),
(
"make_dangling_symlink_to_windows_invalid",
"dangling-qmarks-symlink",
"???",
),
(
"make_dangling_symlink_to_windows_reserved",
"dangling-con-symlink",
"CON",
),
] {
let (_source_tree, destination, _index, outcome) = checkout_index_in_tmp_dir(opts.clone(), fixture, None)?;
let worktree_files = dir_structure(&destination);
let worktree_files_stripped = stripped_prefix(&destination, &worktree_files);

assert_eq!(worktree_files_stripped, paths([symlink_name]));
let symlink_path = &worktree_files[0];
assert!(symlink_path
.symlink_metadata()
.expect("dangling symlink is on disk")
.is_symlink());
assert_eq!(std::fs::read_link(symlink_path)?, Path::new(target_name));
assert!(outcome.collisions.is_empty());
}

Ok(())
}

#[test]
fn allow_or_disallow_symlinks() -> crate::Result {
let mut opts = opts_from_probe();
Expand Down

0 comments on commit 6df6e84

Please sign in to comment.