git_policy_cli/commit_lifecycle.rs
1//! What:
2//! The call order around a guarded Git child:
3//! install the signal guard, run the child, release the locks and the transaction, drop the guard,
4//! then exit with the child's code.
5//! Why:
6//! The incumbent releases the transaction before the signal guard goes away, so a signal sent during
7//! release is still relayed to the child.
8//! The order after a landed commit (automatic maintenance, then the post-commit hook) is not here:
9//! the incumbent runs maintenance without stopping on failure (`src/policy-engine/commit-transaction-conclusion.ts`),
10//! and the post-landing lane owns that sequence.
11//! Nothing calls this yet: the command path keeps refusing `git commit` until landing is merged.
12//!
13//! In TS you'd write (pseudocode):
14//! ```ts
15//! const guard = installSignalGuard(); const outcome = await run(child); await release(); guard.drop(); exit(exitCode(outcome));
16//! ```
17
18use super::forwarding::exit_code;
19use super::signal_relay::SignalGuard;
20use std::process::Command;
21
22/// What:
23/// The steps that release what a guarded child held, run after the child ends and before the guard drops.
24/// Why:
25/// Release runs while the guard still relays signals, so a signal sent during release reaches the child.
26///
27/// In TS you'd write (pseudocode):
28/// ```ts
29/// interface ReleaseSteps { release(): Promise<void>; }
30/// ```
31pub trait ReleaseSteps {
32 /// Releases the landing lock and removes the transaction.
33 fn release(&mut self) -> Result<(), String>;
34}
35
36/// What:
37/// Run a Git child under the signal guard, release what it held, and report its exit code.
38/// Why:
39/// Release runs even when the child fails to run, and each failure is reported, not hidden.
40///
41/// In TS you'd write (pseudocode):
42/// ```ts
43/// const guard = installSignalGuard(); const outcome = await run(child); await release(); guard.drop(); exit(exitCode(outcome));
44/// ```
45pub fn run_guarded_child(command: &mut Command, release: &mut dyn ReleaseSteps) -> Result<i32, String> {
46 let guard: SignalGuard = match SignalGuard::install() {
47 Ok(installed) => installed,
48 Err(error) => return Err(format!("installing the signal guard failed: {error}")),
49 };
50 let outcome = guard.run_child(command);
51 let released: Result<(), String> = release.release();
52 drop(guard);
53 let child = match outcome {
54 Ok(found) => found,
55 Err(error) => return Err(format!("running the child failed: {error}")),
56 };
57 if let Err(error) = released {
58 return Err(format!("releasing after the child failed: {error}"));
59 }
60 return Ok(exit_code(child));
61}
62
63#[cfg(test)]
64#[path = "commit_lifecycle_tests.rs"]
65mod tests;