mod harness;
use std::fs;
use harness::TestRepo;
use tempfile::TempDir;
const MAGIC: &[u8] = b"\0GITXCRYPT\0";
fn elsewhere() -> TempDir {
TempDir::new().expect("could not create a temporary directory")
}
#[test]
fn lock_refuses_to_delete_the_key_over_a_declared_file_it_left_in_the_clear() {
let repo = TestRepo::init();
repo.git_ok(["config", "core.ignorecase", "true"]);
repo.init_xcrypt();
repo.write_xcrypt_config("secrets/\n");
repo.xcrypt_ok(["sync"]);
repo.write_file("secrets/db.env", b"hunter2-secret\n");
repo.commit_all("a secret");
assert!(repo.blob_bytes("secrets/db.env").starts_with(MAGIC));
let outside = elsewhere();
let key = outside.path().join("repo.key");
repo.xcrypt_ok(["export-key", &key.to_string_lossy()]);
fs::rename(repo.path().join("secrets"), repo.path().join("Secrets"))
.expect("renaming a directory by case must be possible");
let moved = repo.git_ok(["status", "--porcelain"]).stdout;
let case_insensitive = moved.is_empty();
let output = repo.xcrypt(["lock", "--yes"]);
if case_insensitive {
assert_eq!(
output.status.code(),
Some(2),
"lock closed a repository over a declared file it had left in the \
clear:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
repo.path().join(".git/git-xcrypt/keys/default").exists(),
"the key was deleted anyway, which is the half that cannot be undone"
);
assert_eq!(
fs::read(repo.path().join("Secrets/db.env")).expect("reading"),
b"hunter2-secret\n",
"the file is unchanged, so the user can rename it back and re-run"
);
let message = String::from_utf8_lossy(&output.stderr).into_owned();
assert!(
message.contains("Secrets/db.env"),
"the refusal does not name the file to look at:\n{message}"
);
} else {
assert!(
!moved.is_empty(),
"a case-sensitive checkout must report the rename"
);
}
}