mod create_directory { use std::path::Path; use git_worktree::fs; use tempfile::{tempdir, TempDir}; fn panic_on_find<'buf>( _oid: &git_hash::oid, _buf: &'buf mut Vec, ) -> std::io::Result> { unreachable!("find should nto be called") } #[test] fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() -> crate::Result { let dir = tempdir()?; let mut cache = fs::Cache::new( dir.path().join("non-existing-root"), fs::cache::State::for_checkout(false, Default::default()), Default::default(), Vec::new(), Default::default(), ); assert_eq!(cache.num_mkdir_calls(), 0); let path = cache.at_path("hello", Some(false), panic_on_find)?.path(); assert!(!path.parent().unwrap().exists(), "prefix itself is never created"); assert_eq!(cache.num_mkdir_calls(), 0); Ok(()) } #[test] fn directory_paths_are_created_in_full() { let (mut cache, _tmp) = new_cache(); for (name, is_dir) in &[ ("dir", Some(true)), ("submodule", Some(true)), ("file", Some(false)), ("exe", Some(false)), ("link", None), ] { let path = cache .at_path(Path::new("dir").join(name), *is_dir, panic_on_find) .unwrap() .path(); assert!(path.parent().unwrap().is_dir(), "dir exists"); } assert_eq!(cache.num_mkdir_calls(), 3); } #[test] fn existing_directories_are_fine() -> crate::Result { let (mut cache, tmp) = new_cache(); std::fs::create_dir(tmp.path().join("dir"))?; let path = cache.at_path("dir/file", Some(false), panic_on_find)?.path(); assert!(path.parent().unwrap().is_dir(), "directory is still present"); assert!(!path.exists(), "it won't create the file"); assert_eq!(cache.num_mkdir_calls(), 1); Ok(()) } #[test] fn symlinks_or_files_in_path_are_forbidden_or_unlinked_when_forced() -> crate::Result { let (mut cache, tmp) = new_cache(); let forbidden = tmp.path().join("forbidden"); std::fs::create_dir(&forbidden)?; symlink::symlink_dir(&forbidden, tmp.path().join("link-to-dir"))?; std::fs::write(tmp.path().join("file-in-dir"), &[])?; for dirname in &["file-in-dir", "link-to-dir"] { cache.unlink_on_collision(false); let relative_path = format!("{}/file", dirname); assert_eq!( cache .at_path(&relative_path, Some(false), panic_on_find) .unwrap_err() .kind(), std::io::ErrorKind::AlreadyExists ); } assert_eq!( cache.num_mkdir_calls(), 2, "it tries to create each directory once, but it's a file" ); cache.reset_mkdir_calls(); for dirname in &["link-to-dir", "file-in-dir"] { cache.unlink_on_collision(true); let relative_path = format!("{}/file", dirname); let path = cache.at_path(&relative_path, Some(false), panic_on_find)?.path(); assert!(path.parent().unwrap().is_dir(), "directory was forcefully created"); assert!(!path.exists()); } assert_eq!( cache.num_mkdir_calls(), 4, "like before, but it unlinks what's there and tries again" ); Ok(()) } fn new_cache() -> (fs::Cache<'static>, TempDir) { let dir = tempdir().unwrap(); let cache = fs::Cache::new( dir.path(), fs::cache::State::for_checkout(false, Default::default()), Default::default(), Vec::new(), Default::default(), ); (cache, dir) } } #[allow(unused)] mod ignore_and_attributes { use bstr::{BStr, ByteSlice}; use std::path::Path; use git_glob::pattern::Case; use git_index::entry::Mode; use git_odb::pack::bundle::write::Options; use git_odb::FindExt; use git_testtools::hex_to_id; use git_worktree::fs; use tempfile::{tempdir, TempDir}; struct IgnoreExpectations<'a> { lines: bstr::Lines<'a>, } impl<'a> Iterator for IgnoreExpectations<'a> { type Item = (&'a BStr, Option<(&'a BStr, usize, &'a BStr)>); fn next(&mut self) -> Option { let line = self.lines.next()?; let (left, value) = line.split_at(line.find_byte(b'\t').unwrap()); let value = value[1..].as_bstr(); let source_and_line = if left == b"::" { None } else { let mut tokens = left.split(|b| *b == b':'); let source = tokens.next().unwrap().as_bstr(); let line_number: usize = tokens.next().unwrap().to_str_lossy().parse().ok().unwrap(); let pattern = tokens.next().unwrap().as_bstr(); Some((source, line_number, pattern)) }; Some((value, source_and_line)) } } #[test] fn check_against_baseline() -> crate::Result { let dir = git_testtools::scripted_fixture_repo_read_only("make_ignore_and_attributes_setup.sh")?; let worktree_dir = dir.join("repo"); let git_dir = worktree_dir.join(".git"); let mut buf = Vec::new(); let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?; let user_exclude_path = dir.join("user.exclude"); assert!(user_exclude_path.is_file()); let mut index = git_index::File::at(git_dir.join("index"), Default::default())?; let odb = git_odb::at(git_dir.join("objects"))?; let case = git_glob::pattern::Case::Sensitive; let state = git_worktree::fs::cache::State::for_add( Default::default(), // TODO: attribute tests git_worktree::fs::cache::state::Ignore::new( git_attributes::MatchGroup::from_overrides(vec!["!force-include"]), git_attributes::MatchGroup::from_git_dir(&git_dir, Some(user_exclude_path), &mut buf)?, None, case, ), ); let paths_storage = index.take_path_backing(); let attribute_files_in_index = state.build_attribute_list(&index.state, &paths_storage, case); assert_eq!( attribute_files_in_index, vec![( "other-dir-with-ignore/.gitignore".as_bytes().as_bstr(), hex_to_id("5c7e0ed672d3d31d83a3df61f13cc8f7b22d5bfd") )] ); let mut cache = fs::Cache::new(&worktree_dir, state, case, buf, attribute_files_in_index); for (relative_entry, source_and_line) in (IgnoreExpectations { lines: baseline.lines(), }) { let relative_path = git_path::from_byte_slice(relative_entry); let is_dir = worktree_dir.join(&relative_path).metadata().ok().map(|m| m.is_dir()); let platform = cache.at_entry(relative_entry, is_dir, |oid, buf| odb.find_blob(oid, buf))?; let match_ = platform.matching_exclude_pattern(); let is_excluded = platform.is_excluded(); match (match_, source_and_line) { (None, None) => { assert!(!is_excluded); } (Some(m), Some((source_file, line, _pattern))) => { assert_eq!(m.sequence_number, line); // Paths read from the index are relative to the repo, and they don't exist locally due tot skip-worktree if m.source.map_or(false, |p| p.exists()) { assert_eq!( m.source.map(|p| p.canonicalize().unwrap()), Some(worktree_dir.join(source_file.to_str_lossy().as_ref()).canonicalize()?) ); } } (actual, expected) => { panic!( "actual {:?} didn't match {:?} at '{}'", actual, expected, relative_entry ); } } } cache.set_case(Case::Fold); let platform = cache.at_entry("User-file-ANYWHERE", Some(false), |oid, buf| odb.find_blob(oid, buf))?; let m = platform.matching_exclude_pattern().expect("match"); assert_eq!(m.pattern.text, "user-file-anywhere"); Ok(()) } }