49ab7e9 made a new-branch worktree add suffix the branch name instead of
failing, and documented it, but left two tests asserting the old hard
rejection. The rejection contract (#66) still holds for an existing
branch that is already checked out, so that test now exercises that
case.
575 lines
20 KiB
Rust
575 lines
20 KiB
Rust
//! Worktree-manager integration tests against a real `git` binary and a
|
|
//! tmpdir-backed source repo.
|
|
|
|
mod common;
|
|
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
use common::{init_bare_repo, init_repo, push_all_to_origin};
|
|
use maestro_daemon::worktree::WorktreeManager;
|
|
use maestro_daemon::worktree_probe::in_the_way;
|
|
use tempfile::TempDir;
|
|
|
|
fn worktree_branches(repo: &PathBuf) -> Vec<String> {
|
|
let out = Command::new("git")
|
|
.arg("-C")
|
|
.arg(repo)
|
|
.args(["worktree", "list", "--porcelain"])
|
|
.output()
|
|
.expect("git worktree list");
|
|
assert!(out.status.success());
|
|
let s = String::from_utf8_lossy(&out.stdout);
|
|
s.lines()
|
|
.filter_map(|l| l.strip_prefix("branch refs/heads/").map(String::from))
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn add_creates_worktree_on_new_branch() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let wt_root = TempDir::new().unwrap();
|
|
let mgr = WorktreeManager::new(Some(wt_root.path().to_path_buf()));
|
|
|
|
let path = mgr.add_new(&repo, "feat/example").expect("add worktree");
|
|
assert!(path.exists());
|
|
assert!(path.join("README").exists());
|
|
assert!(path.starts_with(wt_root.path()));
|
|
|
|
let branches = worktree_branches(&repo);
|
|
assert!(branches.iter().any(|b| b == "feat/example"), "{branches:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn add_places_worktree_inside_the_base_repo_by_default() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
// No override: the worktree must land where the repository keeps its
|
|
// own worktrees (#56), not under some daemon state dir.
|
|
let mgr = WorktreeManager::new(None);
|
|
|
|
let path = mgr.add_new(&repo, "feat/example").expect("add worktree");
|
|
assert_eq!(path, repo.join("worktrees").join("feat").join("example"));
|
|
assert!(path.exists());
|
|
assert!(path.join("README").exists());
|
|
|
|
let branches = worktree_branches(&repo);
|
|
assert!(branches.iter().any(|b| b == "feat/example"), "{branches:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn worktree_root_override_redirects_placement() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let wt_root = TempDir::new().unwrap();
|
|
let mgr = WorktreeManager::new(Some(wt_root.path().to_path_buf()));
|
|
|
|
// The repo's basename sits between the root and the branch slug, so two
|
|
// repos spawning the same branch name don't target one directory.
|
|
let repo_name = repo.file_name().unwrap();
|
|
let path = mgr.add_new(&repo, "feat/example").expect("add worktree");
|
|
// The override root disambiguates same-named checkouts with a digest of
|
|
// the full repo path, and the branch keeps its own shape.
|
|
// <root>/<repo basename>-<digest>/feat/example
|
|
let prefix = format!("{}-", repo_name.to_string_lossy());
|
|
let repo_dir_name = path
|
|
.strip_prefix(wt_root.path())
|
|
.unwrap()
|
|
.components()
|
|
.next()
|
|
.unwrap();
|
|
assert!(
|
|
repo_dir_name
|
|
.as_os_str()
|
|
.to_string_lossy()
|
|
.starts_with(&prefix),
|
|
"{path:?}"
|
|
);
|
|
assert!(path.ends_with("feat/example"), "{path:?}");
|
|
assert!(path.exists());
|
|
}
|
|
|
|
/// Anything at all occupying the target path — ours, the operator's, or an
|
|
/// unrelated directory — is sidestepped, not adopted and not reported (#77).
|
|
#[test]
|
|
fn an_occupied_target_path_gets_a_numeric_suffix() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
// A non-git directory sits exactly where the worktree would go.
|
|
let squatter = repo.join("worktrees").join("feat").join("collide");
|
|
std::fs::create_dir_all(&squatter).unwrap();
|
|
std::fs::write(squatter.join("operator-notes.txt"), b"mine\n").unwrap();
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let path = mgr.add_new(&repo, "feat/collide").expect("sidesteps");
|
|
assert_eq!(path, repo.join("worktrees").join("feat").join("collide-2"));
|
|
assert!(path.exists());
|
|
assert!(
|
|
squatter.join("operator-notes.txt").exists(),
|
|
"the occupant is untouched"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn add_twice_generates_distinct_paths() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let wt_root = TempDir::new().unwrap();
|
|
let mgr = WorktreeManager::new(Some(wt_root.path().to_path_buf()));
|
|
|
|
let p1 = mgr.add_new(&repo, "feat/a").expect("add a");
|
|
let p2 = mgr.add_new(&repo, "feat/b").expect("add b");
|
|
assert_ne!(p1, p2);
|
|
assert!(p1.exists());
|
|
assert!(p2.exists());
|
|
}
|
|
|
|
/// The unsaved-work probe (#79) counts uncommitted tracked changes.
|
|
#[test]
|
|
fn the_probe_counts_uncommitted_tracked_changes() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let path = mgr.add_new(&repo, "feat/dirty").expect("add");
|
|
std::fs::write(path.join("README"), b"unsaved edits\n").unwrap();
|
|
|
|
let w = in_the_way(&path).expect("probe");
|
|
assert_eq!(w.uncommitted_files, 1);
|
|
assert_eq!(w.untracked_files, 0);
|
|
}
|
|
|
|
/// Untracked sources count; a staged new file is also unsaved tracked work.
|
|
#[test]
|
|
fn the_probe_counts_untracked_files() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let path = mgr.add_new(&repo, "feat/untracked").expect("add");
|
|
std::fs::write(path.join("notes.txt"), b"wip\n").unwrap();
|
|
std::fs::write(path.join("more.txt"), b"also wip\n").unwrap();
|
|
|
|
let w = in_the_way(&path).expect("probe");
|
|
assert_eq!(w.untracked_files, 2);
|
|
assert_eq!(w.uncommitted_files, 0);
|
|
|
|
run_git(&path, &["add", "notes.txt"]);
|
|
let w = in_the_way(&path).expect("probe");
|
|
assert_eq!(w.uncommitted_files, 1, "staged counts as uncommitted");
|
|
assert_eq!(w.untracked_files, 1);
|
|
}
|
|
|
|
/// Ignored files NEVER block: every Rust checkout has a `target/`, and
|
|
/// counting ignored output would leave no session killable (#79).
|
|
#[test]
|
|
fn the_probe_ignores_ignored_files() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
// .gitignore must be committed, or it would itself read as untracked.
|
|
std::fs::write(repo.join(".gitignore"), b"/target\n").unwrap();
|
|
run_git(&repo, &["add", ".gitignore"]);
|
|
run_git(&repo, &["commit", "-q", "-m", "ignore target"]);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let path = mgr.add_new(&repo, "feat/builds").expect("add");
|
|
std::fs::create_dir_all(path.join("target")).unwrap();
|
|
std::fs::write(path.join("target").join("app.o"), b"binary\n").unwrap();
|
|
|
|
let w = in_the_way(&path).expect("probe");
|
|
assert_eq!(w.uncommitted_files, 0);
|
|
assert_eq!(w.untracked_files, 0);
|
|
|
|
// With the branch pushed too, an ignored-only tree reads fully clean.
|
|
let origin_dir = TempDir::new().unwrap();
|
|
init_bare_repo(origin_dir.path());
|
|
push_all_to_origin(&repo, origin_dir.path());
|
|
let w = in_the_way(&path).expect("probe after push");
|
|
assert!(w.is_clear(), "ignored-only worktree must read clean: {w:?}");
|
|
}
|
|
|
|
/// Commits reachable from no remote-tracking ref block; pushing the branch
|
|
/// clears them. With no remote at all every commit is unreachable from one,
|
|
/// which is exactly why feature branches almost always refuse (#79).
|
|
#[test]
|
|
fn the_probe_counts_unpushed_commits_until_the_branch_is_pushed() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let path = mgr.add_new(&repo, "feat/branch").expect("add");
|
|
|
|
let w = in_the_way(&path).expect("probe");
|
|
assert!(
|
|
w.unpushed_commits >= 1,
|
|
"a fresh branch has no remote-tracking ref: {w:?}"
|
|
);
|
|
|
|
let origin_dir = TempDir::new().unwrap();
|
|
init_bare_repo(origin_dir.path());
|
|
push_all_to_origin(&repo, origin_dir.path());
|
|
|
|
let w = in_the_way(&path).expect("probe after push");
|
|
assert_eq!(w.unpushed_commits, 0, "{w:?}");
|
|
assert!(w.is_clear(), "pushed and clean must be empty: {w:?}");
|
|
}
|
|
|
|
/// The full breakdown names each blocker with its count.
|
|
#[test]
|
|
fn the_probe_reports_every_blocker_at_once() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let path = mgr.add_new(&repo, "feat/mixed").expect("add");
|
|
std::fs::write(path.join("README"), b"edited\n").unwrap();
|
|
std::fs::write(path.join("scratch.txt"), b"untracked\n").unwrap();
|
|
|
|
let w = in_the_way(&path).expect("probe");
|
|
assert_eq!(w.uncommitted_files, 1);
|
|
assert_eq!(w.untracked_files, 1);
|
|
assert!(w.unpushed_commits >= 1);
|
|
let summary = w.to_string();
|
|
assert!(summary.contains("1 uncommitted file"), "{summary}");
|
|
assert!(summary.contains("1 untracked file"), "{summary}");
|
|
assert!(summary.contains("unpushed commit"), "{summary}");
|
|
}
|
|
|
|
#[test]
|
|
fn remove_prunes_worktree() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let wt_root = TempDir::new().unwrap();
|
|
let mgr = WorktreeManager::new(Some(wt_root.path().to_path_buf()));
|
|
|
|
let path = mgr.add_new(&repo, "feat/gone").expect("add");
|
|
assert!(path.exists());
|
|
|
|
mgr.remove(&repo, &path).expect("remove");
|
|
assert!(!path.exists());
|
|
|
|
let branches = worktree_branches(&repo);
|
|
// The branch itself still exists, but the worktree should be gone — only
|
|
// the base repo's main branch should show in `worktree list`.
|
|
assert_eq!(branches, vec!["main".to_string()]);
|
|
}
|
|
|
|
#[test]
|
|
fn add_existing_checks_out_branch_without_creating_it() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
// Pre-create the branch with its own commit so we can tell a checkout
|
|
// from an accidental `-b`.
|
|
let created = Command::new("git")
|
|
.arg("-C")
|
|
.arg(&repo)
|
|
.args(["checkout", "-q", "-b", "feat/dup"])
|
|
.status()
|
|
.unwrap();
|
|
assert!(created.success());
|
|
std::fs::write(repo.join("FEATURE"), b"dup\n").unwrap();
|
|
let staged = Command::new("git")
|
|
.arg("-C")
|
|
.arg(&repo)
|
|
.args(["add", "."])
|
|
.status()
|
|
.unwrap();
|
|
assert!(staged.success());
|
|
let committed = Command::new("git")
|
|
.arg("-C")
|
|
.arg(&repo)
|
|
.args(["commit", "-q", "-am", "wip"])
|
|
.status()
|
|
.unwrap();
|
|
assert!(committed.success());
|
|
let back = Command::new("git")
|
|
.arg("-C")
|
|
.arg(&repo)
|
|
.args(["checkout", "-q", "main"])
|
|
.status()
|
|
.unwrap();
|
|
assert!(back.success());
|
|
|
|
let wt_root = TempDir::new().unwrap();
|
|
let mgr = WorktreeManager::new(Some(wt_root.path().to_path_buf()));
|
|
|
|
let path = mgr.add_existing(&repo, "feat/dup").expect("add existing");
|
|
assert!(path.exists());
|
|
assert!(path.join("README").exists());
|
|
|
|
// The branch set is unchanged: `feat/dup` was checked out, not recreated.
|
|
let branches = worktree_branches(&repo);
|
|
let mut sorted = branches.clone();
|
|
sorted.sort();
|
|
assert_eq!(
|
|
sorted,
|
|
vec!["feat/dup".to_string(), "main".to_string()],
|
|
"{branches:?}"
|
|
);
|
|
}
|
|
|
|
/// Removal is guarded upstream now (#79): the kill refuses unsaved work
|
|
/// before anything is torn down, so by the time `remove` runs the tree is
|
|
/// clean. Git still refuses a dirty tree on its own — this asserts that last
|
|
/// line of defense — and committed-but-unpushed work does not block removal,
|
|
/// because `worktree remove` keeps `refs/heads/<branch>` and the commits stay
|
|
/// reachable.
|
|
#[test]
|
|
fn remove_still_relies_on_git_refusing_a_dirty_tree() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let path = mgr.add_new(&repo, "feat/dirty").expect("add");
|
|
std::fs::write(path.join("README"), b"unsaved edits\n").unwrap();
|
|
|
|
mgr.remove(&repo, &path)
|
|
.expect_err("dirty worktree must be refused");
|
|
assert!(path.exists(), "guarded worktree must stay on disk");
|
|
|
|
run_git(&path, &["add", "."]);
|
|
run_git(&path, &["commit", "-q", "-m", "saved"]);
|
|
|
|
mgr.remove(&repo, &path).expect("committed work can go");
|
|
assert!(!path.exists());
|
|
assert!(
|
|
worktree_branches(&repo).contains(&"main".to_string()),
|
|
"the branch survives removal"
|
|
);
|
|
let branches = String::from_utf8(
|
|
Command::new("git")
|
|
.arg("-C")
|
|
.arg(&repo)
|
|
.args(["for-each-ref", "--format=%(refname:short)", "refs/heads"])
|
|
.output()
|
|
.unwrap()
|
|
.stdout,
|
|
)
|
|
.unwrap();
|
|
assert!(branches.contains("feat/dirty"), "{branches}");
|
|
}
|
|
|
|
/// A worktree whose directory vanished must be pruned, not misread as holding
|
|
/// unsaved work — otherwise the registration is stranded and, because paths
|
|
/// are deterministic, that branch can never be spawned again (#56 review).
|
|
#[test]
|
|
fn remove_prunes_a_worktree_whose_directory_vanished() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let path = mgr.add_new(&repo, "feat/gone").expect("add");
|
|
std::fs::remove_dir_all(&path).unwrap();
|
|
|
|
mgr.remove(&repo, &path)
|
|
.expect("a vanished worktree prunes");
|
|
// The registration is gone too, so the same branch can be spawned again.
|
|
mgr.add_existing(&repo, "feat/gone")
|
|
.expect("branch is spawnable again");
|
|
}
|
|
|
|
fn run_git(dir: &std::path::Path, args: &[&str]) {
|
|
let out = Command::new("git")
|
|
.arg("-C")
|
|
.arg(dir)
|
|
.args(args)
|
|
.output()
|
|
.expect("git spawn");
|
|
assert!(
|
|
out.status.success(),
|
|
"git {args:?} failed: {}",
|
|
String::from_utf8_lossy(&out.stderr)
|
|
);
|
|
}
|
|
|
|
/// 49ab7e9: a new-branch add onto a name that already exists suffixes the
|
|
/// branch (and so the directory) instead of failing, per docs/WORKTREES.md.
|
|
#[test]
|
|
fn add_new_suffixes_when_branch_already_exists() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
// pre-create the branch
|
|
let status = Command::new("git")
|
|
.arg("-C")
|
|
.arg(&repo)
|
|
.args(["branch", "feat/dup"])
|
|
.status()
|
|
.unwrap();
|
|
assert!(status.success());
|
|
|
|
let wt_root = TempDir::new().unwrap();
|
|
let mgr = WorktreeManager::new(Some(wt_root.path().to_path_buf()));
|
|
|
|
let path = mgr.add_new(&repo, "feat/dup").unwrap();
|
|
assert!(path.ends_with("feat/dup-2"), "{}", path.display());
|
|
assert!(path.is_dir());
|
|
}
|
|
|
|
/// The project picker lists linked worktrees as pickable rows, so `base_repo`
|
|
/// is easily a worktree rather than the main checkout. Placing inside it would
|
|
/// nest one worktree in another — and git hides a registered worktree's
|
|
/// contents from the outer `git status`, so the outer tree reads clean and
|
|
/// removing it deletes the inner one's uncommitted work (#54 review).
|
|
#[test]
|
|
fn placement_resolves_a_linked_worktree_to_the_main_checkout() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let outer = mgr.add_new(&repo, "feat/outer").expect("add outer");
|
|
|
|
// Spawn again with the *worktree* as the base, the way the picker would.
|
|
let inner = mgr.add_new(&outer, "feat/inner").expect("add inner");
|
|
assert!(
|
|
!inner.starts_with(&outer),
|
|
"must not nest: {inner:?} inside {outer:?}"
|
|
);
|
|
assert!(
|
|
inner.starts_with(repo.canonicalize().unwrap().join("worktrees")),
|
|
"placed against the main checkout: {inner:?}"
|
|
);
|
|
}
|
|
|
|
/// Belt and braces for a nesting that predates this daemon: refuse to remove
|
|
/// a worktree that has another registered underneath it.
|
|
#[test]
|
|
fn remove_refuses_a_worktree_holding_a_nested_one() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let outer = mgr.add_new(&repo, "feat/outer").expect("add outer");
|
|
// Register one by hand inside `outer`, bypassing our placement.
|
|
let nested = outer.join("worktrees").join("hand-rolled");
|
|
run_git(
|
|
&repo,
|
|
&[
|
|
"worktree",
|
|
"add",
|
|
"-b",
|
|
"feat/hand",
|
|
nested.to_str().unwrap(),
|
|
],
|
|
);
|
|
std::fs::write(nested.join("notes.txt"), b"unsaved\n").unwrap();
|
|
|
|
mgr.remove(&repo, &outer)
|
|
.expect_err("a worktree holding a nested one must be refused");
|
|
assert!(nested.join("notes.txt").exists(), "inner work survives");
|
|
}
|
|
|
|
/// Two repos spawning the same branch name under one override root must not
|
|
/// collide (#54 review).
|
|
#[test]
|
|
fn override_root_keeps_repos_apart() {
|
|
let root_dir = TempDir::new().unwrap();
|
|
let a_dir = TempDir::new().unwrap();
|
|
let b_dir = TempDir::new().unwrap();
|
|
init_repo(a_dir.path());
|
|
init_repo(b_dir.path());
|
|
|
|
let mgr = WorktreeManager::new(Some(root_dir.path().to_path_buf()));
|
|
let a = mgr.add_new(a_dir.path(), "feat/shared").expect("repo a");
|
|
let b = mgr.add_new(b_dir.path(), "feat/shared").expect("repo b");
|
|
assert_ne!(a, b, "same branch name in two repos must not collide");
|
|
}
|
|
|
|
/// Flattening `/` and `.` to `-` made these three share one directory, so the
|
|
/// losers could never get a worktree (#54 review).
|
|
#[test]
|
|
fn similar_branch_names_get_distinct_directories() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let a = mgr.add_new(&repo, "feat/x").expect("feat/x");
|
|
let b = mgr.add_new(&repo, "feat.x").expect("feat.x");
|
|
let c = mgr.add_new(&repo, "feat-x").expect("feat-x");
|
|
assert_ne!(a, b);
|
|
assert_ne!(b, c);
|
|
assert_ne!(a, c);
|
|
assert!(a.ends_with("feat/x"), "{a:?}");
|
|
}
|
|
|
|
/// A guarded leftover stays on disk untouched, and is never adopted (#77).
|
|
/// Re-spawning its branch is refused by git itself — the branch is still
|
|
/// checked out inside the leftover — which the caller surfaces.
|
|
#[test]
|
|
fn a_guarded_leftover_is_never_adopted() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let mgr = WorktreeManager::new(None);
|
|
let first = mgr.add_new(&repo, "feat/resume").expect("add");
|
|
std::fs::write(first.join("notes.txt"), b"unsaved\n").unwrap();
|
|
|
|
// Removal is refused — git itself refuses a dirty tree — leaving it on
|
|
// disk; the daemon-side guard lives on the kill (#79).
|
|
mgr.remove(&repo, &first)
|
|
.expect_err("dirty tree is guarded");
|
|
|
|
let err = mgr
|
|
.add_existing(&repo, "feat/resume")
|
|
.expect_err("a branch already checked out cannot be spawned twice");
|
|
assert!(format!("{err}").contains("git exited"), "{err}");
|
|
assert!(
|
|
first.join("notes.txt").exists(),
|
|
"the leftover's unsaved work is untouched"
|
|
);
|
|
}
|
|
|
|
/// The `worktree_root` override gets the same collision suffix (#77).
|
|
#[test]
|
|
fn the_override_root_suffixes_collisions_too() {
|
|
let repo_dir = TempDir::new().unwrap();
|
|
let repo = repo_dir.path().to_path_buf();
|
|
init_repo(&repo);
|
|
|
|
let wt_root = TempDir::new().unwrap();
|
|
let mgr = WorktreeManager::new(Some(wt_root.path().to_path_buf()));
|
|
|
|
// First add settles `<root>/<repo basename>-<digest>/` so the squatter
|
|
// can be created at the exact target.
|
|
mgr.add_new(&repo, "feat/first").expect("add first");
|
|
let repo_dir_in_root = std::fs::read_dir(wt_root.path())
|
|
.unwrap()
|
|
.next()
|
|
.unwrap()
|
|
.unwrap()
|
|
.path();
|
|
let squatter = repo_dir_in_root.join("feat").join("collide");
|
|
std::fs::create_dir_all(&squatter).unwrap();
|
|
|
|
let path = mgr.add_new(&repo, "feat/collide").expect("sidesteps");
|
|
assert_eq!(path, squatter.with_file_name("collide-2"), "{path:?}");
|
|
}
|