1use super::capture_invocation::InvocationCapture;
17use super::commit_args::{resolve_private_commit_args, split_commit_invocation, without_all_flag};
18use super::commit_landing_loop::{LoopInput, LoopOutcome, land_with_replay};
19use super::commit_facts::PrivateIndexFacts;
20use super::commit_index::write_private_tree;
21use super::commit_no_change::{NoChangeInput, complete_no_change_transaction};
22use super::commit_preparation::{PREPARED_FILENAME, PreparationRequest, read_prepared_commit, run_native_commit};
23use super::commit_replay_options::{ReplayOptions, replay_options};
24use super::journal_record::RecordSync;
25use super::commit_transaction::{CommitInvocation, Selection, TransactionResult, TransactionStop, write_journal_record};
26use super::commit_workspace::CommitWorkspace;
27use super::commit_worktree_records::selected_worktree_records;
28use super::diagnostic_log::debug;
29use super::global_arguments::global_layout;
30use super::lfs_pre_push::uses_lfs;
31use super::policy_checks::ShippedChecks;
32use super::policy_engine::StageRequest;
33use super::policy_events::PolicyEvent;
34use super::policy_pass::PassResult;
35use super::post_landing::{LandedTransaction, PostLandingRequest, complete_landed_transaction};
36use super::repository_facts::RepositoryFacts;
37use super::transaction_git::GitContext;
38use super::transaction_journal::{AddedPath, PreparedRecord};
39use super::transaction_journal_encode::encode_prepared;
40use super::transaction_phase::{Phase, reach_transaction_phase};
41use std::ffi::OsString;
42use std::path::Path;
43
44const TAG: &str = "concludeCommitTransaction";
46
47pub struct ConclusionInput<'a, F: RepositoryFacts> {
57 pub invocation: &'a CommitInvocation<'a>,
59 pub workspace: &'a mut CommitWorkspace,
61 pub capture: &'a InvocationCapture,
63 pub selection: &'a Selection,
65 pub request: &'a StageRequest,
67 pub checks: &'a mut ShippedChecks<PrivateIndexFacts<F>>,
69 pub pass: PassResult,
71 pub changed_passes: u64,
73 pub changed_paths: Vec<Vec<u8>>,
75 pub committed_paths: Vec<Vec<u8>>,
77 pub pathspecs: &'a [String],
79}
80
81pub fn events_with_changed_summary(events: Vec<PolicyEvent>, passes: u64, changed_paths: &[Vec<u8>]) -> Vec<PolicyEvent> {
91 if passes == 0 {
92 return events;
93 }
94 return events_with_summary(events, passes, changed_paths);
95}
96
97pub fn events_with_summary(mut events: Vec<PolicyEvent>, passes: u64, changed_paths: &[Vec<u8>]) -> Vec<PolicyEvent> {
107 let mut sorted: Vec<Vec<u8>> = changed_paths.to_vec();
108 sorted.sort();
109 events.push(PolicyEvent::FixSummary {
110 trigger: super::policy_trigger::Trigger::PreForward,
111 passes,
112 changed_paths: sorted
113 .iter()
114 .map(|path| { return super::event_path::EventPath::from_git_bytes(path.as_slice()); })
115 .collect(),
116 });
117 return events;
118}
119
120fn private_invocation(pass: &PassResult, pathspecs: &[String], selection: &Selection) -> (Vec<String>, Vec<String>) {
130 let text: Vec<String> = pass.arguments.iter().map(|token| { return token.to_string_lossy().into_owned(); }).collect();
131 let private_args: Vec<String> =
132 resolve_private_commit_args(text.as_slice(), pathspecs, selection.mode, selection.read_only);
133 let owned: Vec<OsString> = private_args.iter().map(OsString::from).collect();
134 let index: usize = global_layout(owned.as_slice()).prefix_len;
135 return split_commit_invocation(private_args.as_slice(), index);
136}
137
138pub fn conclude_commit_transaction<F: RepositoryFacts>(
148 input: ConclusionInput<'_, F>,
149) -> Result<TransactionResult, TransactionStop> {
150 let ConclusionInput {
151 invocation,
152 workspace,
153 capture,
154 selection,
155 request,
156 checks,
157 pass,
158 changed_passes,
159 changed_paths,
160 committed_paths,
161 pathspecs,
162 } = input;
163 let context: &GitContext = invocation.context;
164 let cwd: &Path = invocation.cwd;
165 let repository_root: &Path = capture.layout.repository_root.as_path();
166 let timeout_ms: u64 = invocation.config.concurrency.index_lock.unproven_owner_timeout_ms;
167 let intended_tree: String = write_private_tree(
168 context,
169 cwd,
170 workspace.landing.commit_index.as_path(),
171 workspace.object_directory.as_path(),
172 )?;
173 let changes = checks.content.install_changes();
174 let selected: Vec<AddedPath> = selected_worktree_records(
175 context,
176 cwd,
177 repository_root,
178 workspace.landing.commit_index.as_path(),
179 changes.as_slice(),
180 )?;
181 let eligible: bool = !changes.is_empty()
182 && !invocation.region.allow_empty
183 && !invocation.region.amend
184 && !selection.concludes_sequencer;
185 if let Some(result) = complete_no_change_transaction(NoChangeInput {
186 invocation,
187 workspace,
188 capture,
189 mode: selection.mode,
190 eligible,
191 intended_tree: intended_tree.as_str(),
192 committed_paths: committed_paths.as_slice(),
193 selected_worktree_paths: selected.as_slice(),
194 events: &pass.events,
195 changed_passes,
196 changed_paths: changed_paths.as_slice(),
197 index_lock_timeout_ms: timeout_ms,
198 })? {
199 return Ok(result);
200 }
201 let (global_args, commit_args): (Vec<String>, Vec<String>) = private_invocation(&pass, pathspecs, selection);
202 let native_args: Vec<String> = if selection.staged_all {
203 without_all_flag(commit_args.as_slice())
204 } else {
205 commit_args.clone()
206 };
207 let preparation: PreparationRequest<'_> = PreparationRequest {
208 capture,
209 shadow: workspace.landing.shadow.as_path(),
210 commit_index: workspace.landing.commit_index.as_path(),
211 global_args: global_args.as_slice(),
212 commit_args: native_args.as_slice(),
213 object_directory: None,
214 };
215 let status: i32 = run_native_commit(context, cwd, &preparation).map_err(|error| { return TransactionStop::Message(error.to_string()); })?;
216 if status != 0 {
217 return Err(TransactionStop::NativeExit(status));
218 }
219 let prepared = read_prepared_commit(context, workspace.landing.shadow.as_path())?;
220 debug(TAG, format!("prepared {}", prepared.oid).as_str());
221 let prepared_records: Vec<AddedPath> = selected.clone();
222 write_journal_record(
223 workspace.landing.directory.as_path(),
224 PREPARED_FILENAME,
225 encode_prepared(&PreparedRecord {
226 shadow_path: workspace.landing.shadow.display().to_string(),
227 prepared_oid: prepared.oid.clone(),
228 signed: prepared.signed,
229 intended_tree_oid: intended_tree.clone(),
230 committed_paths: committed_paths.clone(),
231 added_paths: Vec::new(),
232 selected_worktree_paths: prepared_records.clone(),
233 })
234 .as_str(),
235 RecordSync::Nothing,
237 )?;
238 reach_transaction_phase(Phase::PreparationDone, None)?;
239 let settled_events: Vec<PolicyEvent> = events_with_changed_summary(pass.events.clone(), changed_passes, changed_paths.as_slice());
240 let options: ReplayOptions = replay_options(commit_args.as_slice());
241 let outcome: LoopOutcome = land_with_replay(LoopInput {
242 invocation,
243 workspace,
244 capture,
245 mode: selection.mode,
246 request,
247 checks,
248 prepared: &prepared,
249 settled_events: settled_events.clone(),
250 options: &options,
251 global_args: global_args.as_slice(),
252 read_only_selection: selection.read_only,
253 committed_paths: committed_paths.clone(),
254 added_paths: Vec::new(),
255 worktree_records: prepared_records.clone(),
256 index_lock_timeout_ms: timeout_ms,
257 reserve_after_lost_races: invocation.config.concurrency.landing.reserve_after_lost_races,
258 })?;
259 let (oid, loop_events, final_worktree, final_added): (String, Vec<PolicyEvent>, Vec<AddedPath>, Vec<AddedPath>) =
260 match outcome {
261 LoopOutcome::Failed { events, exit_code } => {
262 return Ok(TransactionResult {
263 events,
264 exit_code,
265 landed_oid: None,
266 uses_lfs: false,
267 });
268 }
269 LoopOutcome::Landed {
270 oid,
271 events,
272 worktree_records,
273 added_paths,
274 } => (oid, events, worktree_records, added_paths),
275 };
276 let landed: LandedTransaction = LandedTransaction {
278 landed_oid: oid.clone(),
279 branch: capture.layout.target_ref.clone(),
280 cwd: cwd.to_path_buf(),
281 repository_root: repository_root.to_path_buf(),
282 directory: workspace.landing.directory.clone(),
283 shadow: workspace.landing.shadow.clone(),
284 git_dir: capture.layout.git_dir.clone(),
285 registry_root: capture.layout.registry_root.clone(),
286 added_paths: final_added,
287 worktree_records: final_worktree,
288 config_snapshot: capture.layout.config_snapshot.clone(),
289 };
290 let completed = complete_landed_transaction(PostLandingRequest {
291 context,
292 landed: &landed,
293 });
294 if let Err(error) = completed {
295 return Err(TransactionStop::Message(error.to_string()));
296 }
297 workspace.finish_transaction();
300 workspace.mark_disposed();
301 let mut events: Vec<PolicyEvent> = settled_events;
302 events.extend(loop_events);
303 return Ok(TransactionResult {
304 events,
305 exit_code: 0,
306 landed_oid: Some(oid),
307 uses_lfs: capture.layout.config_snapshot.as_ref().is_none_or(uses_lfs),
308 });
309}
310
311#[cfg(test)]
313#[path = "commit_conclusion_tests.rs"]
314mod tests;