mod common;
use common::TestRepo;
use std::path::Path;
use std::process::Command;
use git_worktree_manager::scope::discover_scope;
fn git_walk(path: &Path, args: &[&str]) {
let output = Command::new("git")
.args(args)
.current_dir(path)
.env("GIT_AUTHOR_NAME", "Test")
.env("GIT_AUTHOR_EMAIL", "test@test.com")
.env("GIT_COMMITTER_NAME", "Test")
.env("GIT_COMMITTER_EMAIL", "test@test.com")
.output()
.expect("git failed");
assert!(
output.status.success(),
"git {:?} failed: {}{}",
args,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
fn init_repo_at(path: &Path) {
std::fs::create_dir_all(path).unwrap();
git_walk(path, &["init", "-b", "main"]);
git_walk(path, &["config", "user.name", "Test"]);
git_walk(path, &["config", "user.email", "test@test.com"]);
git_walk(path, &["config", "commit.gpgsign", "false"]);
std::fs::write(path.join("README.md"), "# Test\n").unwrap();
git_walk(path, &["add", "."]);
git_walk(path, &["commit", "-m", "Initial commit"]);
}
fn add_worktree_at(repo_path: &Path, branch: &str, dest_path: &Path) {
git_walk(
repo_path,
&["worktree", "add", dest_path.to_str().unwrap(), "-b", branch],
);
}
#[test]
fn scope_inside_worktree_returns_family() {
let repo = TestRepo::new();
let _wt_x = repo.create_worktree("feat-x");
let _wt_y = repo.create_worktree("feat-y");
let wt_x_path = repo.path().parent().unwrap().join(format!(
"{}-feat-x",
repo.path().file_name().unwrap().to_str().unwrap()
));
let scope = discover_scope(&wt_x_path).expect("scope discovery");
let names: Vec<&str> = scope.worktrees().iter().map(|w| w.name.as_str()).collect();
assert_eq!(
scope.worktrees().len(),
3,
"main + feat-x + feat-y; got {names:?}"
);
let suffixes: Vec<bool> = scope
.worktrees()
.iter()
.map(|w| w.name.ends_with("-feat-x") || w.name.ends_with("-feat-y") || w.is_main)
.collect();
assert!(
suffixes.iter().all(|b| *b),
"every member should be main or feat-{{x,y}}: {names:?}"
);
let main_count = scope.worktrees().iter().filter(|w| w.is_main).count();
assert_eq!(main_count, 1, "exactly one main");
}
#[test]
fn scope_outside_walks_down_and_unions_repos() {
let parent = tempfile::TempDir::new().unwrap();
let repo_a = parent.path().join("a");
let repo_b = parent.path().join("b");
init_repo_at(&repo_a);
init_repo_at(&repo_b);
let wt_a = parent.path().join("a-feat-1");
let wt_b = parent.path().join("b-feat-2");
add_worktree_at(&repo_a, "feat-1", &wt_a);
add_worktree_at(&repo_b, "feat-2", &wt_b);
let scope = discover_scope(parent.path()).expect("scope");
let branches: Vec<Option<&str>> = scope
.worktrees()
.iter()
.map(|w| w.branch.as_deref())
.collect();
assert!(
branches.contains(&Some("feat-1")),
"missing feat-1: {branches:?}"
);
assert!(
branches.contains(&Some("feat-2")),
"missing feat-2: {branches:?}"
);
assert_eq!(
scope.worktrees().len(),
4,
"expected 4 entries, got {branches:?}"
);
}
#[test]
fn scope_outside_no_repos_errors() {
let parent = tempfile::TempDir::new().unwrap();
let result = discover_scope(parent.path());
assert!(result.is_err(), "expected Err for empty parent dir");
}