use super::test_support::*;
use super::*;
use std::{fs, path::Path};
use tempfile::TempDir;
#[test]
fn test_ahead_count_when_no_upstream_with_remote_ref() {
let (tmp, repo) = init_temp_repo();
let a = repo.head().unwrap().target().unwrap();
repo.reference("refs/remotes/origin/main", a, true, "test setup")
.unwrap();
add_commit_on_head(&repo, "b.txt", "b");
add_commit_on_head(&repo, "c.txt", "c");
let status = query_status(tmp.path(), &SubmoduleConfig::default()).unwrap();
assert_eq!(status.ahead, 2);
assert_eq!(status.behind, 0);
}
#[test]
fn test_no_remotes_keeps_zero_ahead() {
let (tmp, repo) = init_temp_repo();
add_commit_on_head(&repo, "b.txt", "b");
add_commit_on_head(&repo, "c.txt", "c");
let status = query_status(tmp.path(), &SubmoduleConfig::default()).unwrap();
assert_eq!(status.ahead, 0);
assert_eq!(status.behind, 0);
}
#[test]
fn test_stash_count_zero_when_no_stash() {
let (tmp, _repo) = init_temp_repo();
let status = query_status(tmp.path(), &SubmoduleConfig::default()).unwrap();
assert!(status.stashes.is_empty());
assert_eq!(status.stash_count(), 0);
}
#[test]
fn test_stash_count_reflects_stash_save() {
let (tmp, mut repo) = init_temp_repo();
add_commit_on_head(&repo, "tracked.txt", "v1");
fs::write(tmp.path().join("tracked.txt"), "v2").unwrap();
let sig = git2::Signature::now("Test", "test@test.com").unwrap();
repo.stash_save2(&sig, None, None).unwrap();
let status = query_status(tmp.path(), &SubmoduleConfig::default()).unwrap();
assert_eq!(status.stash_count(), 1);
assert!(!status.stashes[0].oid.is_empty());
fs::write(tmp.path().join("tracked.txt"), "v3").unwrap();
repo.stash_save2(&sig, None, None).unwrap();
let status = query_status(tmp.path(), &SubmoduleConfig::default()).unwrap();
assert_eq!(status.stash_count(), 2);
assert!(status.stashes.iter().any(|s| s.index == 0));
assert!(status.stashes.iter().any(|s| s.index == 1));
}
#[test]
fn test_ahead_count_when_head_shares_only_root_with_unrelated_remote() {
let (tmp, repo) = init_temp_repo();
let a = repo.head().unwrap().target().unwrap();
let a_commit = repo.find_commit(a).unwrap();
let sig = git2::Signature::now("Test", "test@test.com").unwrap();
let workdir = repo.workdir().unwrap().to_path_buf();
fs::write(workdir.join("d.txt"), "d").unwrap();
let mut index = repo.index().unwrap();
index.add_path(Path::new("d.txt")).unwrap();
let d_tree_id = index.write_tree().unwrap();
let d_tree = repo.find_tree(d_tree_id).unwrap();
let d = repo
.commit(None, &sig, &sig, "D", &d_tree, &[&a_commit])
.unwrap();
index.remove_path(Path::new("d.txt")).unwrap();
index.write().unwrap();
fs::remove_file(workdir.join("d.txt")).unwrap();
repo.reference("refs/remotes/origin/other", d, true, "test setup")
.unwrap();
add_commit_on_head(&repo, "b.txt", "b");
add_commit_on_head(&repo, "c.txt", "c");
let status = query_status(tmp.path(), &SubmoduleConfig::default()).unwrap();
assert_eq!(status.ahead, 2);
assert_eq!(status.behind, 0);
}
#[test]
fn fingerprint_changes_when_other_branch_moves() {
let (_tmp, repo) = init_temp_repo();
let base = repo.head().unwrap().peel_to_commit().unwrap();
repo.branch("feature", &base, false).unwrap();
let before = graph_refs_fingerprint(&repo);
let head_before = repo.head().unwrap().target();
let sig = git2::Signature::now("Test", "test@test.com").unwrap();
let tree = repo.find_tree(base.tree_id()).unwrap();
let moved = repo
.commit(None, &sig, &sig, "on feature", &tree, &[&base])
.unwrap();
repo.reference("refs/heads/feature", moved, true, "move feature")
.unwrap();
let after = graph_refs_fingerprint(&repo);
assert_ne!(
before.local, after.local,
"a moved local branch must change the local refs fingerprint"
);
assert_eq!(
repo.head().unwrap().target(),
head_before,
"the checked-out HEAD must be untouched by the other-branch commit"
);
}
#[test]
fn fingerprint_changes_when_branch_added_at_existing_commit() {
let (_tmp, repo) = init_temp_repo();
let base = repo.head().unwrap().peel_to_commit().unwrap();
let before = graph_refs_fingerprint(&repo);
repo.branch("feature", &base, false).unwrap();
let after = graph_refs_fingerprint(&repo);
assert_ne!(before.local, after.local);
}
#[test]
fn fingerprint_buckets_remote_separately_from_local() {
let (_tmp, repo) = init_temp_repo();
let base = repo.head().unwrap().peel_to_commit().unwrap();
let before = graph_refs_fingerprint(&repo);
repo.reference("refs/remotes/origin/feature", base.id(), true, "remote tip")
.unwrap();
let after = graph_refs_fingerprint(&repo);
assert_eq!(
before.local, after.local,
"remote move must not touch local"
);
assert_ne!(before.remote, after.remote, "remote bucket must change");
}
#[test]
fn query_status_detects_commit_in_linked_worktree() {
let tmp = TempDir::new().unwrap();
let root = tmp.path().join("root");
let wt = tmp.path().join("wt-feature");
fs::create_dir_all(&root).unwrap();
let git = |args: &[&str], cwd: &Path| -> bool {
std::process::Command::new("git")
.args(args)
.current_dir(cwd)
.env("GIT_AUTHOR_NAME", "Test")
.env("GIT_AUTHOR_EMAIL", "t@t")
.env("GIT_COMMITTER_NAME", "Test")
.env("GIT_COMMITTER_EMAIL", "t@t")
.status()
.map(|s| s.success())
.unwrap_or(false)
};
if !git(&["init", "-q"], &root) {
eprintln!("skipping query_status_detects_commit_in_linked_worktree: git unavailable");
return;
}
assert!(git(&["commit", "-q", "--allow-empty", "-m", "init"], &root));
assert!(git(
&[
"worktree",
"add",
"-q",
wt.to_str().unwrap(),
"-b",
"feature"
],
&root,
));
let before = query_status(&root, &SubmoduleConfig::default()).unwrap();
assert!(git(
&["commit", "-q", "--allow-empty", "-m", "in worktree"],
&wt,
));
let after = query_status(&root, &SubmoduleConfig::default()).unwrap();
assert_eq!(
before.head_oid, after.head_oid,
"root checked-out HEAD must be unchanged by the worktree commit"
);
assert_ne!(
before.refs.local, after.refs.local,
"worktree commit moved a shared branch tip; the local refs \
fingerprint must change so the graph reloads"
);
}