git_policy_cli/replay_revalidation.rs
1//! What:
2//! The parts of revalidation after a replay that need no policy and no hook:
3//! the replayed index, the shadow `HEAD` move the policies see, and the decision whether `pre-commit` must re-run.
4//! Why:
5//! Policies and the `pre-commit` re-run belong to the integrator, which owns the policy engine and the hook dispatcher;
6//! these three steps are pure Git work the integrator calls before it.
7//!
8//! In TS you'd write (pseudocode):
9//! ```ts
10//! await runTransactionGit({ gitPath, cwd, indexPath, args: ['read-tree', mergedTree], objectDirectory });
11//! await moveShadowHead({ context, onto });
12//! const rerun = !context.noVerify && policyTree !== approvedTree;
13//! ```
14
15/// Private directories and the transaction Git runner.
16use super::private_storage::create_private_directory;
17use super::transaction_git::{GitContext, GitFailure, GitRequest, run_git_checked};
18/// `OsString` names the index and object-store variables.
19use std::path::{Path, PathBuf};
20
21/// What:
22/// The private index of one replayed tree: `replay-<r>/commit.index` under the transaction directory.
23/// Why:
24/// Policies and `pre-commit` see the replayed tree through this index, not the real one.
25///
26/// In TS you'd write (pseudocode):
27/// ```ts
28/// join(directory, 'commit.index');
29/// ```
30pub fn replayed_index_path(transaction_directory: &Path, replay: u64) -> PathBuf {
31 return transaction_directory
32 .join(format!("replay-{replay}"))
33 .join("commit.index");
34}
35
36/// What:
37/// Reads the merged tree into the replay's private index, with the shadow object store reachable.
38/// Why:
39/// The index is the input to policies and `pre-commit`; objects the merge wrote live in the shadow store.
40///
41/// In TS you'd write (pseudocode):
42/// ```ts
43/// await runTransactionGit({ gitPath, cwd, indexPath, args: ['read-tree', mergedTree], objectDirectory });
44/// ```
45pub fn read_replayed_index(
46 context: &GitContext,
47 cwd: &Path,
48 object_directory: &Path,
49 index_path: &Path,
50 merged_tree: &str,
51) -> Result<(), GitFailure> {
52 if let Some(parent) = index_path.parent() {
53 // Subsumption creates the same directory for its base index first.
54 if let Err(error) = create_private_directory(parent)
55 && error.kind() != std::io::ErrorKind::AlreadyExists
56 {
57 return Err(GitFailure(format!("creating {} failed: {error}", parent.display())));
58 }
59 }
60 let mut request: GitRequest = GitRequest::new(cwd, &["read-tree", merged_tree]);
61 request.index = Some(index_path.to_path_buf());
62 request.object_directory = Some(object_directory.to_path_buf());
63 run_git_checked(context, &request)?;
64 return Ok(());
65}
66
67/// What:
68/// Moves the shadow's target ref to the replay parent, so policies and hooks see the parent the commit will have.
69/// Why:
70/// A detached `HEAD` moves itself without dereferencing; a symbolic one moves the branch it names.
71///
72/// In TS you'd write (pseudocode):
73/// ```ts
74/// await runShadowGit({ args: ['update-ref', ...(detached ? ['--no-deref'] : []), targetRef, onto] });
75/// ```
76pub fn move_shadow_head(
77 context: &GitContext,
78 shadow: &Path,
79 cwd: &Path,
80 target_ref: &str,
81 onto: &str,
82 detached: bool,
83) -> Result<(), GitFailure> {
84 let mut arguments: Vec<&str> = vec!["update-ref"];
85 if detached {
86 arguments.push("--no-deref");
87 }
88 arguments.push(target_ref);
89 arguments.push(onto);
90 let request: GitRequest = super::shadow_git::shadow_request(shadow, cwd, &arguments);
91 run_git_checked(context, &request)?;
92 return Ok(());
93}
94
95/// What:
96/// Whether `pre-commit` must see the replayed tree:
97/// the invocation did not bypass hooks, and the policies changed the tree `pre-commit` last approved.
98/// Why:
99/// An unchanged tree was already approved, so re-running the hook would only repeat its answer.
100///
101/// In TS you'd write (pseudocode):
102/// ```ts
103/// const rerun = !context.noVerify && policyTree !== approvedTree;
104/// ```
105pub fn pre_commit_rerun_required(no_verify: bool, policy_tree: &str, approved_tree: &str) -> bool {
106 return !no_verify && policy_tree != approved_tree;
107}
108
109#[cfg(test)]
110#[path = "replay_revalidation_tests.rs"]
111mod tests;