mod common;
use common::TempRepo;
use gitwell::git::Repo;
use gitwell::scanner::branches::BranchScanner;
use gitwell::scanner::{Finding, Scanner};
const SIXTY_DAYS: u64 = 60 * 86_400;
fn very_old_ts() -> i64 {
1_577_836_800
}
fn scanner(stale_secs: u64) -> BranchScanner {
BranchScanner {
stale_secs,
ignore_branches: Vec::new(),
}
}
fn scanner_with_ignores(stale_secs: u64, ignores: Vec<String>) -> BranchScanner {
BranchScanner {
stale_secs,
ignore_branches: ignores,
}
}
fn find_branch<'a>(findings: &'a [Finding], name: &str) -> Option<&'a Finding> {
findings.iter().find(|f| match f {
Finding::StaleBranch { name: n, .. } => n == name,
_ => false,
})
}
#[test]
fn stale_unmerged_branch_is_found() {
let tr = TempRepo::new("branches-stale");
tr.branch("feature/old-work");
tr.commit_file_at(
"feat.txt",
"unfinished\n",
"WIP old feature",
very_old_ts(),
);
tr.checkout("main");
let repo = Repo::open(tr.path()).expect("open repo");
let findings = scanner(SIXTY_DAYS).scan(&repo);
let branch = find_branch(&findings, "feature/old-work");
assert!(
branch.is_some(),
"expected feature/old-work to be reported as stale; got {:?}",
findings
);
if let Some(Finding::StaleBranch {
last_commit_message,
ahead,
behind,
..
}) = branch
{
assert_eq!(last_commit_message, "WIP old feature");
assert!(
*ahead >= 1,
"branch should be ahead of main (got {})",
ahead
);
assert_eq!(*behind, 0, "main hasn't moved, branch shouldn't be behind");
}
}
#[test]
fn branch_merged_into_default_is_not_found() {
let tr = TempRepo::new("branches-merged");
tr.branch("feature/done");
tr.commit_file_at(
"done.txt",
"finished\n",
"Feature complete",
very_old_ts(),
);
tr.checkout("main");
tr.merge("feature/done");
let repo = Repo::open(tr.path()).expect("open repo");
let findings = scanner(SIXTY_DAYS).scan(&repo);
assert!(
find_branch(&findings, "feature/done").is_none(),
"merged branch should not be reported; got {:?}",
findings
);
}
#[test]
fn recent_branch_is_not_stale() {
let tr = TempRepo::new("branches-recent");
tr.branch("feature/fresh");
tr.commit_file("fresh.txt", "just today\n", "Fresh work");
tr.checkout("main");
let repo = Repo::open(tr.path()).expect("open repo");
let findings = scanner(30 * 86_400).scan(&repo);
assert!(
find_branch(&findings, "feature/fresh").is_none(),
"recent branch should not be stale; got {:?}",
findings
);
}
#[test]
fn ignore_branches_glob_excludes_matches() {
let tr = TempRepo::new("branches-ignore");
tr.branch("dependabot/npm/lodash");
tr.commit_file_at(
"dep.txt",
"bump\n",
"chore(deps): bump lodash",
very_old_ts(),
);
tr.checkout("main");
tr.branch("feature/keeper");
tr.commit_file_at(
"keep.txt",
"real work\n",
"WIP real work",
very_old_ts(),
);
tr.checkout("main");
let repo = Repo::open(tr.path()).expect("open repo");
let ignores = vec!["dependabot/*".to_string()];
let findings = scanner_with_ignores(SIXTY_DAYS, ignores).scan(&repo);
assert!(
find_branch(&findings, "dependabot/npm/lodash").is_none(),
"dependabot/* should be filtered; got {:?}",
findings
);
assert!(
find_branch(&findings, "feature/keeper").is_some(),
"unmatched stale branch should still be reported; got {:?}",
findings
);
}
#[test]
fn default_branch_itself_is_never_reported() {
let tr = TempRepo::new("branches-default-excluded");
tr.empty_commit_at("old main commit", very_old_ts());
let repo = Repo::open(tr.path()).expect("open repo");
let findings = scanner(SIXTY_DAYS).scan(&repo);
assert!(
find_branch(&findings, "main").is_none(),
"the default branch must never be reported as stale"
);
}