use super::tests::{World, built_request, git_with_index, land_with_failed_index, request, world};
use super::*;
use crate::service_fault::hooks;
use crate::service_journal::{DONE_EXTENSION, OPEN_EXTENSION, batch_path, parse_intent};
use crate::test_support::{fixture, git, git_context, git_text, remove};
fn blind_context(root: &Path) -> GitContext {
let mut context: GitContext = git_context();
context.overlay.push((std::ffi::OsString::from("GIT_DIR"), root.join("no-such-git-dir").into_os_string()));
return context;
}
#[test]
fn a_git_that_cannot_read_the_repository_is_no_answer_about_the_head() {
let root: PathBuf = fixture("service-head-no-answer");
let context = blind_context(root.as_path());
for reference in ["HEAD", "refs/heads/main"] {
let asked = still_checked_out(&context, root.as_path(), root.join("no-git-dir").as_path(), root.join("no-common").as_path(), reference);
assert_eq!(asked, Err(FailureCode::Unavailable), "{reference}");
}
let moved = follows_move(&context, root.as_path(), root.join("no-git-dir").as_path(), root.join("no-common").as_path(), "HEAD", "0".repeat(40).as_str());
assert_eq!(moved, Err(FailureCode::Unavailable));
remove(root.as_path());
}
#[test]
fn a_head_attached_after_the_last_look_stays_attached() {
let world: World = world("service-head-attached");
let base: String = git_text(world.repo.as_path(), &["rev-parse", "HEAD"]);
git(world.repo.as_path(), &["checkout", "--quiet", "--detach"]);
git(world.repo.as_path(), &["branch", "attached", base.as_str()]);
let mut built: CommitRequest = built_request(&world, "loose", "loose.txt", &[base.as_str()]);
built.reference = String::from("HEAD");
let commit: String = built.commit.clone().unwrap_or_default();
let repo: PathBuf = world.repo.clone();
hooks::set("before-ref", std::rc::Rc::new(move || { git(repo.as_path(), &["symbolic-ref", "HEAD", "refs/heads/attached"]); }));
let outcomes = land_batch(&world.context, 1, &[built]).expect("lands");
assert_eq!(outcomes, vec![Outcome::Landed { commit: commit.clone() }]);
assert_eq!(git_text(world.repo.as_path(), &["symbolic-ref", "HEAD"]), "refs/heads/attached", "the head is still attached");
assert_eq!(git_text(world.repo.as_path(), &["rev-parse", "attached"]), commit, "the branch the head names took the commit");
assert_eq!(git_text(world.repo.as_path(), &["rev-parse", "main"]), base, "the other branch did not move");
assert_eq!(git_text(world.repo.as_path(), &["status", "--short", "--", "loose.txt"]), "", "the index shows the commit");
remove(world.root.as_path());
}
#[test]
fn a_late_switch_still_finishes_an_alternate_index() {
let world: World = world("service-head-late-alternate");
git(world.repo.as_path(), &["branch", "other"]);
let alternate: PathBuf = world.repo.join(".git/alternate-index");
std::fs::copy(world.repo.join(".git/index"), &alternate).expect("an alternate index");
let mut mine: CommitRequest = request(&world, "mine", "mine.txt", "mine\n");
mine.index = alternate.clone();
let repo: PathBuf = world.repo.clone();
hooks::set("before-ref", std::rc::Rc::new(move || { git(repo.as_path(), &["symbolic-ref", "HEAD", "refs/heads/other"]); }));
let outcome = land_batch(&world.context, 1, std::slice::from_ref(&mine)).expect("lands");
assert!(matches!(outcome.as_slice(), [Outcome::Landed { .. }]), "{outcome:?}");
let listed: String = git_with_index(world.repo.as_path(), alternate.as_path(), &["ls-files", "--stage", "--", "mine.txt"]);
assert!(!listed.is_empty(), "the alternate index shows the landing");
assert!(batch_path(world.context.journal.as_path(), 1, DONE_EXTENSION).exists() && !batch_path(world.context.journal.as_path(), 1, OPEN_EXTENSION).exists(), "the batch is finished");
remove(world.root.as_path());
}
#[test]
fn a_late_switch_keeps_the_batch_open_while_its_cleanup_is_pending() {
let world: World = world("service-head-late-cleanup");
git(world.repo.as_path(), &["branch", "other"]);
let scratch: PathBuf = world.root.join("scratch");
std::fs::create_dir_all(scratch.join(crate::conclusion_cleanup::CONCLUSION_COPY_DIRECTORY)).expect("a copy directory with no record in it");
let mut mine: CommitRequest = request(&world, "mine", "mine.txt", "mine\n");
mine.cleanup = Some(crate::service_protocol::Cleanup { git_dir: world.root.join("no-such-git-dir"), scratch, reftable: false });
let repo: PathBuf = world.repo.clone();
hooks::set("before-ref", std::rc::Rc::new(move || { git(repo.as_path(), &["symbolic-ref", "HEAD", "refs/heads/other"]); }));
let outcome = land_batch(&world.context, 1, std::slice::from_ref(&mine)).expect("lands");
assert!(matches!(outcome.as_slice(), [Outcome::Landed { .. }]), "{outcome:?}");
assert!(batch_path(world.context.journal.as_path(), 1, OPEN_EXTENSION).exists(), "the batch stays open while its cleanup cannot be done");
assert!(!world.repo.join(".git/index.lock").exists());
remove(world.root.as_path());
}
#[test]
fn a_restart_reads_the_head_under_its_lock() {
let world: World = world("service-head-under-lock");
git(world.repo.as_path(), &["branch", "other"]);
let landed: CommitRequest = request(&world, "landed", "landed.txt", "landed\n");
land_with_failed_index(&world, 1, &[landed]);
let before: Vec<u8> = std::fs::read(world.repo.join(".git/index")).expect("index");
let repo: PathBuf = world.repo.clone();
hooks::set("restart-locked", std::rc::Rc::new(move || { git(repo.as_path(), &["symbolic-ref", "HEAD", "refs/heads/other"]); }));
recover(&world.context);
assert_eq!(std::fs::read(world.repo.join(".git/index")).expect("index"), before, "the index of the branch checked out was not touched");
assert!(batch_path(world.context.journal.as_path(), 1, DONE_EXTENSION).exists(), "the batch is finished");
remove(world.root.as_path());
}
#[test]
fn a_restart_that_cannot_read_the_head_leaves_the_batch_open() {
let world: World = world("service-head-unreadable");
let landed: CommitRequest = request(&world, "landed", "landed.txt", "landed\n");
land_with_failed_index(&world, 1, &[landed]);
let open: PathBuf = batch_path(world.context.journal.as_path(), 1, OPEN_EXTENSION);
let mut intent: Intent = parse_intent(std::fs::read_to_string(&open).expect("read").as_str()).expect("parse");
intent.git_dir = world.root.join("no-git-dir");
intent.index = intent.git_dir.join("index");
intent.repository = world.root.clone();
let settled = crate::service_land_finish::settle(&blind_context(world.root.as_path()), &intent, world.context.journal.as_path());
assert!(matches!(settled, crate::service_land_finish::Settled::Open), "the batch stays open");
assert!(!world.root.join("no-git-dir").exists(), "nothing was made where the index would be");
remove(world.root.as_path());
}