Skip to content

Commit 0854024

Browse files
committed
validate that colliding files are checked out (#301)
1 parent 620c955 commit 0854024

File tree

2 files changed

+69
-22
lines changed

2 files changed

+69
-22
lines changed

git-worktree/src/index.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ pub(crate) mod entry {
108108
path: root.to_path_buf(),
109109
})?;
110110
let mut options = OpenOptions::new();
111-
options.create_new(true).write(true);
111+
options.create(true).write(true);
112112
#[cfg(unix)]
113113
if executable_bit && entry.mode == git_index::entry::Mode::FILE_EXECUTABLE {
114114
use std::os::unix::fs::OpenOptionsExt;

git-worktree/tests/index/mod.rs

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,19 @@ mod checkout {
88

99
use git_object::bstr::ByteSlice;
1010
use git_odb::FindExt;
11+
use git_worktree::fs::Capabilities;
1112
use git_worktree::index;
12-
use git_worktree::index::checkout::Options;
1313
use tempfile::TempDir;
1414

1515
use crate::fixture_path;
1616

17-
#[test]
18-
fn allow_symlinks() -> crate::Result {
19-
let opts = opts_with_symlink(true);
20-
if !git_worktree::fs::Capabilities::probe(std::env::current_dir()?.join("..").join(".git")).symlink {
21-
eprintln!("IGNORING symlink test on file system without symlink support");
22-
// skip if symlinks aren't supported anyway.
23-
return Ok(());
24-
};
25-
let (source_tree, destination) = setup_fixture_with_options(opts, "make_mixed_without_submodules")?;
26-
27-
assert_equality(&source_tree, &destination, opts.fs.symlink)?;
28-
Ok(())
17+
fn probe_gitoxide_dir() -> crate::Result<Capabilities> {
18+
Ok(git_worktree::fs::Capabilities::probe(
19+
std::env::current_dir()?.join("..").join(".git"),
20+
))
2921
}
3022

31-
fn opts_with_symlink(symlink: bool) -> Options {
23+
fn opts_with_symlink(symlink: bool) -> index::checkout::Options {
3224
index::checkout::Options {
3325
fs: git_worktree::fs::Capabilities {
3426
symlink,
@@ -41,14 +33,67 @@ mod checkout {
4133
#[test]
4234
fn symlinks_become_files_if_disabled() -> crate::Result {
4335
let opts = opts_with_symlink(false);
44-
let (source_tree, destination) = setup_fixture_with_options(opts, "make_mixed_without_submodules")?;
36+
let (source_tree, destination, _index) = checkout_index_in_tmp_dir(opts, "make_mixed_without_submodules")?;
4537

4638
assert_equality(&source_tree, &destination, opts.fs.symlink)?;
4739

4840
Ok(())
4941
}
5042

51-
fn assert_equality(source_tree: &Path, destination: &TempDir, allow_symlinks: bool) -> crate::Result {
43+
#[test]
44+
fn allow_symlinks() -> crate::Result {
45+
let opts = opts_with_symlink(true);
46+
if !probe_gitoxide_dir()?.symlink {
47+
eprintln!("IGNORING symlink test on file system without symlink support");
48+
// skip if symlinks aren't supported anyway.
49+
return Ok(());
50+
};
51+
let (source_tree, destination, _index) = checkout_index_in_tmp_dir(opts, "make_mixed_without_submodules")?;
52+
53+
assert_equality(&source_tree, &destination, opts.fs.symlink)?;
54+
Ok(())
55+
}
56+
57+
#[test]
58+
fn no_case_related_collisions_on_case_sensitive_filesystem() {
59+
if probe_gitoxide_dir().unwrap().ignore_case {
60+
eprintln!("Skipping case-sensitive testing on what would be a case-insenstive file system");
61+
return;
62+
}
63+
let opts = opts_with_symlink(true);
64+
let (source_tree, destination, index) = checkout_index_in_tmp_dir(opts, "make_ignorecase_collisions").unwrap();
65+
assert_eq!(index.entries().len(), 2, "there is just one colliding item");
66+
67+
let num_files = assert_equality(&source_tree, &destination, opts.fs.symlink).unwrap();
68+
assert_eq!(num_files, index.entries().len(), "it checks out all files");
69+
}
70+
71+
#[test]
72+
fn collisions_are_detected_on_a_case_sensitive_filesystem() {
73+
if !probe_gitoxide_dir().unwrap().ignore_case {
74+
eprintln!("Skipping case-insensitive testing on what would be a case-senstive file system");
75+
return;
76+
}
77+
let opts = opts_with_symlink(true);
78+
let (source_tree, destination, index) = checkout_index_in_tmp_dir(opts, "make_ignorecase_collisions").unwrap();
79+
assert_eq!(index.entries().len(), 2, "there is just one colliding item");
80+
81+
let source_files = dir_structure(&source_tree);
82+
assert_eq!(
83+
stripped_prefix(&source_tree, &source_files),
84+
vec![PathBuf::from("a")],
85+
"the source also only contains the first created file"
86+
);
87+
88+
let dest_files = dir_structure(&destination);
89+
assert_eq!(
90+
stripped_prefix(&destination, &dest_files),
91+
vec![PathBuf::from("A")],
92+
"it only creates the first file of a collision"
93+
);
94+
}
95+
96+
fn assert_equality(source_tree: &Path, destination: &TempDir, allow_symlinks: bool) -> crate::Result<usize> {
5297
let source_files = dir_structure(source_tree);
5398
let worktree_files = dir_structure(&destination);
5499

@@ -57,7 +102,9 @@ mod checkout {
57102
stripped_prefix(&destination, &worktree_files),
58103
);
59104

105+
let mut count = 0;
60106
for (source_file, worktree_file) in source_files.iter().zip(worktree_files.iter()) {
107+
count += 1;
61108
if !allow_symlinks && source_file.is_symlink() {
62109
assert!(!worktree_file.is_symlink());
63110
assert_eq!(fs::read(worktree_file)?.to_path()?, fs::read_link(source_file)?);
@@ -71,7 +118,7 @@ mod checkout {
71118
);
72119
}
73120
}
74-
Ok(())
121+
Ok(count)
75122
}
76123

77124
pub fn dir_structure<P: AsRef<std::path::Path>>(path: P) -> Vec<std::path::PathBuf> {
@@ -87,10 +134,10 @@ mod checkout {
87134
files
88135
}
89136

90-
fn setup_fixture_with_options(
91-
opts: git_worktree::index::checkout::Options,
137+
fn checkout_index_in_tmp_dir(
138+
opts: index::checkout::Options,
92139
name: &str,
93-
) -> crate::Result<(PathBuf, TempDir)> {
140+
) -> crate::Result<(PathBuf, TempDir, git_index::File)> {
94141
let source_tree = fixture_path(name);
95142
let git_dir = source_tree.join(".git");
96143
let mut index = git_index::File::at(git_dir.join("index"), Default::default())?;
@@ -103,7 +150,7 @@ mod checkout {
103150
move |oid, buf| odb.find_blob(oid, buf).ok(),
104151
opts,
105152
)?;
106-
Ok((source_tree, destination))
153+
Ok((source_tree, destination, index))
107154
}
108155

109156
fn stripped_prefix(prefix: impl AsRef<Path>, source_files: &[PathBuf]) -> Vec<&Path> {

0 commit comments

Comments
 (0)