use std::path::{Path, PathBuf};
use std::process::Command;
use basemind::git::Repo;
use basemind::git_history::GitHistoryIndex;
use basemind::git_history::builder;
use basemind::git_history::fts::FtsScope;
fn git_lines(repo: &Path, args: &[&str]) -> Vec<String> {
let out = Command::new("git")
.arg("-C")
.arg(repo)
.args(args)
.output()
.expect("git in PATH");
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout)
.expect("utf8")
.lines()
.map(str::to_string)
.filter(|s| !s.is_empty())
.collect()
}
fn git_one(repo: &Path, args: &[&str]) -> Option<String> {
git_lines(repo, args).into_iter().next()
}
fn target_repo() -> Option<PathBuf> {
let raw = std::env::var("BASEMIND_GIT_PARITY_REPO").ok()?;
let path = PathBuf::from(raw);
assert!(
path.join(".git").exists(),
"BASEMIND_GIT_PARITY_REPO={} is not a git repo (no .git)",
path.display()
);
Some(path)
}
fn build_index(repo: &Path, scratch: &Path) -> (GitHistoryIndex, String) {
let repository = Repo::discover(repo).expect("discover repo");
let index = GitHistoryIndex::open(scratch).expect("open git-history index");
let outcome = builder::sync(&index, &repository, scratch).expect("sync git-history index");
let head = index
.last_indexed_head_hex()
.expect("index records the head it built from");
eprintln!(
"git_parity: built index — {outcome:?}, {} commits, head {}",
index.commit_count(),
&head[..12.min(head.len())]
);
(index, head)
}
#[test]
#[ignore = "requires BASEMIND_GIT_PARITY_REPO to point at a real repo"]
fn author_search_matches_real_git_at_full_depth() {
let Some(repo) = target_repo() else {
eprintln!("git_parity: BASEMIND_GIT_PARITY_REPO unset — skipping");
return;
};
let scratch = tempfile::tempdir().expect("tempdir");
let (index, head) = build_index(&repo, scratch.path());
let deep = git_one(&repo, &["log", &head, "--format=%an", "-1", "--skip=250"])
.or_else(|| git_one(&repo, &["log", &head, "--format=%an", "-1"]))
.expect("at least one commit");
eprintln!("git_parity: author under test = {deep:?}");
let git_shas = git_lines(
&repo,
&[
"log",
&head,
"-i",
&format!("--author={deep}"),
"--format=%H",
],
);
assert!(!git_shas.is_empty(), "git found no commits for {deep:?}");
let git_newest = &git_shas[0];
let git_set: std::collections::HashSet<&String> = git_shas.iter().collect();
let hits = index.search_commits(&deep, FtsScope::Author, 0, 5000);
assert!(
!hits.is_empty(),
"index author search returned nothing for {deep:?} (git found {})",
git_shas.len()
);
assert_eq!(
&hits[0].sha, git_newest,
"index newest author commit must equal `git log -i --author` newest"
);
for h in &hits {
assert!(
git_set.contains(&h.sha),
"indexed author hit {} is not in git's author set for {deep:?}",
h.sha
);
}
eprintln!(
"git_parity: author {deep:?} — git {} commits, index returned {} (newest matches)",
git_shas.len(),
hits.len()
);
}
#[test]
#[ignore = "requires BASEMIND_GIT_PARITY_REPO to point at a real repo"]
fn recent_and_path_history_match_real_git() {
let Some(repo) = target_repo() else {
eprintln!("git_parity: BASEMIND_GIT_PARITY_REPO unset — skipping");
return;
};
let scratch = tempfile::tempdir().expect("tempdir");
let (index, head) = build_index(&repo, scratch.path());
let git_recent = git_lines(&repo, &["log", &head, "--format=%H", "-50"]);
let idx_recent: Vec<String> = index
.recent_commits(0, git_recent.len(), false)
.into_iter()
.map(|c| c.sha)
.collect();
assert_eq!(
idx_recent, git_recent,
"recent_commits must match `git log <head>` newest-first, exactly"
);
if let Some(path) = git_one(&repo, &["show", "--format=", "--name-only", &head]) {
let git_touch = git_lines(
&repo,
&["log", &head, "--full-history", "--format=%H", "--", &path],
);
let idx_touch: Vec<String> = index
.commits_touching(&path.as_bytes().into(), 0, git_touch.len())
.into_iter()
.map(|c| c.sha)
.collect();
assert_eq!(
idx_touch, git_touch,
"commits_touching({path}) must match `git log --full-history -- <path>`"
);
eprintln!(
"git_parity: commits_touching({path}) — {} commits match",
git_touch.len()
);
}
}