use crate::core::repo::gather_repo_info;
use crate::core::shortid::IdAllocator;
use crate::core::test_helpers::TestRepo;
use super::{hide_branches, resolve_commit_filter, resolve_context};
#[test]
fn hidden_branch_removed_from_branches() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit_empty("C1");
let tip = test_repo.head_oid();
test_repo.create_branch_at_commit("local-secrets", tip);
let mut info = gather_repo_info(&test_repo.repo, false, 1).unwrap();
assert_eq!(info.branches.len(), 1);
assert_eq!(info.branches[0].name, "local-secrets");
hide_branches(&mut info, "local-");
assert!(info.branches.is_empty());
}
#[test]
fn hidden_branch_commits_not_shown_as_loose() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit_empty("C1");
let tip = test_repo.head_oid();
test_repo.create_branch_at_commit("local-secrets", tip);
let mut info = gather_repo_info(&test_repo.repo, false, 1).unwrap();
assert_eq!(info.commits.len(), 1);
hide_branches(&mut info, "local-");
assert!(info.commits.is_empty());
}
#[test]
fn non_hidden_branch_unaffected() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit_empty("C1");
let tip = test_repo.head_oid();
test_repo.create_branch_at_commit("feature-a", tip);
let mut info = gather_repo_info(&test_repo.repo, false, 1).unwrap();
hide_branches(&mut info, "local-");
assert_eq!(info.branches.len(), 1);
assert_eq!(info.branches[0].name, "feature-a");
assert_eq!(info.commits.len(), 1);
}
#[test]
fn hidden_branch_stacked_on_visible_branch() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit_empty("A1");
let a1 = test_repo.head_oid();
test_repo.create_branch_at_commit("feature-a", a1);
test_repo.commit_empty("P1");
let p1 = test_repo.head_oid();
test_repo.create_branch_at_commit("local-private", p1);
let mut info = gather_repo_info(&test_repo.repo, false, 1).unwrap();
assert_eq!(info.branches.len(), 2);
assert_eq!(info.commits.len(), 2);
hide_branches(&mut info, "local-");
assert_eq!(info.branches.len(), 1);
assert_eq!(info.branches[0].name, "feature-a");
assert_eq!(info.commits.len(), 1);
assert_eq!(info.commits[0].message, "A1");
}
#[test]
fn hidden_colocated_with_visible_branch_preserves_commit() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit_empty("A1");
let tip = test_repo.head_oid();
test_repo.create_branch_at_commit("feature-a", tip);
test_repo.create_branch_at_commit("local-backup", tip);
let mut info = gather_repo_info(&test_repo.repo, false, 1).unwrap();
assert_eq!(info.branches.len(), 2);
hide_branches(&mut info, "local-");
assert_eq!(info.branches.len(), 1);
assert_eq!(info.branches[0].name, "feature-a");
assert_eq!(info.commits.len(), 1);
assert_eq!(info.commits[0].message, "A1");
}
#[test]
fn multiple_hidden_branches() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit_empty("S1");
let s1 = test_repo.head_oid();
test_repo.create_branch_at_commit("local-secrets", s1);
test_repo.commit_empty("S2");
let s2 = test_repo.head_oid();
test_repo.create_branch_at_commit("local-config", s2);
let mut info = gather_repo_info(&test_repo.repo, false, 1).unwrap();
assert_eq!(info.branches.len(), 2);
hide_branches(&mut info, "local-");
assert!(info.branches.is_empty());
assert!(info.commits.is_empty());
}
#[test]
fn top_commit_returns_tip_loose_commit() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit_empty("C1");
test_repo.commit_empty("C2");
let c2 = test_repo.head_oid();
let top = super::top_commit(&test_repo.repo).unwrap();
assert_eq!(top, Some(c2));
}
#[test]
fn top_commit_skips_merge_of_hidden_branch() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit_empty("Base");
let base = test_repo.head_oid();
test_repo.create_branch_at_commit("local-only", base);
test_repo.switch_branch("local-only");
test_repo.commit_empty("L1");
test_repo.switch_branch("integration");
test_repo.commit_empty("C1");
let c1 = test_repo.head_oid();
test_repo.merge_no_ff("local-only");
assert!(test_repo.head_commit().parent_count() > 1, "tip is a merge");
let top = super::top_commit(&test_repo.repo).unwrap();
assert_eq!(top, Some(c1), "should skip the merge and pick C1");
}
#[test]
fn filter_by_full_git_hash_shows_only_that_commit() {
let test_repo = TestRepo::new_with_remote();
let c1_oid = test_repo.commit("C1", "file1.txt");
test_repo.commit("C2", "file2.txt");
let info = gather_repo_info(&test_repo.repo, true, 1).unwrap();
assert_eq!(info.commits.len(), 2);
assert!(info.commits.iter().all(|c| !c.files.is_empty()));
let allocator = IdAllocator::new(info.collect_entities());
let filter = resolve_commit_filter(&test_repo.repo, &[c1_oid.to_string()], &info, &allocator);
assert!(filter.contains(&c1_oid));
assert_eq!(filter.len(), 1);
}
#[test]
fn filter_by_loom_short_id_shows_only_that_commit() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit("C1", "file1.txt");
let c2_oid = test_repo.commit("C2", "file2.txt");
let info = gather_repo_info(&test_repo.repo, true, 1).unwrap();
let allocator = IdAllocator::new(info.collect_entities());
let c2_sid = allocator.get_commit(c2_oid).to_string();
let filter = resolve_commit_filter(&test_repo.repo, &[c2_sid], &info, &allocator);
assert!(filter.contains(&c2_oid));
assert_eq!(filter.len(), 1);
}
#[test]
fn filter_unknown_id_silently_skipped() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit("C1", "file1.txt");
let info = gather_repo_info(&test_repo.repo, true, 1).unwrap();
let allocator = IdAllocator::new(info.collect_entities());
let filter = resolve_commit_filter(
&test_repo.repo,
&["nonexistent_id".to_string()],
&info,
&allocator,
);
assert!(filter.is_empty());
}
fn reopen(test_repo: &TestRepo) -> git2::Repository {
git2::Repository::open(test_repo.workdir()).unwrap()
}
#[test]
fn context_depth_falls_back_from_argument_to_config() {
let test_repo = TestRepo::new_with_remote();
assert_eq!(resolve_context(&test_repo.repo, None), 1);
assert_eq!(resolve_context(&test_repo.repo, Some(3)), 3);
test_repo.set_config("loom.statusContext", "5");
assert_eq!(resolve_context(&reopen(&test_repo), None), 5);
assert_eq!(resolve_context(&reopen(&test_repo), Some(2)), 2);
}
#[test]
fn unusable_context_config_keeps_the_default() {
let test_repo = TestRepo::new_with_remote();
for value in ["0", "-2", "many"] {
test_repo.set_config("loom.statusContext", value);
assert_eq!(resolve_context(&reopen(&test_repo), None), 1, "{value}");
}
}
#[test]
fn commit_id_survives_an_update_rebase() {
let test_repo = TestRepo::new_with_remote();
test_repo.write_file("a.txt", "a");
test_repo
.in_dir(|| {
crate::commit::run(
Some("feature-a".to_string()),
false,
Some("Add a".to_string()),
None,
vec!["a.txt".to_string()],
vec![],
&crate::core::graph::Theme::dark(),
)
})
.unwrap();
let tip_before = test_repo.get_branch_target("feature-a");
let id_before = {
let info = gather_repo_info(&test_repo.repo, false, 1).unwrap();
IdAllocator::new(info.collect_entities())
.get_commit(tip_before)
.to_string()
};
assert_eq!(id_before.len(), 3, "{id_before}");
assert!(id_before.chars().all(|c| ('k'..='z').contains(&c)));
test_repo.add_remote_commits(&["Upstream change"]);
test_repo.in_dir(|| crate::update::run(true)).unwrap();
let tip_after = test_repo.get_branch_target("feature-a");
assert_ne!(tip_after, tip_before, "the rebase rewrote the commit");
let info = gather_repo_info(&reopen(&test_repo), false, 1).unwrap();
let ids = IdAllocator::new(info.collect_entities());
assert_eq!(ids.get_commit(tip_after), id_before);
}
#[test]
fn filter_by_persistent_prefix_skips_ambiguous_ones() {
let test_repo = TestRepo::new_with_remote();
let a = test_repo.commit(
"A\n\nChange-Id: I3ac7000000000000000000000000000000000000\n",
"a.txt",
);
test_repo.commit(
"B\n\nChange-Id: I3ac8000000000000000000000000000000000000\n",
"b.txt",
);
let info = gather_repo_info(&test_repo.repo, true, 1).unwrap();
let allocator = IdAllocator::new(info.collect_entities());
let filter =
|arg: &str| resolve_commit_filter(&test_repo.repo, &[arg.to_string()], &info, &allocator);
assert_eq!(filter("wpnsz").into_iter().collect::<Vec<_>>(), vec![a]);
assert_eq!(
filter("I3ac7000000000000000000000000000000000000")
.into_iter()
.collect::<Vec<_>>(),
vec![a]
);
assert!(filter("wpn").is_empty(), "ambiguous prefix is skipped");
}