#![cfg(unix)]
use super::{WorktreeIdentity, parse_identity_output, resolve_worktree_identity, worktree_root};
use crate::test_support::{REAL_GIT, fixture, git, remove, repository};
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
use std::path::{Path, PathBuf};
fn prefix(parts: &[&Path]) -> Vec<OsString> {
let mut result: Vec<OsString> = Vec::<OsString>::new();
for part in parts {
result.push(part.as_os_str().to_os_string());
}
return result;
}
fn identity(parts: &[&Path]) -> WorktreeIdentity {
return resolve_worktree_identity(Path::new(REAL_GIT), prefix(parts).as_slice(), &[])
.expect("identity query");
}
#[test]
fn successful_output_is_a_main_or_linked_worktree() {
assert_eq!(
parse_identity_output(true, b"false\n/r/.git\n/r/.git\n/r\n").expect("main"),
WorktreeIdentity::MainWorktree {
common_dir: PathBuf::from("/r/.git"),
git_dir: PathBuf::from("/r/.git"),
worktree_root: PathBuf::from("/r"),
}
);
assert_eq!(
parse_identity_output(true, b"false\n/r/.git/worktrees/w\n/r/.git\n/w\n").expect("linked"),
WorktreeIdentity::LinkedWorktree {
common_dir: PathBuf::from("/r/.git"),
git_dir: PathBuf::from("/r/.git/worktrees/w"),
worktree_root: PathBuf::from("/w"),
}
);
assert_eq!(
parse_identity_output(true, b"false\r\n/r\xff/.git\r\n/r\xff/.git\r\n/r\xff\r\n")
.expect("CRLF"),
WorktreeIdentity::MainWorktree {
common_dir: PathBuf::from(OsString::from_vec(b"/r\xff/.git".to_vec())),
git_dir: PathBuf::from(OsString::from_vec(b"/r\xff/.git".to_vec())),
worktree_root: PathBuf::from(OsString::from_vec(b"/r\xff".to_vec())),
}
);
}
#[test]
fn failed_output_distinguishes_absent_bare_and_git_directory() {
assert_eq!(
parse_identity_output(false, b"").expect("outside"),
WorktreeIdentity::OutsideWorktree
);
assert_eq!(
parse_identity_output(false, b"true\n/b.git\n/b.git\n").expect("bare"),
WorktreeIdentity::BareRepository {
common_dir: PathBuf::from("/b.git"),
git_dir: PathBuf::from("/b.git"),
}
);
assert_eq!(
parse_identity_output(false, b"false\n/r/.git\n/r/.git\n").expect("inside .git"),
WorktreeIdentity::OutsideWorktree
);
}
#[test]
fn unexpected_output_shapes_are_rejected() {
for (success, stdout) in [
(true, b"".as_slice()),
(true, b"false\n/r/.git\n/r/.git\n"),
(true, b"false\n/r/.git\n/r/.git\n/r\nextra\n"),
(true, b"false\n/r/.git\n/r/.git\n/a\nb\n"),
(true, b"maybe\n/r/.git\n/r/.git\n/r\n"),
(true, b"\n/r/.git\n/r/.git\n/r\n"),
(true, b"false\n\n/r/.git\n/r\n"),
(true, b"false\n/r/.git\n\n/r\n"),
(true, b"false\n/r/.git\n/r/.git\n\n"),
(false, b"true\n"),
(false, b"true\n/b.git\n"),
(false, b"true\n/b.git\n/b.git\n/extra\n"),
(false, b"TRUE\n/b.git\n/b.git\n"),
(false, b"true\n\n/b.git\n"),
] {
let error = parse_identity_output(success, stdout).expect_err("malformed");
assert!(
error.to_string().starts_with(
"cli-git could not classify the Git worktree: git rev-parse returned "
),
"{error}"
);
}
}
#[test]
fn native_git_identifies_main_and_linked_worktrees() {
let root: PathBuf = fixture("identity-worktrees");
let main: PathBuf = repository(root.as_path(), "main");
let linked: PathBuf = root.join("linked");
git(
main.as_path(),
&[
OsString::from("worktree"),
OsString::from("add"),
OsString::from("--quiet"),
OsString::from("-b"),
OsString::from("topic"),
linked.clone().into_os_string(),
],
);
std::fs::create_dir(main.join("nested")).expect("nested directory");
let expected_main: WorktreeIdentity = WorktreeIdentity::MainWorktree {
common_dir: main.join(".git"),
git_dir: main.join(".git"),
worktree_root: main.clone(),
};
assert_eq!(identity(&[Path::new("-C"), main.as_path()]), expected_main);
assert_eq!(
identity(&[Path::new("-C"), main.join("nested").as_path()]),
expected_main
);
assert_eq!(worktree_root(&expected_main), Some(main.as_path()));
let expected_linked: WorktreeIdentity = WorktreeIdentity::LinkedWorktree {
common_dir: main.join(".git"),
git_dir: main.join(".git/worktrees/linked"),
worktree_root: linked.clone(),
};
assert_eq!(
identity(&[Path::new("-C"), linked.as_path()]),
expected_linked
);
assert_eq!(worktree_root(&expected_linked), Some(linked.as_path()));
remove(root.as_path());
}
#[test]
fn native_git_applies_global_selection_options() {
let root: PathBuf = fixture("identity-options");
let main: PathBuf = repository(root.as_path(), "main");
std::fs::create_dir(main.join("nested")).expect("nested directory");
std::os::unix::fs::symlink(main.join("nested"), root.join("link")).expect("symlink");
let expected: WorktreeIdentity = WorktreeIdentity::MainWorktree {
common_dir: main.join(".git"),
git_dir: main.join(".git"),
worktree_root: main.clone(),
};
assert_eq!(
identity(&[
Path::new("-C"),
root.as_path(),
Path::new("-C"),
Path::new("link/.."),
Path::new("-C"),
Path::new(""),
]),
expected
);
let elsewhere: PathBuf = root.join("elsewhere");
std::fs::create_dir(&elsewhere).expect("work tree directory");
assert_eq!(
identity(&[
Path::new("--git-dir"),
main.join(".git").as_path(),
Path::new("--work-tree"),
elsewhere.as_path(),
]),
WorktreeIdentity::MainWorktree {
common_dir: main.join(".git"),
git_dir: main.join(".git"),
worktree_root: elsewhere.clone(),
}
);
remove(root.as_path());
}
#[test]
fn native_git_reports_locations_without_a_worktree() {
let root: PathBuf = fixture("identity-none");
let main: PathBuf = repository(root.as_path(), "main");
let bare: PathBuf = root.join("bare.git");
git(
root.as_path(),
&[
OsString::from("init"),
OsString::from("--quiet"),
OsString::from("--bare"),
bare.clone().into_os_string(),
],
);
let bare_identity: WorktreeIdentity = identity(&[Path::new("-C"), bare.as_path()]);
assert_eq!(
bare_identity,
WorktreeIdentity::BareRepository {
common_dir: bare.clone(),
git_dir: bare.clone(),
}
);
assert_eq!(worktree_root(&bare_identity), None);
assert_eq!(
identity(&[Path::new("-C"), main.join(".git").as_path()]),
WorktreeIdentity::OutsideWorktree
);
let plain: PathBuf = root.join("plain");
std::fs::create_dir(&plain).expect("plain directory");
assert_eq!(
identity(&[Path::new("-C"), plain.as_path()]),
WorktreeIdentity::OutsideWorktree
);
assert_eq!(
identity(&[Path::new("-C"), root.join("missing").as_path()]),
WorktreeIdentity::OutsideWorktree
);
assert_eq!(worktree_root(&WorktreeIdentity::OutsideWorktree), None);
remove(root.as_path());
}
#[test]
fn native_git_identifies_non_utf8_repository_paths() {
let root: PathBuf = fixture("identity-bytes");
let name: OsString = OsString::from_vec(b"repo-\xff\xfe".to_vec());
let created: PathBuf = repository(root.as_path(), "placeholder");
let renamed: PathBuf = root.join(&name);
std::fs::rename(&created, &renamed).expect("rename to non-UTF-8 name");
assert_eq!(
identity(&[Path::new("-C"), renamed.as_path()]),
WorktreeIdentity::MainWorktree {
common_dir: renamed.join(".git"),
git_dir: renamed.join(".git"),
worktree_root: renamed.clone(),
}
);
remove(root.as_path());
}
#[test]
fn unstartable_git_is_an_error() {
assert_eq!(
resolve_worktree_identity(Path::new("/nonexistent-directory/git"), &[], &[])
.expect_err("missing program")
.kind(),
std::io::ErrorKind::NotFound
);
}