use std::path::Path;
use std::process::{Command, Output};
use regex::Regex;
use tempfile::TempDir;
fn strip_ansi(s: &str) -> String {
let re = Regex::new(r"\x1b\[[0-9;]*m").unwrap();
re.replace_all(s, "").to_string()
}
fn create_origin_repo() -> TempDir {
let dir = TempDir::new().expect("Failed to create temp dir");
run_git(dir.path(), &["init", "--bare", "--initial-branch=main"]);
dir
}
fn create_local_repo(origin_path: &Path) -> TempDir {
let dir = TempDir::new().expect("Failed to create temp dir");
run_git(dir.path(), &["init"]);
run_git(dir.path(), &["config", "user.email", "test@example.com"]);
run_git(dir.path(), &["config", "user.name", "Test User"]);
run_git(dir.path(), &["checkout", "-b", "main"]);
std::fs::write(dir.path().join("README.md"), "# Test").expect("Failed to write file");
run_git(dir.path(), &["add", "."]);
run_git(dir.path(), &["commit", "-m", "Initial commit"]);
let origin_url = format!("file://{}", origin_path.display());
run_git(dir.path(), &["remote", "add", "origin", &origin_url]);
run_git(dir.path(), &["push", "-u", "origin", "main"]);
dir
}
fn run_git(dir: &Path, args: &[&str]) -> String {
let output = Command::new("git")
.args(args)
.current_dir(dir)
.output()
.expect("Failed to run git command");
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
panic!("git {} failed: {}", args.join(" "), stderr);
}
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
fn run_gw(dir: &Path, args: &[&str]) -> Output {
let gw_path = env!("CARGO_BIN_EXE_gw");
Command::new(gw_path)
.args(args)
.current_dir(dir)
.env("NO_COLOR", "1")
.output()
.expect("Failed to run gw command")
}
fn current_branch(dir: &Path) -> String {
run_git(dir, &["rev-parse", "--abbrev-ref", "HEAD"])
}
fn branch_exists(dir: &Path, branch: &str) -> bool {
Command::new("git")
.args([
"show-ref",
"--verify",
"--quiet",
&format!("refs/heads/{}", branch),
])
.current_dir(dir)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn create_feature_branch(dir: &Path, name: &str) {
run_git(dir, &["checkout", "-b", name, "main"]);
let filename = format!("{}.txt", name.replace('/', "-"));
std::fs::write(dir.join(&filename), format!("work on {}", name)).expect("Failed to write file");
run_git(dir, &["add", "."]);
run_git(dir, &["commit", "-m", &format!("feat: work on {}", name)]);
}
#[test]
fn test_cleanup_current_branch_switches_to_home() {
let origin = create_origin_repo();
let local = create_local_repo(origin.path());
create_feature_branch(local.path(), "feature-a");
assert_eq!(current_branch(local.path()), "feature-a");
let output = run_gw(local.path(), &["cleanup"]);
assert!(
output.status.success(),
"gw cleanup failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = strip_ansi(&String::from_utf8_lossy(&output.stdout));
assert_eq!(current_branch(local.path()), "main");
assert!(!branch_exists(local.path(), "feature-a"));
assert!(
stdout.contains("Switched to"),
"Expected switch message, got: {}",
stdout
);
assert!(
stdout.contains("Cleanup complete"),
"Expected cleanup complete, got: {}",
stdout
);
}
#[test]
fn test_cleanup_named_branch_when_on_that_branch_switches_to_home() {
let origin = create_origin_repo();
let local = create_local_repo(origin.path());
create_feature_branch(local.path(), "feature-a");
assert_eq!(current_branch(local.path()), "feature-a");
let output = run_gw(local.path(), &["cleanup", "feature-a"]);
assert!(
output.status.success(),
"gw cleanup feature-a failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = strip_ansi(&String::from_utf8_lossy(&output.stdout));
assert_eq!(current_branch(local.path()), "main");
assert!(!branch_exists(local.path(), "feature-a"));
assert!(
stdout.contains("Switched to"),
"Expected switch message, got: {}",
stdout
);
}
#[test]
fn test_cleanup_different_branch_does_not_switch() {
let origin = create_origin_repo();
let local = create_local_repo(origin.path());
create_feature_branch(local.path(), "feature-a");
run_git(local.path(), &["checkout", "main"]);
create_feature_branch(local.path(), "feature-b");
assert_eq!(current_branch(local.path()), "feature-b");
let output = run_gw(local.path(), &["cleanup", "feature-a"]);
assert!(
output.status.success(),
"gw cleanup feature-a failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = strip_ansi(&String::from_utf8_lossy(&output.stdout));
assert_eq!(current_branch(local.path()), "feature-b");
assert!(!branch_exists(local.path(), "feature-a"));
assert!(branch_exists(local.path(), "feature-b"));
assert!(
!stdout.contains("Switched to"),
"Should not switch branches, but got: {}",
stdout
);
assert!(
stdout.contains("stayed on"),
"Expected 'stayed on' message, got: {}",
stdout
);
}
#[test]
fn test_cleanup_different_branch_with_uncommitted_changes_succeeds() {
let origin = create_origin_repo();
let local = create_local_repo(origin.path());
create_feature_branch(local.path(), "feature-a");
run_git(local.path(), &["checkout", "main"]);
create_feature_branch(local.path(), "feature-b");
std::fs::write(local.path().join("dirty.txt"), "uncommitted work")
.expect("Failed to write file");
assert_eq!(current_branch(local.path()), "feature-b");
let output = run_gw(local.path(), &["cleanup", "feature-a"]);
assert!(
output.status.success(),
"gw cleanup feature-a should succeed with dirty working dir on different branch: {}",
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(current_branch(local.path()), "feature-b");
assert!(!branch_exists(local.path(), "feature-a"));
assert!(local.path().join("dirty.txt").exists());
}
#[test]
fn test_cleanup_current_branch_with_uncommitted_changes_fails() {
let origin = create_origin_repo();
let local = create_local_repo(origin.path());
create_feature_branch(local.path(), "feature-a");
std::fs::write(local.path().join("feature-a.txt"), "modified content")
.expect("Failed to write file");
assert_eq!(current_branch(local.path()), "feature-a");
let output = run_gw(local.path(), &["cleanup"]);
assert!(
!output.status.success(),
"gw cleanup should fail with uncommitted changes on current branch"
);
let stderr = strip_ansi(&String::from_utf8_lossy(&output.stderr));
assert_eq!(current_branch(local.path()), "feature-a");
assert!(branch_exists(local.path(), "feature-a"));
assert!(
stderr.contains("uncommitted changes"),
"Expected uncommitted changes error, got: {}",
stderr
);
}