use std::path::PathBuf; use git_discover::repository::Kind; fn expected_trust() -> git_sec::Trust { if std::env::var_os("GITOXIDE_TEST_EXPECT_REDUCED_TRUST").is_some() { git_sec::Trust::Reduced } else { git_sec::Trust::Full }} mod ceiling_dirs; #[test]fn from_bare_git_dir() -> crate::Result { let dir = repo_path()?.join("bare.git"); let (path, trust) = git_discover::upwards(&dir)?; assert_eq!(path.as_ref(), dir, "the bare .git dir is directly returned"); assert_eq!(path.kind(), Kind::Bare); assert_eq!(trust, expected_trust()); Ok(())} #[test]fn from_inside_bare_git_dir() -> crate::Result { let git_dir = repo_path()?.join("bare.git"); let dir = git_dir.join("objects"); let (path, trust) = git_discover::upwards(&dir)?; assert_eq!( path.as_ref(), git_dir, "the bare .git dir is found while traversing upwards" ); assert_eq!(path.kind(), Kind::Bare); assert_eq!(trust, expected_trust()); Ok(())} #[test]fn from_git_dir() -> crate::Result { let dir = repo_path()?.join(".git"); let (path, trust) = git_discover::upwards(&dir)?; assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None }); assert_eq!( path.into_repository_and_work_tree_directories().0, dir, "the .git dir is directly returned if valid" ); assert_eq!(trust, expected_trust()); Ok(())} #[test]fn from_working_dir() -> crate::Result { let dir = repo_path()?; let (path, trust) = git_discover::upwards(&dir)?; assert_eq!(path.as_ref(), dir, "a working tree dir yields the git dir"); assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None }); assert_eq!(trust, expected_trust()); Ok(())} #[test]fn from_nested_dir() -> crate::Result { let working_dir = repo_path()?; let dir = working_dir.join("some/very/deeply/nested/subdir"); let (path, trust) = git_discover::upwards(&dir)?; assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None }); assert_eq!(path.as_ref(), working_dir, "a working tree dir yields the git dir"); assert_eq!(trust, expected_trust()); Ok(())} #[test]fn from_dir_with_dot_dot() -> crate::Result { // This would be neater if we could just change the actual working directory, // but Rust tests run in parallel by default so we'd interfere with other tests. // Instead ensure it finds the gitoxide repo instead of a test repo if we crawl // up far enough. (This tests that `discover::existing` canonicalizes paths before // exploring ancestors.) let working_dir = repo_path()?; let dir = working_dir.join("some/very/deeply/nested/subdir/../../../../../.."); let (path, trust) = git_discover::upwards(&dir)?; assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None }); assert_eq!( path.as_ref(), std::path::Path::new(".."), "there is only the minimal amount of relative path components to see this worktree" ); assert_ne!( path.as_ref().canonicalize()?, working_dir.canonicalize()?, "a relative path that climbs above the test repo should yield the gitoxide repo" ); assert_eq!(trust, expected_trust()); Ok(())} #[test]fn from_nested_dir_inside_a_git_dir() -> crate::Result { let working_dir = repo_path()?; let dir = working_dir.join(".git").join("objects"); let (path, trust) = git_discover::upwards(&dir)?; assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None }); assert_eq!(path.as_ref(), working_dir, "we find .git directories on the way"); assert_eq!(trust, expected_trust()); Ok(())} #[test]fn from_non_existing_worktree() { let top_level_repo = repo_path().unwrap(); let (path, _trust) = git_discover::upwards(top_level_repo.join("worktrees/b-private-dir-deleted")).unwrap(); assert_eq!(path, git_discover::repository::Path::WorkTree(top_level_repo.clone())); let (path, _trust) = git_discover::upwards(top_level_repo.join("worktrees/from-bare/d-private-dir-deleted")).unwrap(); assert_eq!(path, git_discover::repository::Path::WorkTree(top_level_repo));} #[test]fn from_existing_worktree_inside_dot_git() { let top_level_repo = repo_path().unwrap(); let (path, _trust) = git_discover::upwards(top_level_repo.join(".git/worktrees/a")).unwrap(); let suffix = std::path::Path::new(top_level_repo.file_name().unwrap()) .join("worktrees") .join("a"); assert!( matches!(path, git_discover::repository::Path::LinkedWorkTree { work_dir, .. } if work_dir.ends_with(suffix)), "we can handle to start from within a (somewhat partial) worktree git dir" );} #[test]fn from_non_existing_worktree_inside_dot_git() { let top_level_repo = repo_path().unwrap(); let (path, _trust) = git_discover::upwards(top_level_repo.join(".git/worktrees/c-worktree-deleted")).unwrap(); let suffix = std::path::Path::new(top_level_repo.file_name().unwrap()) .join("worktrees") .join("c-worktree-deleted"); assert!( matches!(path, git_discover::repository::Path::LinkedWorkTree { work_dir, .. } if work_dir.ends_with(suffix)), "it's no problem if work-dirs don't exist - this can be discovered later and a lot of operations are possible anyway." );} #[test]fn from_existing_worktree() -> crate::Result { let top_level_repo = repo_path()?; for (discover_path, expected_worktree_path, expected_git_dir) in [ (top_level_repo.join("worktrees/a"), "worktrees/a", ".git/worktrees/a"), ( top_level_repo.join("worktrees/from-bare/c"), "worktrees/from-bare/c", "bare.git/worktrees/c", ), ] { let (path, trust) = git_discover::upwards(discover_path)?; assert!(matches!(path, git_discover::repository::Path::LinkedWorkTree { .. })); assert_eq!(trust, expected_trust()); let (git_dir, worktree) = path.into_repository_and_work_tree_directories(); assert_eq!( git_dir.strip_prefix(git_path::realpath(&top_level_repo).unwrap()), Ok(std::path::Path::new(expected_git_dir)), "we don't skip over worktrees and discover their git dir (gitdir is absolute in file)" ); let worktree = worktree.expect("linked worktree is set"); assert_eq!( worktree.strip_prefix(&top_level_repo), Ok(std::path::Path::new(expected_worktree_path)), "the worktree path is the .git file's directory" ); } Ok(())} #[cfg(target_os = "macos")]#[test]fn cross_fs() -> crate::Result { use std::{os::unix::fs::symlink, process::Command}; use git_discover::upwards::Options; let top_level_repo = git_testtools::scripted_fixture_repo_writable("make_basic_repo.sh")?; let _cleanup = { // Create an empty dmg file let dmg_location = tempfile::tempdir()?; let dmg_file = dmg_location.path().join("temp.dmg"); Command::new("hdiutil") .args(&["create", "-size", "1m"]) .arg(&dmg_file) .status()?; // Mount dmg file into temporary location let mount_point = tempfile::tempdir()?; Command::new("hdiutil") .args(&["attach", "-nobrowse", "-mountpoint"]) .arg(mount_point.path()) .arg(&dmg_file) .status()?; // Ensure that the mount point is always cleaned up let cleanup = defer::defer({ let arg = mount_point.path().to_owned(); move || { Command::new("hdiutil") .arg("detach") .arg(arg) .status() .expect("detach temporary test dmg filesystem successfully"); } }); // Symlink the mount point into the repo symlink(mount_point.path(), top_level_repo.path().join("remote"))?; cleanup }; let res = git_discover::upwards(top_level_repo.path().join("remote")) .expect_err("the cross-fs option should prevent us from discovering the repo"); assert!(matches!( res, git_discover::upwards::Error::NoGitRepositoryWithinFs { .. } )); let (repo_path, _trust) = git_discover::upwards_opts( &top_level_repo.path().join("remote"), Options { cross_fs: true, ..Default::default() }, ) .expect("the cross-fs option should allow us to discover the repo"); assert_eq!( repo_path .into_repository_and_work_tree_directories() .1 .expect("work dir") .file_name(), top_level_repo.path().file_name() ); Ok(())} #[test]fn do_not_shorten_absolute_paths() -> crate::Result { let top_level_repo = repo_path()?.canonicalize().expect("repo path exists"); let (repo_path, _trust) = git_discover::upwards(top_level_repo).expect("we can discover the repo"); match repo_path { git_discover::repository::Path::WorkTree(work_dir) => { assert!(work_dir.is_absolute()); } _ => panic!("expected worktree path"), }; Ok(())} mod submodules { #[test] fn by_their_worktree_checkout() -> crate::Result { let dir = git_testtools::scripted_fixture_repo_read_only("make_submodules.sh")?; let parent = dir.join("with-submodules"); let modules = parent.join(".git").join("modules"); for module in ["m1", "dir/m1"] { let submodule_m1_workdir = parent.join(module); let submodule_m1_gitdir = modules.join(module); let (path, _trust) = git_discover::upwards(&submodule_m1_workdir)?; assert!( matches!(path, git_discover::repository::Path::LinkedWorkTree{ref work_dir, ref git_dir} if work_dir == &submodule_m1_workdir && git_dir == &submodule_m1_gitdir), "{:?} should match {:?} {:?}", path, submodule_m1_workdir, submodule_m1_gitdir ); let (path, _trust) = git_discover::upwards(&submodule_m1_workdir.join("subdir"))?; assert!( matches!(path, git_discover::repository::Path::LinkedWorkTree{ref work_dir, ref git_dir} if work_dir == &submodule_m1_workdir && git_dir == &submodule_m1_gitdir), "{:?} should match {:?} {:?}", path, submodule_m1_workdir, submodule_m1_gitdir ); } Ok(()) } #[test] fn by_their_module_git_dir() -> crate::Result { let dir = git_testtools::scripted_fixture_repo_read_only("make_submodules.sh")?; let modules = dir.join("with-submodules").join(".git").join("modules"); for module in ["m1", "dir/m1"] { let submodule_m1_gitdir = modules.join(module); let (path, _trust) = git_discover::upwards(&submodule_m1_gitdir)?; assert!( matches!(path, git_discover::repository::Path::Repository(ref dir) if dir == &submodule_m1_gitdir), "{:?} should match {:?}", path, submodule_m1_gitdir ); } Ok(()) }} fn repo_path() -> crate::Result<PathBuf> { git_testtools::scripted_fixture_repo_read_only("make_basic_repo.sh")}
use std::path::PathBuf;
use git_discover::repository::Kind;
fn expected_trust() -> git_sec::Trust {
if std::env::var_os("GITOXIDE_TEST_EXPECT_REDUCED_TRUST").is_some() {
git_sec::Trust::Reduced
} else {
git_sec::Trust::Full
}
}
mod ceiling_dirs;
#[test]
fn from_bare_git_dir() -> crate::Result {
let dir = repo_path()?.join("bare.git");
let (path, trust) = git_discover::upwards(&dir)?;
assert_eq!(path.as_ref(), dir, "the bare .git dir is directly returned");
assert_eq!(path.kind(), Kind::Bare);
assert_eq!(trust, expected_trust());
Ok(())
}
#[test]
fn from_inside_bare_git_dir() -> crate::Result {
let git_dir = repo_path()?.join("bare.git");
let dir = git_dir.join("objects");
let (path, trust) = git_discover::upwards(&dir)?;
assert_eq!(
path.as_ref(),
git_dir,
"the bare .git dir is found while traversing upwards"
);
assert_eq!(path.kind(), Kind::Bare);
assert_eq!(trust, expected_trust());
Ok(())
}
#[test]
fn from_git_dir() -> crate::Result {
let dir = repo_path()?.join(".git");
let (path, trust) = git_discover::upwards(&dir)?;
assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None });
assert_eq!(
path.into_repository_and_work_tree_directories().0,
dir,
"the .git dir is directly returned if valid"
);
assert_eq!(trust, expected_trust());
Ok(())
}
#[test]
fn from_working_dir() -> crate::Result {
let dir = repo_path()?;
let (path, trust) = git_discover::upwards(&dir)?;
assert_eq!(path.as_ref(), dir, "a working tree dir yields the git dir");
assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None });
assert_eq!(trust, expected_trust());
Ok(())
}
#[test]
fn from_nested_dir() -> crate::Result {
let working_dir = repo_path()?;
let dir = working_dir.join("some/very/deeply/nested/subdir");
let (path, trust) = git_discover::upwards(&dir)?;
assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None });
assert_eq!(path.as_ref(), working_dir, "a working tree dir yields the git dir");
assert_eq!(trust, expected_trust());
Ok(())
}
#[test]
fn from_dir_with_dot_dot() -> crate::Result {
// This would be neater if we could just change the actual working directory,
// but Rust tests run in parallel by default so we'd interfere with other tests.
// Instead ensure it finds the gitoxide repo instead of a test repo if we crawl
// up far enough. (This tests that `discover::existing` canonicalizes paths before
// exploring ancestors.)
let working_dir = repo_path()?;
let dir = working_dir.join("some/very/deeply/nested/subdir/../../../../../..");
let (path, trust) = git_discover::upwards(&dir)?;
assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None });
assert_eq!(
path.as_ref(),
std::path::Path::new(".."),
"there is only the minimal amount of relative path components to see this worktree"
);
assert_ne!(
path.as_ref().canonicalize()?,
working_dir.canonicalize()?,
"a relative path that climbs above the test repo should yield the gitoxide repo"
);
assert_eq!(trust, expected_trust());
Ok(())
}
#[test]
fn from_nested_dir_inside_a_git_dir() -> crate::Result {
let working_dir = repo_path()?;
let dir = working_dir.join(".git").join("objects");
let (path, trust) = git_discover::upwards(&dir)?;
assert_eq!(path.kind(), Kind::WorkTree { linked_git_dir: None });
assert_eq!(path.as_ref(), working_dir, "we find .git directories on the way");
assert_eq!(trust, expected_trust());
Ok(())
}
#[test]
fn from_non_existing_worktree() {
let top_level_repo = repo_path().unwrap();
let (path, _trust) = git_discover::upwards(top_level_repo.join("worktrees/b-private-dir-deleted")).unwrap();
assert_eq!(path, git_discover::repository::Path::WorkTree(top_level_repo.clone()));
let (path, _trust) =
git_discover::upwards(top_level_repo.join("worktrees/from-bare/d-private-dir-deleted")).unwrap();
assert_eq!(path, git_discover::repository::Path::WorkTree(top_level_repo));
}
#[test]
fn from_existing_worktree_inside_dot_git() {
let top_level_repo = repo_path().unwrap();
let (path, _trust) = git_discover::upwards(top_level_repo.join(".git/worktrees/a")).unwrap();
let suffix = std::path::Path::new(top_level_repo.file_name().unwrap())
.join("worktrees")
.join("a");
assert!(
matches!(path, git_discover::repository::Path::LinkedWorkTree { work_dir, .. } if work_dir.ends_with(suffix)),
"we can handle to start from within a (somewhat partial) worktree git dir"
);
}
#[test]
fn from_non_existing_worktree_inside_dot_git() {
let top_level_repo = repo_path().unwrap();
let (path, _trust) = git_discover::upwards(top_level_repo.join(".git/worktrees/c-worktree-deleted")).unwrap();
let suffix = std::path::Path::new(top_level_repo.file_name().unwrap())
.join("worktrees")
.join("c-worktree-deleted");
assert!(
matches!(path, git_discover::repository::Path::LinkedWorkTree { work_dir, .. } if work_dir.ends_with(suffix)),
"it's no problem if work-dirs don't exist - this can be discovered later and a lot of operations are possible anyway."
);
}
#[test]
fn from_existing_worktree() -> crate::Result {
let top_level_repo = repo_path()?;
for (discover_path, expected_worktree_path, expected_git_dir) in [
(top_level_repo.join("worktrees/a"), "worktrees/a", ".git/worktrees/a"),
(
top_level_repo.join("worktrees/from-bare/c"),
"worktrees/from-bare/c",
"bare.git/worktrees/c",
),
] {
let (path, trust) = git_discover::upwards(discover_path)?;
assert!(matches!(path, git_discover::repository::Path::LinkedWorkTree { .. }));
assert_eq!(trust, expected_trust());
let (git_dir, worktree) = path.into_repository_and_work_tree_directories();
assert_eq!(
git_dir.strip_prefix(git_path::realpath(&top_level_repo).unwrap()),
Ok(std::path::Path::new(expected_git_dir)),
"we don't skip over worktrees and discover their git dir (gitdir is absolute in file)"
);
let worktree = worktree.expect("linked worktree is set");
assert_eq!(
worktree.strip_prefix(&top_level_repo),
Ok(std::path::Path::new(expected_worktree_path)),
"the worktree path is the .git file's directory"
);
}
Ok(())
}
#[cfg(target_os = "macos")]
#[test]
fn cross_fs() -> crate::Result {
use std::{os::unix::fs::symlink, process::Command};
use git_discover::upwards::Options;
let top_level_repo = git_testtools::scripted_fixture_repo_writable("make_basic_repo.sh")?;
let _cleanup = {
// Create an empty dmg file
let dmg_location = tempfile::tempdir()?;
let dmg_file = dmg_location.path().join("temp.dmg");
Command::new("hdiutil")
.args(&["create", "-size", "1m"])
.arg(&dmg_file)
.status()?;
// Mount dmg file into temporary location
let mount_point = tempfile::tempdir()?;
Command::new("hdiutil")
.args(&["attach", "-nobrowse", "-mountpoint"])
.arg(mount_point.path())
.arg(&dmg_file)
.status()?;
// Ensure that the mount point is always cleaned up
let cleanup = defer::defer({
let arg = mount_point.path().to_owned();
move || {
Command::new("hdiutil")
.arg("detach")
.arg(arg)
.status()
.expect("detach temporary test dmg filesystem successfully");
}
});
// Symlink the mount point into the repo
symlink(mount_point.path(), top_level_repo.path().join("remote"))?;
cleanup
};
let res = git_discover::upwards(top_level_repo.path().join("remote"))
.expect_err("the cross-fs option should prevent us from discovering the repo");
assert!(matches!(
res,
git_discover::upwards::Error::NoGitRepositoryWithinFs { .. }
));
let (repo_path, _trust) = git_discover::upwards_opts(
&top_level_repo.path().join("remote"),
Options {
cross_fs: true,
..Default::default()
},
)
.expect("the cross-fs option should allow us to discover the repo");
assert_eq!(
repo_path
.into_repository_and_work_tree_directories()
.1
.expect("work dir")
.file_name(),
top_level_repo.path().file_name()
);
Ok(())
}
#[test]
fn do_not_shorten_absolute_paths() -> crate::Result {
let top_level_repo = repo_path()?.canonicalize().expect("repo path exists");
let (repo_path, _trust) = git_discover::upwards(top_level_repo).expect("we can discover the repo");
match repo_path {
git_discover::repository::Path::WorkTree(work_dir) => {
assert!(work_dir.is_absolute());
}
_ => panic!("expected worktree path"),
};
Ok(())
}
mod submodules {
#[test]
fn by_their_worktree_checkout() -> crate::Result {
let dir = git_testtools::scripted_fixture_repo_read_only("make_submodules.sh")?;
let parent = dir.join("with-submodules");
let modules = parent.join(".git").join("modules");
for module in ["m1", "dir/m1"] {
let submodule_m1_workdir = parent.join(module);
let submodule_m1_gitdir = modules.join(module);
let (path, _trust) = git_discover::upwards(&submodule_m1_workdir)?;
assert!(
matches!(path, git_discover::repository::Path::LinkedWorkTree{ref work_dir, ref git_dir} if work_dir == &submodule_m1_workdir && git_dir == &submodule_m1_gitdir),
"{:?} should match {:?} {:?}",
path,
submodule_m1_workdir,
submodule_m1_gitdir
);
let (path, _trust) = git_discover::upwards(&submodule_m1_workdir.join("subdir"))?;
assert!(
matches!(path, git_discover::repository::Path::LinkedWorkTree{ref work_dir, ref git_dir} if work_dir == &submodule_m1_workdir && git_dir == &submodule_m1_gitdir),
"{:?} should match {:?} {:?}",
path,
submodule_m1_workdir,
submodule_m1_gitdir
);
}
Ok(())
}
#[test]
fn by_their_module_git_dir() -> crate::Result {
let dir = git_testtools::scripted_fixture_repo_read_only("make_submodules.sh")?;
let modules = dir.join("with-submodules").join(".git").join("modules");
for module in ["m1", "dir/m1"] {
let submodule_m1_gitdir = modules.join(module);
let (path, _trust) = git_discover::upwards(&submodule_m1_gitdir)?;
assert!(
matches!(path, git_discover::repository::Path::Repository(ref dir) if dir == &submodule_m1_gitdir),
"{:?} should match {:?}",
path,
submodule_m1_gitdir
);
}
Ok(())
}
}
fn repo_path() -> crate::Result<PathBuf> {
git_testtools::scripted_fixture_repo_read_only("make_basic_repo.sh")
}