git_policy_cli/
commit_revalidation.rs1use super::candidate_object::{ObjectId, parse_object_id};
15use super::capture_invocation::InvocationCapture;
16use super::commit_convergence::{CommitConvergence, CommitConvergenceInput, converge_commit_policies};
17use super::commit_facts::PrivateIndexFacts;
18use super::commit_index::{list_changed_index_paths, write_private_tree};
19use super::commit_transaction::{CommitInvocation, TransactionStop, failed, settled_passes, staged_at_base};
20use super::commit_workspace::CommitWorkspace;
21use super::diagnostic_log::debug;
22use super::policy_checks::ShippedChecks;
23use super::policy_content::{ContentState, LifecycleContent};
24use super::policy_engine::{StageRequest, pass_exit_code};
25use super::policy_events::PolicyEvent;
26use super::policy_pass::{PassResult, run_policy_pass};
27use super::private_storage::create_private_directory;
28use super::replay_revalidation::{move_shadow_head, read_replayed_index, replayed_index_path};
29use super::repository_facts::RepositoryFacts;
30use super::transaction_git::GitContext;
31use super::transaction_journal::SymbolicHead;
32use std::path::{Path, PathBuf};
33
34const TAG: &str = "revalidateReplay";
36
37pub enum Revalidation {
47 Revalidated {
49 tree: String,
51 index: PathBuf,
53 events: Vec<PolicyEvent>,
55 committed_paths: Vec<Vec<u8>>,
57 },
58 Blocked {
60 events: Vec<PolicyEvent>,
62 exit_code: i32,
64 },
65}
66
67pub struct RevalidationContext<'a> {
77 pub invocation: &'a CommitInvocation<'a>,
79 pub capture: &'a InvocationCapture,
81 pub read_only_selection: bool,
83}
84
85pub fn revalidate_replay<F: RepositoryFacts>(
96 context: &RevalidationContext<'_>,
97 workspace: &CommitWorkspace,
98 request: &StageRequest,
99 checks: &mut ShippedChecks<PrivateIndexFacts<F>>,
100 replay: u64,
101 merged_tree: &str,
102 onto: &str,
103) -> Result<Revalidation, TransactionStop> {
104 let git: &GitContext = context.invocation.context;
105 let cwd: &Path = context.invocation.cwd;
106 let index: PathBuf = replayed_index_path(workspace.landing.directory.as_path(), replay);
107 let directory: PathBuf = workspace.landing.directory.join(format!("replay-{replay}"));
108 ensure_replay_directory(directory.as_path())
109 .map_err(|error| { return failed(format!("creating {} failed: {error}", directory.display())); })?;
110 read_replayed_index(git, cwd, workspace.object_directory.as_path(), index.as_path(), merged_tree)?;
111 move_shadow_head(
112 git,
113 workspace.landing.shadow.as_path(),
114 cwd,
115 context.capture.layout.target_ref.as_str(),
116 onto,
117 is_detached(&context.capture.layout.symbolic_head),
118 )?;
119 let candidate_paths: Vec<Vec<u8>> =
120 list_changed_index_paths(git, cwd, index.as_path(), onto, workspace.object_directory.as_path())?;
121 let base: Option<ObjectId> = parse_object_id(onto.as_bytes());
122 checks.facts.use_index(index.as_path(), workspace.object_directory.as_path());
123 checks.content = ContentState::new();
124 checks.candidates = LifecycleContent::Requested(staged_at_base(base, candidate_paths.as_slice()));
125 let (pass, changed_passes, changed_paths): (PassResult, u64, Vec<Vec<u8>>) = if context.read_only_selection {
126 checks.can_apply_patches = false;
127 (run_policy_pass(request, checks), 0, Vec::new())
128 } else {
129 let outcome: CommitConvergence = converge_commit_policies(CommitConvergenceInput {
130 request,
131 checks,
132 stage: super::private_index_stage::PrivateStage {
133 context: git,
134 cwd,
135 index: index.as_path(),
136 object_directory: Some(workspace.object_directory.as_path()),
137 },
138 });
139 let passes: u64 = settled_passes(&outcome.convergence);
140 (outcome.pass, passes, outcome.changed_paths)
141 };
142 let code: i32 = pass_exit_code(pass.events.as_slice(), pass.end);
143 if code != 0 {
144 return Ok(Revalidation::Blocked {
145 events: pass.events,
146 exit_code: code,
147 });
148 }
149 let policy_tree: String =
150 write_private_tree(git, cwd, index.as_path(), workspace.object_directory.as_path())?;
151 let tree: String = policy_tree;
152 debug(TAG, format!("revalidated replay {replay} onto {onto}: tree {tree}").as_str());
153 return Ok(Revalidation::Revalidated {
154 tree,
155 index,
156 events: super::commit_conclusion::events_with_changed_summary(pass.events, changed_passes, changed_paths.as_slice()),
157 committed_paths: candidate_paths,
158 });
159}
160
161pub fn ensure_replay_directory(directory: &Path) -> std::io::Result<()> {
171 match create_private_directory(directory) {
172 Err(error) if error.kind() != std::io::ErrorKind::AlreadyExists => return Err(error),
173 Ok(()) | Err(_) => return Ok(()),
174 }
175}
176
177pub fn is_detached(head: &SymbolicHead) -> bool {
187 return *head == SymbolicHead::Detached;
188}
189
190#[cfg(test)]
192#[path = "commit_revalidation_tests.rs"]
193mod tests;