mod harness;
use git_xcrypt::crypto::format::MAGIC;
use harness::TestRepo;
const FIRST: &[u8] = b"api_key = one\nshared line\n";
const SECOND: &[u8] = b"api_key = two\nshared line\n";
const THIRD: &[u8] = b"api_key = three\nshared line\n";
fn prepared() -> TestRepo {
let repo = TestRepo::init();
repo.init_xcrypt();
repo.write_xcrypt_config("secrets/\n");
repo.xcrypt_ok(["sync"]);
repo
}
fn carries_magic(haystack: &[u8]) -> bool {
haystack.windows(MAGIC.len()).any(|window| window == MAGIC)
}
fn text(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes).into_owned()
}
fn review(repo: &TestRepo, arguments: &[&str], wanted: &[&str]) {
let output = repo.git_ok(arguments);
let rendered = text(&output.stdout);
let label = arguments.join(" ");
for line in wanted {
assert!(
rendered.contains(line),
"`git {label}` did not show `{line}`:\n{rendered}"
);
}
assert!(
!rendered.contains("Binary files"),
"`git {label}` fell back to git's answer for content it cannot read, so \
the diff driver was never consulted:\n{rendered}"
);
assert!(
!carries_magic(&output.stdout),
"`git {label}` put ciphertext on the user's screen"
);
}
#[test]
fn a_secret_is_reviewed_as_plaintext_at_every_point_a_user_looks_at_it() {
let repo = prepared();
repo.write_file("secrets/db.env", FIRST);
repo.write_file("README.md", b"# ordinary project\n");
repo.commit_all("declare a secret");
repo.write_file("secrets/db.env", SECOND);
assert!(
repo.blob_is_encrypted("secrets/db.env"),
"the fixture is not encrypted, so this test grades nothing"
);
review(
&repo,
&["--no-pager", "diff", "--", "secrets/db.env"],
&["-api_key = one", "+api_key = two", " shared line"],
);
repo.commit_all("rotate the key");
review(
&repo,
&["--no-pager", "show", "HEAD", "--", "secrets/db.env"],
&["-api_key = one", "+api_key = two"],
);
review(
&repo,
&["--no-pager", "log", "-p", "--", "secrets/db.env"],
&["+api_key = one", "-api_key = one", "+api_key = two"],
);
repo.write_file("secrets/db.env", THIRD);
repo.commit_all("rotate it again");
repo.assert_status_clean();
review(
&repo,
&[
"--no-pager",
"diff",
"HEAD~2",
"HEAD~1",
"--",
"secrets/db.env",
],
&["-api_key = one", "+api_key = two"],
);
repo.write_file("README.md", b"# a better project\n");
review(
&repo,
&["--no-pager", "diff", "--", "README.md"],
&["-# ordinary project", "+# a better project"],
);
repo.git_ok(["checkout", "--", "README.md"]);
}
#[test]
fn a_declared_path_git_stopped_filtering_is_still_reviewed_as_plaintext() {
let repo = prepared();
repo.write_file("secrets/db.env", FIRST);
repo.commit_all("declare a secret");
repo.write_file("secrets/db.env", SECOND);
repo.commit_all("rotate the key");
let mut attributes = repo.worktree_bytes(".gitattributes");
attributes.extend_from_slice(b"secrets/** -filter\n");
repo.write_file(".gitattributes", &attributes);
repo.git_ok(["add", ".gitattributes"]);
repo.git_ok(["commit", "-q", "-m", "a foreign line takes the filter off"]);
assert_eq!(
repo.check_attr("filter", "secrets/db.env"),
"unset",
"the fixture no longer takes the filter off the declared path"
);
assert_eq!(
repo.check_attr("diff", "secrets/db.env"),
"git-xcrypt",
"the fixture no longer leaves the diff driver on"
);
review(
&repo,
&[
"--no-pager",
"diff",
"HEAD~2",
"HEAD~1",
"--",
"secrets/db.env",
],
&["-api_key = one", "+api_key = two"],
);
let output = repo.xcrypt(["status"]);
let report = String::from_utf8_lossy(&output.stdout).into_owned();
assert_eq!(
output.status.code(),
Some(2),
"a repository git does not filter must fail the gate as a configuration \
problem:\n{report}"
);
assert!(
report.contains("setup: git is NOT filtering"),
"the finding belongs in the setup section:\n{report}"
);
assert!(
report.contains("secrets/db.env"),
"the report has to name the path git leaves unfiltered:\n{report}"
);
assert!(
report.contains("`unset`"),
"the report has to quote what git resolves instead:\n{report}"
);
}
#[test]
fn a_locked_repository_can_still_be_read_with_git_log() {
let repo = prepared();
repo.write_file("secrets/db.env", FIRST);
repo.commit_all("first");
repo.xcrypt_ok(["lock", "--yes"]);
assert!(
!repo
.git(["config", "--get", "diff.git-xcrypt.textconv"])
.status
.success(),
"lock left a diff driver that has no key behind it"
);
let output = repo.git(["--no-pager", "log", "-p", "--", "secrets/db.env"]);
assert!(
output.status.success(),
"a locked repository lost `git log -p`: {}",
text(&output.stderr)
);
assert!(
text(&output.stdout).contains("Binary files"),
"expected git's own answer for content nobody can read: {}",
text(&output.stdout)
);
}