diff --git a/gix-fs/src/symlink.rs b/gix-fs/src/symlink.rs index ce639c48f65..5022332f264 100644 --- a/gix-fs/src/symlink.rs +++ b/gix-fs/src/symlink.rs @@ -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) diff --git a/gix-index/tests/fixtures/make_traverse_literal_separators.sh b/gix-index/tests/fixtures/make_traverse_literal_separators.sh old mode 100644 new mode 100755 diff --git a/gix-worktree-state/tests/fixtures/generated-archives/make_dangling_symlink_to_windows_invalid.tar b/gix-worktree-state/tests/fixtures/generated-archives/make_dangling_symlink_to_windows_invalid.tar new file mode 100644 index 00000000000..dda7c709d1c Binary files /dev/null and b/gix-worktree-state/tests/fixtures/generated-archives/make_dangling_symlink_to_windows_invalid.tar differ diff --git a/gix-worktree-state/tests/fixtures/generated-archives/make_dangling_symlink_to_windows_reserved.tar b/gix-worktree-state/tests/fixtures/generated-archives/make_dangling_symlink_to_windows_reserved.tar new file mode 100644 index 00000000000..3d9c4167655 Binary files /dev/null and b/gix-worktree-state/tests/fixtures/generated-archives/make_dangling_symlink_to_windows_reserved.tar differ diff --git a/gix-worktree-state/tests/fixtures/generated-archives/make_dir_symlink.tar b/gix-worktree-state/tests/fixtures/generated-archives/make_dir_symlink.tar new file mode 100644 index 00000000000..cd4e2b0f6c1 Binary files /dev/null and b/gix-worktree-state/tests/fixtures/generated-archives/make_dir_symlink.tar differ diff --git a/gix-worktree-state/tests/fixtures/make_dangling_symlink.sh b/gix-worktree-state/tests/fixtures/make_dangling_symlink.sh old mode 100644 new mode 100755 diff --git a/gix-worktree-state/tests/fixtures/make_dangling_symlink_to_windows_invalid.sh b/gix-worktree-state/tests/fixtures/make_dangling_symlink_to_windows_invalid.sh new file mode 100755 index 00000000000..68edb84cba9 --- /dev/null +++ b/gix-worktree-state/tests/fixtures/make_dangling_symlink_to_windows_invalid.sh @@ -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 < 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();