use grit_lib::repo::{InitOptions, Repository};
use grit_lib::worktree::pathspec::{PathspecResolveError, ResolvedPathspecs};
use std::fs;
fn init_repo() -> (tempfile::TempDir, Repository) {
let dir = tempfile::tempdir().expect("tempdir");
let repo = Repository::init(dir.path(), &InitOptions::default()).expect("init");
(dir, repo)
}
#[test]
fn resolves_relative_to_prefix_and_normalizes() {
let (_dir, repo) = init_repo();
assert_eq!(
repo.resolve_worktree_pathspec("a.txt", None).expect("root"),
"a.txt"
);
assert_eq!(
repo.resolve_worktree_pathspec("a.txt", Some("sub"))
.expect("prefix"),
"sub/a.txt"
);
assert_eq!(
repo.resolve_worktree_pathspec("../b/./c.txt", Some("sub/deep"))
.expect("dotdot"),
"sub/b/c.txt"
);
}
#[test]
fn magic_pathspecs_pass_through() {
let (_dir, repo) = init_repo();
assert_eq!(
repo.resolve_worktree_pathspec(":(exclude)vendor", None)
.expect("long magic"),
":(exclude)vendor"
);
assert_eq!(
repo.resolve_worktree_pathspec(":(exclude)vendor", Some("sub"))
.expect("long magic under prefix"),
":(exclude,prefix:sub/)vendor"
);
}
#[test]
fn absolute_path_outside_work_tree_is_a_typed_error() {
let (_dir, repo) = init_repo();
let other = tempfile::tempdir().expect("other tempdir");
let outside = other.path().join("elsewhere").display().to_string();
let err = repo
.resolve_worktree_pathspec(&outside, None)
.expect_err("outside");
assert!(
matches!(&err, PathspecResolveError::OutsideRepository { elt, .. } if *elt == outside),
"{err:?}"
);
let inside = repo.work_tree().expect("work tree").join("in.txt");
assert_eq!(
repo.resolve_worktree_pathspec(&inside.display().to_string(), None)
.expect("absolute inside"),
"in.txt"
);
}
#[test]
fn bare_repository_needs_work_tree() {
let dir = tempfile::tempdir().expect("tempdir");
let repo =
Repository::init(dir.path(), &InitOptions::default().with_bare(true)).expect("init bare");
assert_eq!(
repo.resolve_worktree_pathspec("a", None),
Err(PathspecResolveError::NeedsWorkTree)
);
}
#[test]
fn resolved_pathspecs_match_per_path_resolution() {
let (dir, repo) = init_repo();
let raw: Vec<String> = (0..20).map(|i| format!("d{i}/f.txt")).collect();
let resolved =
ResolvedPathspecs::resolve(&repo, &dir.path().join("sub"), &raw, None).expect("resolve");
let one_by_one: Vec<String> = raw
.iter()
.map(|p| repo.resolve_worktree_pathspec(p, Some("sub")).expect("one"))
.collect();
assert_eq!(resolved.specs, one_by_one);
assert_eq!(resolved.sources, raw);
}
#[cfg(unix)]
#[test]
fn many_pathspecs_do_not_reprobe_git_dir_per_path() {
let (dir, _repo) = init_repo();
grit_test_support::git(dir.path(), &["config", "core.precomposeunicode", "true"]);
let repo = Repository::discover(Some(dir.path()), &Default::default()).expect("reopen");
repo.resolve_worktree_pathspec("warm", None).expect("warm");
let git_dir = repo.git_dir().to_path_buf();
let old = std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000_000);
fs::File::open(&git_dir)
.expect("open .git")
.set_modified(old)
.expect("set .git mtime");
let raw: Vec<String> = (0..100).map(|i| format!("dir{i}/file.txt")).collect();
let resolved = ResolvedPathspecs::resolve(&repo, dir.path(), &raw, None).expect("resolve");
assert_eq!(resolved.specs, raw);
for p in &raw {
repo.resolve_worktree_pathspec(p, None)
.expect("resolve one");
}
let after = fs::metadata(&git_dir)
.expect("stat .git")
.modified()
.expect("mtime");
assert_eq!(
after, old,
"resolving pathspecs must not create files in .git per path"
);
}