use super::*;
use crate::test_support::{fixture, git, git_context, git_text, remove, repository};
struct Scene {
root: PathBuf,
repo: PathBuf,
git_dir: PathBuf,
shadow: PathBuf,
transaction: PathBuf,
}
fn scene(name: &str) -> Scene {
let root: PathBuf = fixture(name);
let repo: PathBuf = repository(root.as_path(), "repo");
let shadow_work: PathBuf = repository(root.as_path(), "shadow");
let transaction: PathBuf = root.join("transaction");
std::fs::create_dir_all(transaction.join(CONCLUSION_COPY_DIRECTORY)).expect("copies");
std::fs::write(
transaction
.join(CONCLUSION_COPY_DIRECTORY)
.join(STORE_RECORD_FILENAME),
b"{}",
)
.expect("store record");
return Scene {
git_dir: repo.join(".git"),
repo,
shadow: shadow_work.join(".git"),
root,
transaction,
};
}
fn clean(scene: &Scene, format: RefFormat) -> Result<(), RecoveryError> {
return reproduce_conclusion_cleanup(
&git_context(),
scene.repo.as_path(),
scene.git_dir.as_path(),
scene.shadow.as_path(),
scene.transaction.as_path(),
format,
);
}
#[test]
fn names_match_the_incumbent() {
assert_eq!(CONCLUSION_STATE_FILES.len(), 8);
assert!(is_store_held(RefFormat::Reftable, "AUTO_MERGE"));
assert!(is_store_held(RefFormat::Reftable, "CHERRY_PICK_HEAD"));
assert!(is_store_held(RefFormat::Reftable, "REVERT_HEAD"));
assert!(!is_store_held(RefFormat::Reftable, "MERGE_HEAD"));
assert!(!is_store_held(RefFormat::Files, "AUTO_MERGE"));
}
#[test]
fn absent_copies_change_nothing() {
let scene: Scene = scene("conclusion-absent");
std::fs::remove_dir_all(scene.transaction.join(CONCLUSION_COPY_DIRECTORY)).expect("no copies");
std::fs::write(scene.git_dir.join("MERGE_MSG"), b"msg").expect("real entry");
clean(&scene, RefFormat::Files).expect("cleanup");
assert!(scene.git_dir.join("MERGE_MSG").exists());
remove(scene.root.as_path());
}
#[test]
fn entries_are_removed_only_while_unchanged() {
let scene: Scene = scene("conclusion-files");
let copies: PathBuf = scene.transaction.join(CONCLUSION_COPY_DIRECTORY);
std::fs::write(copies.join("MERGE_HEAD"), b"oid\n").expect("copy");
std::fs::write(scene.git_dir.join("MERGE_HEAD"), b"oid\n").expect("real");
std::fs::write(copies.join("MERGE_MSG"), b"old").expect("copy");
std::fs::write(scene.git_dir.join("MERGE_MSG"), b"new").expect("real");
std::fs::write(copies.join("SQUASH_MSG"), b"s").expect("copy");
std::fs::write(scene.shadow.join("SQUASH_MSG"), b"s").expect("shadow");
std::fs::write(scene.git_dir.join("SQUASH_MSG"), b"s").expect("real");
std::fs::write(scene.git_dir.join("MERGE_MODE"), b"no-ff").expect("real");
std::fs::write(copies.join("ORIG_HEAD"), b"o").expect("copy");
std::fs::write(scene.git_dir.join("ORIG_HEAD"), b"o").expect("real");
std::fs::write(copies.join("CHERRY_PICK_HEAD"), b"c").expect("copy");
std::fs::write(scene.git_dir.join("CHERRY_PICK_HEAD"), b"c").expect("real");
std::fs::write(copies.join("REVERT_HEAD"), b"r").expect("copy");
clean(&scene, RefFormat::Files).expect("cleanup");
assert!(!scene.git_dir.join("MERGE_HEAD").exists());
assert!(!scene.git_dir.join("CHERRY_PICK_HEAD").exists());
assert_eq!(
std::fs::read(scene.git_dir.join("MERGE_MSG")).expect("kept"),
b"new"
);
assert!(scene.git_dir.join("SQUASH_MSG").exists());
assert!(scene.git_dir.join("MERGE_MODE").exists());
assert!(scene.git_dir.join("ORIG_HEAD").exists());
assert!(!scene.git_dir.join("REVERT_HEAD").exists());
remove(scene.root.as_path());
}
#[test]
fn merge_rr_is_copied_back() {
let scene: Scene = scene("conclusion-merge-rr");
std::fs::write(scene.git_dir.join("MERGE_RR"), b"old").expect("real");
clean(&scene, RefFormat::Files).expect("cleanup without shadow MERGE_RR");
assert_eq!(
std::fs::read(scene.git_dir.join("MERGE_RR")).expect("kept"),
b"old"
);
std::fs::write(scene.shadow.join("MERGE_RR"), b"resolved\0path").expect("shadow");
clean(&scene, RefFormat::Files).expect("cleanup");
assert_eq!(
std::fs::read(scene.git_dir.join("MERGE_RR")).expect("copied"),
b"resolved\0path"
);
let mut leftovers: usize = 0;
for entry in std::fs::read_dir(&scene.git_dir).expect("list") {
let name: String = entry
.expect("entry")
.file_name()
.to_string_lossy()
.into_owned();
if name.starts_with("MERGE_RR.cli-git-") {
leftovers += 1;
}
}
assert_eq!(leftovers, 0);
remove(scene.root.as_path());
}
#[test]
fn sequencer_state_follows_native_git() {
let scene: Scene = scene("conclusion-sequencer");
let copy: PathBuf = scene
.transaction
.join(CONCLUSION_COPY_DIRECTORY)
.join(SEQUENCER_DIRECTORY);
let real: PathBuf = scene.git_dir.join(SEQUENCER_DIRECTORY);
for directory in [©, &real] {
std::fs::create_dir_all(directory.join("nested")).expect("sequencer");
std::fs::write(directory.join("todo"), b"pick a").expect("todo");
std::fs::write(directory.join("nested").join("opts"), b"o").expect("nested");
}
std::fs::create_dir(scene.shadow.join(SEQUENCER_DIRECTORY)).expect("shadow sequencer");
clean(&scene, RefFormat::Files).expect("cleanup");
assert!(real.exists());
std::fs::remove_dir(scene.shadow.join(SEQUENCER_DIRECTORY)).expect("native removed it");
std::fs::write(real.join("nested").join("opts"), b"changed").expect("changed");
clean(&scene, RefFormat::Files).expect("cleanup");
assert!(real.exists());
std::fs::write(real.join("extra"), b"x").expect("extra");
std::fs::write(real.join("nested").join("opts"), b"o").expect("restored");
clean(&scene, RefFormat::Files).expect("cleanup");
assert!(real.exists());
std::fs::remove_file(real.join("extra")).expect("extra removed");
clean(&scene, RefFormat::Files).expect("cleanup");
assert!(!real.exists());
std::fs::remove_dir_all(©).expect("remove copy");
std::fs::create_dir(©).expect("empty copy");
std::fs::create_dir(&real).expect("empty real");
clean(&scene, RefFormat::Files).expect("cleanup");
assert!(real.exists());
assert!(!same_tree(copy.as_path(), real.as_path()).expect("empty trees"));
assert!(
!same_tree(scene.root.join("missing").as_path(), real.as_path()).expect("missing tree")
);
remove(scene.root.as_path());
}
#[test]
fn store_records_must_be_json() {
let scene: Scene = scene("conclusion-store-record");
let record: PathBuf = scene
.transaction
.join(CONCLUSION_COPY_DIRECTORY)
.join(STORE_RECORD_FILENAME);
std::fs::write(&record, b"{").expect("malformed");
assert_eq!(
clean(&scene, RefFormat::Files).expect_err("malformed").0,
format!("{} is not JSON.", record.display())
);
std::fs::write(&record, [0xff]).expect("binary");
assert_eq!(
clean(&scene, RefFormat::Files).expect_err("binary").0,
format!("{} is not UTF-8.", record.display())
);
std::fs::remove_file(&record).expect("missing");
assert!(
clean(&scene, RefFormat::Files)
.expect_err("missing")
.0
.starts_with("reading ")
);
remove(scene.root.as_path());
}
#[test]
fn reftable_pseudorefs_are_deleted_through_git() {
let root: PathBuf = fixture("conclusion-reftable");
let repo: PathBuf = root.join("repo");
let shadow_work: PathBuf = root.join("shadow");
for directory in [&repo, &shadow_work] {
std::fs::create_dir(directory).expect("directory");
git(
directory.as_path(),
&[
"init",
"--quiet",
"--initial-branch=main",
"--ref-format=reftable",
],
);
git(
directory.as_path(),
&["commit", "--quiet", "--allow-empty", "--message=initial"],
);
}
let head: String = git_text(repo.as_path(), &["rev-parse", "HEAD"]);
git(
repo.as_path(),
&["update-ref", "CHERRY_PICK_HEAD", head.as_str()],
);
git(
repo.as_path(),
&["update-ref", "REVERT_HEAD", head.as_str()],
);
git(repo.as_path(), &["update-ref", "AUTO_MERGE", head.as_str()]);
git(
shadow_work.as_path(),
&["commit", "--quiet", "--allow-empty", "--message=shadow"],
);
let shadow_head: String = git_text(shadow_work.as_path(), &["rev-parse", "HEAD"]);
assert_ne!(shadow_head, head);
git(
shadow_work.as_path(),
&["update-ref", "AUTO_MERGE", shadow_head.as_str()],
);
let transaction: PathBuf = root.join("transaction");
std::fs::create_dir_all(transaction.join(CONCLUSION_COPY_DIRECTORY)).expect("copies");
std::fs::write(
transaction.join(CONCLUSION_COPY_DIRECTORY).join(STORE_RECORD_FILENAME),
format!(
"{{\"CHERRY_PICK_HEAD\":\"{head}\",\"REVERT_HEAD\":\"{shadow_head}\",\"AUTO_MERGE\":\"{head}\",\"MERGE_HEAD\":1}}"
),
)
.expect("store record");
let scene: Scene = Scene {
git_dir: repo.join(".git"),
repo: repo.clone(),
shadow: shadow_work.join(".git"),
root: root.clone(),
transaction,
};
clean(&scene, RefFormat::Reftable).expect("cleanup");
let verify: std::process::Output = crate::test_support::git_output(
repo.as_path(),
&["rev-parse", "--verify", "--quiet", "CHERRY_PICK_HEAD"],
);
assert!(!verify.status.success());
assert_eq!(
git_text(repo.as_path(), &["rev-parse", "--verify", "REVERT_HEAD"]),
head
);
assert_eq!(
git_text(repo.as_path(), &["rev-parse", "--verify", "AUTO_MERGE"]),
head
);
std::fs::write(
scene
.transaction
.join(CONCLUSION_COPY_DIRECTORY)
.join(STORE_RECORD_FILENAME),
"{\"REVERT_HEAD\":7,\"AUTO_MERGE\":null}",
)
.expect("non-text values");
clean(&scene, RefFormat::Reftable).expect("cleanup");
assert_eq!(
git_text(repo.as_path(), &["rev-parse", "--verify", "REVERT_HEAD"]),
head
);
std::fs::write(
scene
.transaction
.join(CONCLUSION_COPY_DIRECTORY)
.join(STORE_RECORD_FILENAME),
b"[]",
)
.expect("array record");
clean(&scene, RefFormat::Reftable).expect("cleanup");
assert_eq!(
git_text(repo.as_path(), &["rev-parse", "--verify", "REVERT_HEAD"]),
head
);
assert_eq!(
resolve_pseudoref(
&git_context(),
GitRequest::new(
repo.as_path(),
&["rev-parse", "--verify", "--quiet", "NONE_HEAD"]
)
),
None
);
remove(root.as_path());
}