mod support;
use grit_lib::config::environment::{Environment, RepositoryOptions};
use grit_lib::repo::Repository;
use std::fs;
use grit_lib::revwalk::rev_parse::{
resolve_revision, resolve_revision_as_commit, resolve_revision_for_checkout_guess,
resolve_revision_for_range_end, resolve_revision_for_verify,
resolve_revision_without_index_dwim,
};
use support::{build_commit_dag, commit_file, git, git_rev_parse_verify, HistoryRepo};
fn open_grit(wt: &std::path::Path) -> Repository {
let mut env = Environment::empty();
env.cwd = wt.to_path_buf();
env.pwd = Some(wt.to_string_lossy().into_owned());
let options = RepositoryOptions::with_environment(env);
Repository::discover(Some(wt), &options).expect("discover")
}
#[test]
fn exercise_many_specs_on_deep_history() {
let hist = HistoryRepo::init();
let wt = hist.worktree();
let mut edge_labels: Vec<(String, String)> = Vec::new();
for i in 1..=100 {
let child = format!("c{i}");
let parent = if i == 1 {
"root".to_string()
} else {
format!("c{}", i - 1)
};
edge_labels.push((child, parent));
}
let edges: Vec<(&str, &str)> = edge_labels
.iter()
.map(|(c, p)| (c.as_str(), p.as_str()))
.collect();
build_commit_dag(wt, &edges, &[("c100", "refs/heads/main")]);
let repo = open_grit(wt);
for n in 0..100 {
let spec = format!("HEAD~{n}");
let _ = resolve_revision_for_verify(&repo, &spec);
let _ = resolve_revision_without_index_dwim(&repo, &spec);
let _ = resolve_revision_for_range_end(&repo, &spec);
let _ = resolve_revision_as_commit(&repo, &spec);
let _ = resolve_revision_for_checkout_guess(&repo, &spec, true);
let _ = resolve_revision_for_checkout_guess(&repo, &spec, false);
}
for n in 1..=3 {
let spec = format!("main^^{n}");
let _ = resolve_revision_for_verify(&repo, &spec);
}
for spec in [
"main^!",
"main^@",
"HEAD^{commit}",
"HEAD^{tree}",
"HEAD^{/history harness c100}",
":/harness",
"main@{0}",
"main@{1}",
"main@{upstream}",
"main@{u}",
"main@{push}",
"@",
"@{0}",
] {
let _ = resolve_revision_for_verify(&repo, spec);
let _ = resolve_revision(&repo, spec);
}
}
#[test]
fn head_tilde_matches_git_when_commit_graph_is_stale() {
let hist = HistoryRepo::init();
let wt = hist.worktree();
for i in 0..15 {
git(wt, &["commit", "--allow-empty", "-m", &format!("c{i}")]);
}
git(wt, &["commit-graph", "write", "--reachable"]);
let tip_at_graph_write = git_rev_parse_verify(wt, "HEAD").expect("tip at graph write");
for i in 15..25 {
git(wt, &["commit", "--allow-empty", "-m", &format!("c{i}")]);
}
let repo = open_grit(wt);
let spec = "HEAD~20";
let grit_oid = resolve_revision(&repo, spec).expect("stale graph");
let git_oid = git_rev_parse_verify(wt, spec).expect("git");
assert_eq!(grit_oid, git_oid);
assert_ne!(
grit_oid, tip_at_graph_write,
"fixture must resolve through commits added after the graph was written"
);
let info = wt.join(".git/objects/info");
assert!(
info.join("commit-graph").is_file() || info.join("commit-graphs").is_dir(),
"commit-graph must still exist on disk (stale, not absent)"
);
}
#[test]
fn head_tilde_matches_git_when_commit_graph_is_missing() {
let hist = HistoryRepo::init();
let wt = hist.worktree();
for i in 0..25 {
git(wt, &["commit", "--allow-empty", "-m", &format!("c{i}")]);
}
let repo = open_grit(wt);
let spec = "HEAD~20";
let grit_oid = resolve_revision(&repo, spec).expect("no graph");
assert_eq!(grit_oid, git_rev_parse_verify(wt, spec).expect("git"));
let graph_path = wt.join(".git/objects/info/commit-graph");
let _ = fs::remove_file(&graph_path);
let _ = fs::remove_dir_all(wt.join(".git/objects/info/commit-graphs"));
assert_eq!(
resolve_revision(&repo, spec).expect("after delete"),
grit_oid
);
}
#[test]
fn exercise_failure_specs_do_not_panic() {
let hist = HistoryRepo::init();
let wt = hist.worktree();
commit_file(wt, "f", "1\n", "one");
let repo = open_grit(wt);
for spec in [
"not-a-ref-xyz",
"HEAD~999",
"deadbeef",
"main^{tag}",
"main:missing/path",
":missing",
"HEAD^9",
"main..",
"...main",
"@{-99}",
"main@{upstream}@{upstream}",
] {
let _ = resolve_revision_for_verify(&repo, spec);
let _ = resolve_revision(&repo, spec);
}
}