#[cfg(any(target_os = "linux", target_os = "macos"))]
mod branch_namespace_tests {
use super::*;
fn fixture() -> (tempfile::TempDir, PathBuf, PathBuf) {
let root = tempfile::tempdir().unwrap();
let original = root.path().join("original");
std::fs::create_dir(&original).unwrap();
init_repo(&original);
git(&original, &["branch", "-m", "original-topic"]);
let task = root.path().join("task");
git(
&original,
&[
"worktree",
"add",
"--detach",
task.to_str().unwrap(),
"HEAD",
],
);
own_gitdir_grants(&task);
(root, original, task)
}
fn conflict(existing: &str, requested: &str, packed: bool) {
let (_root, original, task) = fixture();
if existing != "original-topic" {
git(&original, &["branch", existing]);
}
let common = original.join(".git");
if packed {
git(&original, &["pack-refs", "--all"]);
}
std::fs::remove_dir_all(common.join("logs/refs/heads")).unwrap();
let (common_dir, admin) = git_dirs(&task).unwrap();
let identity = BoundGitIdentity::bind(common_dir, admin.clone()).unwrap();
let heads_before = identity
.common_workspace
.read_dir(Path::new("refs/heads"))
.unwrap();
let head_before = std::fs::read(admin.join("HEAD")).unwrap();
let packed_before = std::fs::read(common.join("packed-refs")).ok();
let original_before = git_output(&original, &["rev-parse", "original-topic"]);
let result = create_worktree_branch(&task, requested);
assert!(
result.is_err(),
"namespace collision {existing} -> {requested}: {result:?}"
);
assert_eq!(
identity
.common_workspace
.read_dir(Path::new("refs/heads"))
.unwrap(),
heads_before,
"refusal must precede parent directory creation"
);
assert_eq!(std::fs::read(admin.join("HEAD")).unwrap(), head_before);
assert_eq!(
std::fs::read(common.join("packed-refs")).ok(),
packed_before
);
assert_eq!(
git_output(&original, &["rev-parse", "original-topic"]),
original_before
);
assert!(!common.join("logs/refs/heads").exists());
assert!(!common.join("packed-refs.lock").exists());
}
#[test]
fn packed_original_ancestor_refuses_before_mkdir() {
conflict("original-topic", "original-topic/child", true);
}
#[test]
fn packed_descendant_refuses_before_mkdir() {
conflict("topic/child", "topic", true);
}
#[test]
fn packed_exact_refuses_before_mkdir() {
conflict("topic/child", "topic/child", true);
}
#[test]
fn loose_namespace_collisions() {
conflict("topic", "topic/child", false);
conflict("topic/child", "topic", false);
}
#[test]
fn invalid_loose_ref_is_not_absence() {
let (_root, original, task) = fixture();
let (common, admin) = git_dirs(&task).unwrap();
let identity = BoundGitIdentity::bind(common.clone(), admin.clone()).unwrap();
let head_before = std::fs::read(admin.join("HEAD")).unwrap();
for bytes in [
"",
"\n \t",
"garbage\n",
"0123\n",
"0000000000000000000000000000000000000000\n",
"ref: refs/heads/original-topic\n",
] {
let target = original.join(".git/refs/heads/sentinel");
std::fs::write(&target, bytes).unwrap();
let result = create_worktree_branch(&task, "sentinel");
assert!(
result.is_err(),
"invalid loose ref {bytes:?} overwritten: {result:?}"
);
assert!(read_ref_natively(&identity, "sentinel").is_err());
assert_eq!(std::fs::read(&target).unwrap(), bytes.as_bytes());
assert_eq!(std::fs::read(admin.join("HEAD")).unwrap(), head_before);
assert!(!common.join("logs/refs/heads/sentinel").exists());
assert!(!common.join("refs/heads/sentinel.lock").exists());
assert!(!common.join("packed-refs.lock").exists());
}
}
#[test]
fn native_creation_keeps_git_writers_out_until_publish() {
let (root, original, task) = fixture();
let (common, admin) = git_dirs(&task).unwrap();
let identity = BoundGitIdentity::bind(common.clone(), admin).unwrap();
let oid = git_output(&original, &["rev-parse", "HEAD"]);
let competing = || {
for name in ["refs/heads/topic/child", "refs/heads/topic"] {
let result = hermetic_git(&original, root.path())
.args(["update-ref", name, &oid, ""])
.output()
.unwrap();
assert!(!result.status.success(), "competing Git published {name}");
}
};
advance_branch_ref_natively_seamed(
&identity,
"topic/child",
None,
&oid,
"new branch",
&competing,
)
.unwrap();
assert_eq!(git_output(&original, &["rev-parse", "topic/child"]), oid);
assert!(!common.join("refs/heads/topic/child.lock").exists());
assert!(!common.join("packed-refs.lock").exists());
}
#[test]
fn leaf_lock_contention_releases_namespace_lock() {
let (_root, original, task) = fixture();
let common = original.join(".git");
let lock = common.join("refs/heads/fresh.lock");
std::fs::write(&lock, "other writer").unwrap();
assert!(create_worktree_branch(&task, "fresh").is_err());
assert_eq!(std::fs::read_to_string(&lock).unwrap(), "other writer");
assert!(!common.join("packed-refs.lock").exists());
assert!(!common.join("logs/refs/heads/fresh").exists());
std::fs::remove_file(lock).unwrap();
create_worktree_branch(&task, "fresh/nested").unwrap();
create_worktree_branch(&task, "fresh/peer").unwrap();
create_worktree_branch(&task, "fresh-other").unwrap();
}
#[test]
fn packed_writer_lock_excludes_creation() {
let (_root, original, task) = fixture();
let lock = original.join(".git/packed-refs.lock");
std::fs::write(&lock, "other writer").unwrap();
assert!(create_worktree_branch(&task, "fresh/nested").is_err());
assert_eq!(std::fs::read_to_string(&lock).unwrap(), "other writer");
assert!(!original.join(".git/refs/heads/fresh").exists());
}
}