use super::*;
use crate::capture_invocation::InvocationLayout;
use crate::transaction_git::{GitContext, GitFailure};
use crate::test_support::{REAL_GIT, fixture, git, remove, repository};
use crate::transaction_journal::{Conclusion, RefFormat, SymbolicHead};
use std::ffi::OsString;
fn capture() -> InvocationCapture {
return InvocationCapture {
base: Base::Unborn,
layout: InvocationLayout {
symbolic_head: SymbolicHead::Branch(String::from("refs/heads/main")),
target_ref: String::from("refs/heads/main"),
conclusion: Conclusion::None,
repository_root: PathBuf::from("/r"),
git_dir: PathBuf::from("/r/.git"),
common_dir: PathBuf::from("/r/.git"),
real_index_path: PathBuf::from("/r/.git/index"),
object_directory: PathBuf::from("/r/.git/objects"),
registry_root: PathBuf::from("/r/.git/cli-git-transactions"),
ref_format: RefFormat::Files,
object_format: String::from("sha1"),
empty_tree_oid: "4".repeat(40),
invoked_at: String::from("2026-10-11T00:00:00.000Z"),
},
};
}
#[test]
fn a_broken_replay_is_one_transaction_failure() {
match replay_failure(&capture(), "abc", String::from("because")) {
LoopOutcome::Failed { events, exit_code } => {
assert_eq!(exit_code, 2);
assert_eq!(events.len(), 1);
match &events[0] {
PolicyEvent::EngineFailure { message, code, trigger, .. } => {
assert_eq!(*code, crate::diagnostics::EngineFailureCode::TransactionFailed);
assert_eq!(*trigger, Some(crate::policy_trigger::Trigger::PreForward));
assert_eq!(
message,
"refs/heads/main moved while this commit was prepared, and replaying it onto abc failed; nothing landed: because"
);
}
other => panic!("expected an engine failure, got {other:?}"),
}
}
LoopOutcome::Landed { .. } => panic!("a broken replay lands nothing"),
}
}
#[test]
fn failure_events_keep_their_order() {
let settled: Vec<PolicyEvent> = vec![PolicyEvent::LandingReserved { lost_races: 1 }];
let raced: Vec<PolicyEvent> = vec![PolicyEvent::LandingReserved { lost_races: 2 }];
let last: Vec<PolicyEvent> = vec![PolicyEvent::LandingReserved { lost_races: 3 }];
assert_eq!(
failed_events(settled.as_slice(), raced.as_slice(), last),
vec![
PolicyEvent::LandingReserved { lost_races: 1 },
PolicyEvent::LandingReserved { lost_races: 2 },
PolicyEvent::LandingReserved { lost_races: 3 },
]
);
}
#[test]
fn replay_error_texts_keep_the_cause() {
assert_eq!(replay_error_text(&ReplayError::Git(GitFailure(String::from("git said no")))), "git said no");
assert_eq!(replay_error_text(&ReplayError::Object(String::from("bad object"))), "bad object");
assert!(replay_error_text(&ReplayError::Merge(crate::replay_merge::ReplayMergeError::Rebuild(String::from("rebuilt")))).contains("rebuilt"));
}
#[test]
fn a_reservation_failure_is_a_transaction_failure() {
match reservation_stop(ReservationError::Identity(String::from("who"))) {
TransactionStop::Failed { message, .. } => assert!(message.contains("who"), "{message}"),
other => panic!("expected a failure, got {other:?}"),
}
}
#[test]
fn completions_are_retargeted_to_the_replayed_blobs() {
let root: PathBuf = fixture("loop-retarget");
let repo: PathBuf = repository(root.as_path(), "repo");
std::fs::write(repo.join("same.txt"), b"same\n").expect("same");
std::fs::write(repo.join("moded.txt"), b"moded\n").expect("moded");
git(repo.as_path(), &["add", "--", "same.txt", "moded.txt"]);
git(repo.as_path(), &["update-index", "--chmod=+x", "moded.txt"]);
let blob: String = String::from_utf8_lossy(git(repo.as_path(), &["rev-parse", ":same.txt"]).stdout.as_slice()).trim().to_string();
let context: GitContext = GitContext {
real_git: PathBuf::from(REAL_GIT),
overlay: Vec::<(OsString, OsString)>::new(),
global_prefix: Vec::new(),
};
let environment: Vec<(OsString, OsString)> = Vec::new();
let config = crate::config_schema::CliGitConfig::defaults();
let controls = crate::wrapper_controls::no_controls();
let region = crate::command_commit::parse_commit_region(&[], &[]).expect("region");
let invocation: CommitInvocation<'_> = CommitInvocation {
context: &context,
environment: environment.as_slice(),
cwd: repo.as_path(),
arguments: &[],
subcommand_index: 0,
region: ®ion,
config: &config,
controls: &controls,
inherited_lease_valid: false,
executable: Path::new("/nonexistent"),
};
let record = |path: &str, mode: &str| {
return AddedPath {
path: String::from(path),
git_mode: String::from(mode),
original_oid: String::from("o"),
intended_oid: String::from("old"),
};
};
let retargeted: Vec<AddedPath> = retarget_records(
&invocation,
repo.join(".git/index").as_path(),
&[record("same.txt", "100644"), record("moded.txt", "100644"), record("gone.txt", "100644")],
)
.expect("retargeted");
assert_eq!(retargeted.len(), 1, "{retargeted:?}");
assert_eq!(retargeted[0].path, "same.txt");
assert_eq!(retargeted[0].intended_oid, blob);
assert_eq!(retargeted[0].original_oid, "o");
remove(root.as_path());
}
#[test]
fn a_lost_race_moves_the_counters_up_by_one() {
assert_eq!(next_attempt_number(1), 2);
assert_eq!(next_attempt_number(5), 6);
assert_eq!(lost_races_after(0), 1);
assert_eq!(lost_races_after(4), 5);
}
#[test]
fn replay_paths_are_the_union_without_repeats() {
let committed: Vec<String> = vec![String::from("a"), String::from("b")];
assert_eq!(
union_paths(committed.as_slice(), vec![String::from("b"), String::from("c"), String::from("c")]),
vec!["a", "b", "c"]
);
assert_eq!(union_paths(&[], Vec::new()), Vec::<String>::new());
}
#[test]
fn replay_events_name_the_base_and_the_dropped_headers_only_when_there_are_some() {
let rebuilt = |headers: Vec<String>| {
return ReplayedCommit {
oid: String::from("new"),
dropped_headers: headers,
};
};
let raced: Vec<PolicyEvent> = vec![PolicyEvent::LandingReserved { lost_races: 1 }];
let plain: Vec<PolicyEvent> =
replayed_events(raced.clone(), "prep", &Base::Commit(String::from("old")), "onto", &rebuilt(Vec::new()));
assert_eq!(plain.len(), 2);
assert_eq!(plain[0], raced[0]);
assert_eq!(
plain[1],
PolicyEvent::CommitReplayed {
prepared_oid: String::from("prep"),
from_base: Some(String::from("old")),
onto: String::from("onto"),
oid: String::from("new"),
}
);
let unborn: Vec<PolicyEvent> = replayed_events(Vec::new(), "prep", &Base::Unborn, "onto", &rebuilt(vec![String::from("x-h")]));
assert_eq!(unborn.len(), 2);
assert!(matches!(&unborn[0], PolicyEvent::CommitReplayed { from_base: None, .. }));
assert_eq!(
unborn[1],
PolicyEvent::ReplayHeadersDropped {
prepared_oid: String::from("prep"),
oid: String::from("new"),
headers: vec![String::from("x-h")],
}
);
}