use std::process::Command;
use loopflow::ops::{
list_branch_candidates, prune_branches, BranchFilterOptions, BranchListOptions,
BranchPruneOptions, NullProgress,
};
use loopflow_test_support::TestRepo;
fn git(repo: &std::path::Path, args: &[&str]) {
let output = Command::new("git")
.args(args)
.current_dir(repo)
.output()
.expect("git command should run");
assert!(
output.status.success(),
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&output.stderr)
);
}
fn git_stdout(repo: &std::path::Path, args: &[&str]) -> String {
let output = Command::new("git")
.args(args)
.current_dir(repo)
.output()
.expect("git command should run");
assert!(
output.status.success(),
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
fn create_remote_branch(repo: &TestRepo, branch: &str) {
repo.create_branch(branch);
repo.create_file(&format!("{branch}.txt"), branch);
repo.stage_all();
repo.commit(&format!("add {branch}"));
repo.push_new_branch(branch);
repo.checkout("main");
}
#[test]
fn list_branch_candidates_defaults_to_current_git_user() {
let repo = TestRepo::new();
create_remote_branch(&repo, "jack.feature.20260401_1200");
let candidates = list_branch_candidates(
repo.path(),
&BranchListOptions {
filters: BranchFilterOptions::default(),
default_user_if_empty: true,
},
)
.expect("list branches");
assert!(candidates
.iter()
.any(|candidate| candidate.branch == "jack.feature.20260401_1200"));
assert!(!candidates
.iter()
.any(|candidate| candidate.branch == "main"));
}
#[test]
fn list_branch_candidates_filters_by_wave_segment() {
let repo = TestRepo::new();
create_remote_branch(&repo, "jack.redesign.20260401_1200");
create_remote_branch(&repo, "jack.other.20260401_1200");
let candidates = list_branch_candidates(
repo.path(),
&BranchListOptions {
filters: BranchFilterOptions {
user: Some("@me".to_string()),
wave: Some("redesign".to_string()),
..BranchFilterOptions::default()
},
default_user_if_empty: false,
},
)
.expect("list branches");
let names = candidates
.iter()
.map(|candidate| candidate.branch.as_str())
.collect::<Vec<_>>();
assert_eq!(names, vec!["jack.redesign.20260401_1200"]);
}
#[test]
fn prune_branches_dry_run_leaves_remote_branch() {
let repo = TestRepo::new();
create_remote_branch(&repo, "jack.cleanup.20260401_1200");
let candidates = prune_branches(
repo.path(),
&BranchPruneOptions {
filters: BranchFilterOptions {
user: Some("@me".to_string()),
wave: Some("cleanup".to_string()),
..BranchFilterOptions::default()
},
dry_run: true,
yes: true,
},
&NullProgress,
)
.expect("prune branches");
assert_eq!(candidates.len(), 1);
git(
repo.path(),
&[
"show-ref",
"--verify",
"refs/remotes/origin/jack.cleanup.20260401_1200",
],
);
}
#[test]
fn prune_branches_deletes_remote_branch_only() {
let repo = TestRepo::new();
create_remote_branch(&repo, "jack.stale.20260401_1200");
let candidates = prune_branches(
repo.path(),
&BranchPruneOptions {
filters: BranchFilterOptions {
user: Some("@me".to_string()),
wave: Some("stale".to_string()),
..BranchFilterOptions::default()
},
dry_run: false,
yes: true,
},
&NullProgress,
)
.expect("prune branches");
assert_eq!(candidates.len(), 1);
let remote_refs = git_stdout(repo.path(), &["ls-remote", "--heads", "origin"]);
assert!(!remote_refs.contains("jack.stale.20260401_1200"));
git(
repo.path(),
&[
"show-ref",
"--verify",
"refs/heads/jack.stale.20260401_1200",
],
);
}
#[test]
fn prune_branches_requires_filter() {
let repo = TestRepo::new();
let err = prune_branches(
repo.path(),
&BranchPruneOptions {
filters: BranchFilterOptions::default(),
dry_run: true,
yes: true,
},
&NullProgress,
)
.expect_err("filter required");
assert!(err.to_string().contains("without at least one filter"));
}