Skip to content

Commit

Permalink
validate that colliding files are checked out (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Mar 1, 2022
1 parent 620c955 commit 0854024
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 22 deletions.
2 changes: 1 addition & 1 deletion git-worktree/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub(crate) mod entry {
path: root.to_path_buf(),
})?;
let mut options = OpenOptions::new();
options.create_new(true).write(true);
options.create(true).write(true);
#[cfg(unix)]
if executable_bit && entry.mode == git_index::entry::Mode::FILE_EXECUTABLE {
use std::os::unix::fs::OpenOptionsExt;
Expand Down
89 changes: 68 additions & 21 deletions git-worktree/tests/index/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,19 @@ mod checkout {

use git_object::bstr::ByteSlice;
use git_odb::FindExt;
use git_worktree::fs::Capabilities;
use git_worktree::index;
use git_worktree::index::checkout::Options;
use tempfile::TempDir;

use crate::fixture_path;

#[test]
fn allow_symlinks() -> crate::Result {
let opts = opts_with_symlink(true);
if !git_worktree::fs::Capabilities::probe(std::env::current_dir()?.join("..").join(".git")).symlink {
eprintln!("IGNORING symlink test on file system without symlink support");
// skip if symlinks aren't supported anyway.
return Ok(());
};
let (source_tree, destination) = setup_fixture_with_options(opts, "make_mixed_without_submodules")?;

assert_equality(&source_tree, &destination, opts.fs.symlink)?;
Ok(())
fn probe_gitoxide_dir() -> crate::Result<Capabilities> {
Ok(git_worktree::fs::Capabilities::probe(
std::env::current_dir()?.join("..").join(".git"),
))
}

fn opts_with_symlink(symlink: bool) -> Options {
fn opts_with_symlink(symlink: bool) -> index::checkout::Options {
index::checkout::Options {
fs: git_worktree::fs::Capabilities {
symlink,
Expand All @@ -41,14 +33,67 @@ mod checkout {
#[test]
fn symlinks_become_files_if_disabled() -> crate::Result {
let opts = opts_with_symlink(false);
let (source_tree, destination) = setup_fixture_with_options(opts, "make_mixed_without_submodules")?;
let (source_tree, destination, _index) = checkout_index_in_tmp_dir(opts, "make_mixed_without_submodules")?;

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

Ok(())
}

fn assert_equality(source_tree: &Path, destination: &TempDir, allow_symlinks: bool) -> crate::Result {
#[test]
fn allow_symlinks() -> crate::Result {
let opts = opts_with_symlink(true);
if !probe_gitoxide_dir()?.symlink {
eprintln!("IGNORING symlink test on file system without symlink support");
// skip if symlinks aren't supported anyway.
return Ok(());
};
let (source_tree, destination, _index) = checkout_index_in_tmp_dir(opts, "make_mixed_without_submodules")?;

assert_equality(&source_tree, &destination, opts.fs.symlink)?;
Ok(())
}

#[test]
fn no_case_related_collisions_on_case_sensitive_filesystem() {
if probe_gitoxide_dir().unwrap().ignore_case {
eprintln!("Skipping case-sensitive testing on what would be a case-insenstive file system");
return;
}
let opts = opts_with_symlink(true);
let (source_tree, destination, index) = checkout_index_in_tmp_dir(opts, "make_ignorecase_collisions").unwrap();
assert_eq!(index.entries().len(), 2, "there is just one colliding item");

let num_files = assert_equality(&source_tree, &destination, opts.fs.symlink).unwrap();
assert_eq!(num_files, index.entries().len(), "it checks out all files");
}

#[test]
fn collisions_are_detected_on_a_case_sensitive_filesystem() {
if !probe_gitoxide_dir().unwrap().ignore_case {
eprintln!("Skipping case-insensitive testing on what would be a case-senstive file system");
return;
}
let opts = opts_with_symlink(true);
let (source_tree, destination, index) = checkout_index_in_tmp_dir(opts, "make_ignorecase_collisions").unwrap();
assert_eq!(index.entries().len(), 2, "there is just one colliding item");

let source_files = dir_structure(&source_tree);
assert_eq!(
stripped_prefix(&source_tree, &source_files),
vec![PathBuf::from("a")],
"the source also only contains the first created file"
);

let dest_files = dir_structure(&destination);
assert_eq!(
stripped_prefix(&destination, &dest_files),
vec![PathBuf::from("A")],
"it only creates the first file of a collision"
);
}

fn assert_equality(source_tree: &Path, destination: &TempDir, allow_symlinks: bool) -> crate::Result<usize> {
let source_files = dir_structure(source_tree);
let worktree_files = dir_structure(&destination);

Expand All @@ -57,7 +102,9 @@ mod checkout {
stripped_prefix(&destination, &worktree_files),
);

let mut count = 0;
for (source_file, worktree_file) in source_files.iter().zip(worktree_files.iter()) {
count += 1;
if !allow_symlinks && source_file.is_symlink() {
assert!(!worktree_file.is_symlink());
assert_eq!(fs::read(worktree_file)?.to_path()?, fs::read_link(source_file)?);
Expand All @@ -71,7 +118,7 @@ mod checkout {
);
}
}
Ok(())
Ok(count)
}

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

fn setup_fixture_with_options(
opts: git_worktree::index::checkout::Options,
fn checkout_index_in_tmp_dir(
opts: index::checkout::Options,
name: &str,
) -> crate::Result<(PathBuf, TempDir)> {
) -> crate::Result<(PathBuf, TempDir, git_index::File)> {
let source_tree = fixture_path(name);
let git_dir = source_tree.join(".git");
let mut index = git_index::File::at(git_dir.join("index"), Default::default())?;
Expand All @@ -103,7 +150,7 @@ mod checkout {
move |oid, buf| odb.find_blob(oid, buf).ok(),
opts,
)?;
Ok((source_tree, destination))
Ok((source_tree, destination, index))
}

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

0 comments on commit 0854024

Please sign in to comment.