Skip to main content

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;