#![cfg(unix)]
use super::{
ADDED_PATH_HEAD_REASON, ADDED_PATH_MISSING_REASON, ADDED_PATH_STAGED_REASON,
ADDED_PATH_UNSTAGED_REASON, Entry, added_path_message, head_mode, worktree_refusal,
};
use crate::candidate_object::{CandidateMode, ObjectId, parse_object_id};
use crate::test_support::{fixture, remove};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
fn object(digit: u8) -> ObjectId {
return parse_object_id(&[digit; 40]).expect("forty hexadecimal digits");
}
fn entry(mode: CandidateMode, digit: u8) -> Entry {
return (mode, object(digit));
}
#[test]
fn the_message_names_the_file_the_reason_and_both_remedies() {
assert_eq!(
added_path_message("package/module/b/package.json", ADDED_PATH_UNSTAGED_REASON),
"A policy fix needs to change package/module/b/package.json, which this fix did not select, \
but its worktree copy has unstaged changes. Include package/module/b/package.json in the fix \
pathspecs so the fix applies to its worktree copy, or restore it to match HEAD \
(git restore --staged --worktree -- package/module/b/package.json), then run the fix again."
);
}
#[test]
fn head_and_index_must_hold_the_same_ordinary_file() {
let regular: Entry = entry(CandidateMode::Regular, b'a');
let executable: Entry = entry(CandidateMode::Executable, b'a');
assert_eq!(
head_mode(Some(®ular), ®ular),
Ok(CandidateMode::Regular)
);
assert_eq!(
head_mode(Some(&executable), &executable),
Ok(CandidateMode::Executable)
);
assert_eq!(head_mode(None, ®ular), Err(ADDED_PATH_HEAD_REASON));
assert_eq!(
head_mode(Some(&entry(CandidateMode::Regular, b'b')), ®ular),
Err(ADDED_PATH_HEAD_REASON)
);
for mode in [CandidateMode::Symlink, CandidateMode::Gitlink] {
assert_eq!(
head_mode(Some(&entry(mode, b'a')), &entry(mode, b'a')),
Err(ADDED_PATH_HEAD_REASON),
"{mode:?}"
);
}
assert_eq!(
head_mode(Some(®ular), &executable),
Err(ADDED_PATH_STAGED_REASON)
);
assert_eq!(
head_mode(Some(&executable), ®ular),
Err(ADDED_PATH_STAGED_REASON)
);
}
#[test]
fn the_worktree_copy_must_match_head() {
let root: PathBuf = fixture("added-path-worktree");
let file: PathBuf = root.join("file.json");
assert_eq!(
worktree_refusal(file.as_path(), CandidateMode::Regular, b"x\n").expect("missing"),
Some(ADDED_PATH_MISSING_REASON)
);
std::fs::write(&file, b"x\n").expect("file");
assert_eq!(
worktree_refusal(file.as_path(), CandidateMode::Regular, b"x\n").expect("clean"),
None
);
assert_eq!(
worktree_refusal(file.as_path(), CandidateMode::Regular, b"y\n").expect("changed"),
Some(ADDED_PATH_UNSTAGED_REASON)
);
assert_eq!(
worktree_refusal(file.as_path(), CandidateMode::Executable, b"x\n").expect("bit"),
Some(ADDED_PATH_UNSTAGED_REASON)
);
std::fs::set_permissions(&file, std::fs::Permissions::from_mode(0o755)).expect("executable");
assert_eq!(
worktree_refusal(file.as_path(), CandidateMode::Executable, b"x\n").expect("clean"),
None
);
assert_eq!(
worktree_refusal(file.as_path(), CandidateMode::Regular, b"x\n").expect("bit"),
Some(ADDED_PATH_UNSTAGED_REASON)
);
let link: PathBuf = root.join("link.json");
std::os::unix::fs::symlink("file.json", &link).expect("link");
assert_eq!(
worktree_refusal(link.as_path(), CandidateMode::Executable, b"x\n").expect("link"),
Some(ADDED_PATH_UNSTAGED_REASON)
);
let directory: PathBuf = root.join("directory.json");
std::fs::create_dir(&directory).expect("directory");
assert_eq!(
worktree_refusal(directory.as_path(), CandidateMode::Regular, b"x\n").expect("dir"),
Some(ADDED_PATH_UNSTAGED_REASON)
);
let unreadable: PathBuf = root.join("unreadable.json");
std::fs::write(&unreadable, b"x\n").expect("unreadable file");
std::fs::set_permissions(&unreadable, std::fs::Permissions::from_mode(0o200))
.expect("write-only");
if std::fs::read(&unreadable).is_err() {
assert!(worktree_refusal(unreadable.as_path(), CandidateMode::Regular, b"x\n").is_err());
}
let beneath: PathBuf = file.join("child");
assert!(worktree_refusal(beneath.as_path(), CandidateMode::Regular, b"x\n").is_err());
remove(root.as_path());
}