use std::path::Path; use gix_testtools::tempfile::{TempDir, tempdir};use gix_worktree::{Stack, stack}; const IS_FILE: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::FILE);const IS_DIR: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::DIR);const IS_SYMLINK: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::SYMLINK); #[test]fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() -> crate::Result { let dir = tempdir()?; let mut cache = Stack::new( dir.path().join("non-existing-root"), stack::State::for_checkout(false, Default::default(), Default::default()), Default::default(), Vec::new(), Default::default(), ); assert_eq!(cache.statistics().delegate.num_mkdir_calls, 0); let path = cache.at_path("hello", IS_FILE, &gix_object::find::Never)?.path(); assert!(!path.parent().unwrap().exists(), "prefix itself is never created"); assert_eq!(cache.statistics().delegate.num_mkdir_calls, 0); Ok(())} #[test]fn directory_paths_are_created_in_full() { let (mut cache, _tmp) = new_cache(); for (name, mode) in [ ("dir", IS_DIR), ("submodule", IS_DIR), ("file", IS_FILE), ("exe", IS_FILE), ("link", None), ] { let path = cache .at_path(Path::new("dir").join(name), mode, &gix_object::find::Never) .unwrap() .path(); assert!(path.parent().unwrap().is_dir(), "dir exists"); } assert_eq!(cache.statistics().delegate.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", IS_FILE, &gix_object::find::Never)?.path(); assert!(path.parent().unwrap().is_dir(), "directory is still present"); assert!(!path.exists(), "it won't create the file"); assert_eq!(cache.statistics().delegate.num_mkdir_calls, 1); Ok(())} #[test]fn validation_to_each_component() -> crate::Result { let (mut cache, tmp) = new_cache(); let err = cache .at_path("valid/.gIt", IS_FILE, &gix_object::find::Never) .unwrap_err(); assert_eq!( cache.statistics().delegate.num_mkdir_calls, 1, "the valid directory was created" ); assert!(tmp.path().join("valid").is_dir(), "it was actually created"); assert_eq!(err.to_string(), "The .git name may never be used"); 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"] { if let stack::State::CreateDirectoryAndAttributesStack { unlink_on_collision, .. } = cache.state_mut() { *unlink_on_collision = false; } let relative_path = format!("{dirname}/file"); assert_eq!( cache .at_path(&*relative_path, IS_FILE, &gix_object::find::Never) .unwrap_err() .kind(), std::io::ErrorKind::AlreadyExists ); } assert_eq!( cache.statistics().delegate.num_mkdir_calls, 2, "it tries to create each directory once, but it's a file" ); cache.take_statistics(); for dirname in &["link-to-dir", "file-in-dir"] { if let stack::State::CreateDirectoryAndAttributesStack { unlink_on_collision, .. } = cache.state_mut() { *unlink_on_collision = true; } let relative_path = format!("{dirname}/file"); let path = cache .at_path(&*relative_path, IS_FILE, &gix_object::find::Never)? .path(); assert!(path.parent().unwrap().is_dir(), "directory was forcefully created"); assert!(!path.exists()); } assert_eq!( cache.statistics().delegate.num_mkdir_calls, 4, "like before, but it unlinks what's there and tries again" ); Ok(())} #[test]fn symlink_cached_as_file_is_revalidated_before_use_as_directory() -> crate::Result { let (mut cache, tmp) = new_cache(); let forbidden = tmp.path().join("forbidden"); std::fs::create_dir(&forbidden)?; let link_path = cache .at_path("link", IS_SYMLINK, &gix_object::find::Never)? .path() .to_owned(); symlink::symlink_dir(&forbidden, &link_path)?; let err = cache .at_path("link/file", IS_SYMLINK, &gix_object::find::Never) .unwrap_err(); assert_eq!(err.kind(), std::io::ErrorKind::AlreadyExists); assert!( link_path.symlink_metadata()?.file_type().is_symlink(), "the existing symlink must remain in place when collisions are forbidden" ); Ok(())} #[test]fn symlink_cached_as_file_is_unlinked_before_use_as_directory_when_forced() -> crate::Result { let (mut cache, tmp) = new_cache(); let forbidden = tmp.path().join("forbidden"); std::fs::create_dir(&forbidden)?; let link_path = cache .at_path("link", IS_SYMLINK, &gix_object::find::Never)? .path() .to_owned(); symlink::symlink_dir(&forbidden, &link_path)?; if let stack::State::CreateDirectoryAndAttributesStack { unlink_on_collision, .. } = cache.state_mut() { *unlink_on_collision = true; } let path = cache.at_path("link/file", IS_SYMLINK, &gix_object::find::Never)?.path(); assert_eq!(path, tmp.path().join("link").join("file")); assert!( link_path.symlink_metadata()?.is_dir(), "the existing symlink must be replaced with a directory when collisions may be unlinked" ); Ok(())} fn new_cache() -> (Stack, TempDir) { let dir = tempdir().unwrap(); let cache = Stack::new( dir.path(), stack::State::for_checkout(false, Default::default(), Default::default()), Default::default(), Vec::new(), Default::default(), ); (cache, dir)}
use std::path::Path;
use gix_testtools::tempfile::{TempDir, tempdir};
use gix_worktree::{Stack, stack};
const IS_FILE: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::FILE);
const IS_DIR: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::DIR);
const IS_SYMLINK: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::SYMLINK);
#[test]
fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() -> crate::Result {
let dir = tempdir()?;
let mut cache = Stack::new(
dir.path().join("non-existing-root"),
stack::State::for_checkout(false, Default::default(), Default::default()),
Default::default(),
Vec::new(),
Default::default(),
);
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 0);
let path = cache.at_path("hello", IS_FILE, &gix_object::find::Never)?.path();
assert!(!path.parent().unwrap().exists(), "prefix itself is never created");
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 0);
Ok(())
}
#[test]
fn directory_paths_are_created_in_full() {
let (mut cache, _tmp) = new_cache();
for (name, mode) in [
("dir", IS_DIR),
("submodule", IS_DIR),
("file", IS_FILE),
("exe", IS_FILE),
("link", None),
] {
let path = cache
.at_path(Path::new("dir").join(name), mode, &gix_object::find::Never)
.unwrap()
.path();
assert!(path.parent().unwrap().is_dir(), "dir exists");
}
assert_eq!(cache.statistics().delegate.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", IS_FILE, &gix_object::find::Never)?.path();
assert!(path.parent().unwrap().is_dir(), "directory is still present");
assert!(!path.exists(), "it won't create the file");
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 1);
Ok(())
}
#[test]
fn validation_to_each_component() -> crate::Result {
let (mut cache, tmp) = new_cache();
let err = cache
.at_path("valid/.gIt", IS_FILE, &gix_object::find::Never)
.unwrap_err();
assert_eq!(
cache.statistics().delegate.num_mkdir_calls,
1,
"the valid directory was created"
);
assert!(tmp.path().join("valid").is_dir(), "it was actually created");
assert_eq!(err.to_string(), "The .git name may never be used");
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"] {
if let stack::State::CreateDirectoryAndAttributesStack {
unlink_on_collision, ..
} = cache.state_mut()
{
*unlink_on_collision = false;
}
let relative_path = format!("{dirname}/file");
assert_eq!(
cache
.at_path(&*relative_path, IS_FILE, &gix_object::find::Never)
.unwrap_err()
.kind(),
std::io::ErrorKind::AlreadyExists
);
}
assert_eq!(
cache.statistics().delegate.num_mkdir_calls,
2,
"it tries to create each directory once, but it's a file"
);
cache.take_statistics();
for dirname in &["link-to-dir", "file-in-dir"] {
if let stack::State::CreateDirectoryAndAttributesStack {
unlink_on_collision, ..
} = cache.state_mut()
{
*unlink_on_collision = true;
}
let relative_path = format!("{dirname}/file");
let path = cache
.at_path(&*relative_path, IS_FILE, &gix_object::find::Never)?
.path();
assert!(path.parent().unwrap().is_dir(), "directory was forcefully created");
assert!(!path.exists());
}
assert_eq!(
cache.statistics().delegate.num_mkdir_calls,
4,
"like before, but it unlinks what's there and tries again"
);
Ok(())
}
#[test]
fn symlink_cached_as_file_is_revalidated_before_use_as_directory() -> crate::Result {
let (mut cache, tmp) = new_cache();
let forbidden = tmp.path().join("forbidden");
std::fs::create_dir(&forbidden)?;
let link_path = cache
.at_path("link", IS_SYMLINK, &gix_object::find::Never)?
.path()
.to_owned();
symlink::symlink_dir(&forbidden, &link_path)?;
let err = cache
.at_path("link/file", IS_SYMLINK, &gix_object::find::Never)
.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::AlreadyExists);
assert!(
link_path.symlink_metadata()?.file_type().is_symlink(),
"the existing symlink must remain in place when collisions are forbidden"
);
Ok(())
}
#[test]
fn symlink_cached_as_file_is_unlinked_before_use_as_directory_when_forced() -> crate::Result {
let (mut cache, tmp) = new_cache();
let forbidden = tmp.path().join("forbidden");
std::fs::create_dir(&forbidden)?;
let link_path = cache
.at_path("link", IS_SYMLINK, &gix_object::find::Never)?
.path()
.to_owned();
symlink::symlink_dir(&forbidden, &link_path)?;
if let stack::State::CreateDirectoryAndAttributesStack {
unlink_on_collision, ..
} = cache.state_mut()
{
*unlink_on_collision = true;
}
let path = cache.at_path("link/file", IS_SYMLINK, &gix_object::find::Never)?.path();
assert_eq!(path, tmp.path().join("link").join("file"));
assert!(
link_path.symlink_metadata()?.is_dir(),
"the existing symlink must be replaced with a directory when collisions may be unlinked"
);
Ok(())
}
fn new_cache() -> (Stack, TempDir) {
let dir = tempdir().unwrap();
let cache = Stack::new(
dir.path(),
stack::State::for_checkout(false, Default::default(), Default::default()),
Default::default(),
Vec::new(),
Default::default(),
);
(cache, dir)
}