1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
//! What:
//! The commit transaction in the incumbent's order, up to the settled policies:
//! layout capture, the published workspace, the preparation base, `preparing.json`, the shadow repository, the
//! private index under capture order, interactive selection, and the bounded policy convergence.
//! Why:
//! The incumbent (`src/policy-engine/commit-transaction.ts`) captures before it mutates, publishes the directory
//! before the base is read, and takes the landing lock only inside the landing attempt, never through preparation:
//! holding it here would serialize concurrent commits.
//! The conclusion (`commit_conclusion.rs`) takes the settled state to a landed commit.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! const result = await runCommitTransaction({ args, gitPath, policyOptions, concurrency });
//! ```
use super::candidate_object::{ObjectId, parse_object_id};
use super::candidate_prediction::CandidateRequest;
use super::capture_invocation::{
InvocationCapture, InvocationLayout, base_revision, capture_invocation_base, capture_invocation_layout,
};
use super::capture_order::{CaptureIndexes, CaptureOrderError, capture_commit_content};
use super::capture_store::read_next_capture_sequence;
use super::command_commit::CommitRegion;
use super::command_options::OptionValue;
use super::command_options_query::value_bytes;
use super::commit_conclusion::{ConclusionInput, conclude_commit_transaction};
use super::commit_convergence::{CommitConvergence, CommitConvergenceInput, converge_commit_policies};
use super::commit_facts::PrivateIndexFacts;
use super::commit_index::{
IndexSelection, initialize_commit_index, list_changed_index_paths, list_unmerged_index_paths,
materialize_pathspec_file, prepare_interactive_selection,
};
use super::commit_workspace::{CommitWorkspace, WorkspaceError, create_commit_workspace};
use super::config_schema::CliGitConfig;
use super::diagnostic_log::debug;
use super::diagnostics::EngineFailureCode;
use super::landing_attempt::LandingError;
use super::markdown_linter::executable_linter;
use super::policy_checks::{ShippedChecks, shipped_checks};
use super::policy_forbidden_strings::default_scanner_settings;
use super::policy_markdown::markdown_state;
use super::policy_content::LifecycleContent;
use super::policy_convergence::Convergence;
use super::policy_engine::{StageRequest, pass_exit_code};
use super::policy_events::PolicyEvent;
use super::policy_pass::{PassResult, run_policy_pass};
use super::policy_trigger::Trigger;
use super::private_storage::{sync_directory, write_private_file};
use super::repository_facts::RepositoryFacts;
use super::transaction_git::{GitContext, GitFailure};
use super::transaction_journal::{Base, Conclusion, PREPARING_FILENAME, PreparingRecord, TransactionMode};
use super::transaction_journal_encode::encode_preparing;
use super::transaction_phase::PhaseMarkerError;
use super::wrapper_controls::Controls;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
/// Module tag for diagnostics.
const TAG: &str = "runCommitTransaction";
/// What:
/// Everything the invocation fixes before the transaction starts.
/// Why:
/// The steps share one identity, one parsed commit region and one configuration; passing them together keeps a
/// step from reading another invocation's state.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type CommitInvocation = { args; gitPath; cwd; region; policyOptions; concurrency };
/// ```
pub struct CommitInvocation<'a> {
/// Real Git, the child environment overlay, and the invocation's global options.
pub context: &'a GitContext,
/// The invocation's environment, read for leases and inherited configuration.
pub environment: &'a [(OsString, OsString)],
/// The effective invocation directory (the process directory moved by every `-C`).
pub cwd: &'a Path,
/// The arguments with the wrapper's controls removed.
pub arguments: &'a [OsString],
/// The index of `commit` within `arguments`.
pub subcommand_index: usize,
/// The commit options Git's table read.
pub region: &'a CommitRegion,
/// The configuration of the worktree.
pub config: &'a CliGitConfig,
/// The wrapper controls given on the command line.
pub controls: &'a Controls,
/// Whether this invocation runs under a valid outer preparation lease.
pub inherited_lease_valid: bool,
/// This executable, which the hook entries link to.
pub executable: &'a Path,
}
/// What:
/// Why the transaction stopped without a policy result.
/// Why:
/// The incumbent's boundary maps each to a different ending: Git's own exit code with no wrapper text, a message
/// with exit status 1, a finding with exit status 2, or a verbatim stop text.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// NativeCommitFailedError | CommitTransactionGitError | IndexLockUnprovenOwnerError | Error
/// ```
#[derive(Debug)]
pub enum TransactionStop {
/// Native `git commit` exited nonzero during preparation: its status is the invocation's, and Git printed why.
NativeExit(i32),
/// A Git step failed: the message reaches standard error and the status is 1.
Message(String),
/// A transaction failure reported as an engine failure event with status 2.
Failed {
/// The failure's stable code.
code: EngineFailureCode,
/// What went wrong.
message: String,
},
}
impl From<GitFailure> for TransactionStop {
/// A Git step failure keeps its message and ends the command with status 1.
fn from(failure: GitFailure) -> Self {
return TransactionStop::Message(failure.0);
}
}
impl From<WorkspaceError> for TransactionStop {
/// An unpublishable workspace fails the transaction.
fn from(failure: WorkspaceError) -> Self {
return failed(failure.to_string());
}
}
impl From<CaptureOrderError> for TransactionStop {
/// A capture failure fails the transaction.
fn from(failure: CaptureOrderError) -> Self {
return match failure {
CaptureOrderError::Git(git) => TransactionStop::Message(git.0),
other => failed(other.to_string()),
};
}
}
impl From<PhaseMarkerError> for TransactionStop {
/// A malformed test marker fails the transaction.
fn from(failure: PhaseMarkerError) -> Self {
return failed(failure.to_string());
}
}
impl From<LandingError> for TransactionStop {
/// A failed landing attempt: an unproven lock owner has its own code, the rest are transaction failures.
fn from(failure: LandingError) -> Self {
return match failure {
LandingError::Lock(super::index_writer_coordination::CoordinationFailure::UnprovenOwner(text)) => {
TransactionStop::Failed {
code: EngineFailureCode::IndexLockUnprovenOwner,
message: text,
}
}
LandingError::Git(git) => TransactionStop::Message(git.0),
other => failed(other.to_string()),
};
}
}
/// What:
/// A transaction failure with the generic code.
/// Why:
/// Most unexpected failures are reported the same way.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// initialTransactionFailure({ args, message })
/// ```
pub fn failed(message: String) -> TransactionStop {
return TransactionStop::Failed {
code: EngineFailureCode::TransactionFailed,
message,
};
}
/// What:
/// What the transaction decided.
/// Why:
/// The command prints the events, runs the post-commit lifecycle for a landed commit, and exits with the code.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type CommitTransactionResult = { policyResult: PolicyEngineResult; committed: boolean; landedOid?: string };
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TransactionResult {
/// The events the invocation reports, in order.
pub events: Vec<PolicyEvent>,
/// The exit status when nothing landed; 0 after a landed commit.
pub exit_code: i32,
/// The landed commit, when the transaction landed one.
pub landed_oid: Option<String>,
}
/// What:
/// The transaction directory's names, derived from the registry root and the transaction ID.
/// Why:
/// Callers that build facts before the transaction creates anything use the same names.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// transactionPaths({ registryRoot, commonDir, transactionId });
/// ```
pub fn transaction_directory(registry_root: &Path, transaction_id: &str) -> PathBuf {
return registry_root.join(transaction_id);
}
/// What:
/// Write one journal record durably: exclusively, then the directory entry.
/// Why:
/// A state file is never rewritten, so a crash leaves the newest complete state readable.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// await writeJournalRecord({ directory, filename, record });
/// ```
pub fn write_journal_record(directory: &Path, filename: &str, text: &str) -> Result<(), TransactionStop> {
let path: PathBuf = directory.join(filename);
write_private_file(path.as_path(), text.as_bytes())
.map_err(|error| { return failed(format!("writing {} failed: {error}", path.display())); })?;
sync_directory(directory)
.map_err(|error| { return failed(format!("syncing {} failed: {error}", directory.display())); })?;
return Ok(());
}
/// What:
/// The commit-region facts the transaction decides from, derived once.
/// Why:
/// The mode and the selection flags are read in several steps.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const { concludesSequencer, readOnlySelection, selectsPaths, stagedAll, mode } = ...;
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Selection {
/// Whether a pathless commit concludes a merge, cherry-pick or revert.
pub concludes_sequencer: bool,
/// Whether the selection UI (include, interactive, patch) leaves the policies read-only.
pub read_only: bool,
/// Whether any path was selected.
pub selects_paths: bool,
/// Whether `-a` stages privately.
pub staged_all: bool,
/// The private index mode.
pub mode: TransactionMode,
}
/// What:
/// Decide the selection facts of one commit invocation.
/// Why:
/// Invalid combinations reach native Git unchanged, so it rejects them.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const mode = region.hasNoOnlyFlag || concludesSequencer || readOnlySelection || region.hasAllFlag || (!selectsPaths) ? 'index' : 'explicit-path';
/// ```
pub fn decide_selection(region: &CommitRegion, conclusion: Conclusion) -> Selection {
let concludes_sequencer: bool = conclusion != Conclusion::None && conclusion != Conclusion::Amend;
let read_only: bool = region.include || region.interactive || region.patch;
let selects_paths: bool = !region.pathspecs.is_empty() || region.pathspec_from_file.is_some();
let staged_all: bool = region.all && !selects_paths && !read_only && region.only != Some(true);
let index_mode: bool = region.only == Some(false)
|| concludes_sequencer
|| read_only
|| region.all
|| !selects_paths;
return Selection {
concludes_sequencer,
read_only,
selects_paths,
staged_all,
mode: if index_mode { TransactionMode::Index } else { TransactionMode::ExplicitPath },
};
}
/// What:
/// The positional pathspec tokens of the commit region as text.
/// Why:
/// The private index selection and the private commit arguments name them.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// region.pathspecs
/// ```
fn pathspec_texts(invocation: &CommitInvocation<'_>) -> Vec<String> {
let region_arguments: &[OsString] = &invocation.arguments[invocation.subcommand_index + 1..];
return invocation
.region
.pathspecs
.iter()
.map(|index| { return region_arguments[*index].to_string_lossy().into_owned(); })
.collect();
}
/// What:
/// The pathspec-file source the commit names, as text.
/// Why:
/// Standard input is copied once into the transaction so no second read sees different bytes.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// region.pathspecFile
/// ```
fn pathspec_file_source(invocation: &CommitInvocation<'_>) -> Option<String> {
let region_arguments: &[OsString] = &invocation.arguments[invocation.subcommand_index + 1..];
let value: OptionValue = invocation.region.pathspec_from_file?;
return Some(String::from_utf8_lossy(value_bytes(region_arguments, value)).into_owned());
}
/// What:
/// The prepared state a converged commit hands to its conclusion.
/// Why:
/// The conclusion needs the pass that settled and the paths the commit carries.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type SettledCommitTransaction = { workspace; capture; mode; pass; changedPasses; changedPaths; ... };
/// ```
struct Converged {
/// The settled pass, whose events the invocation reports.
pass: PassResult,
/// How many passes changed content before it settled.
changed_passes: u64,
/// The paths the corrections wrote.
changed_paths: Vec<String>,
}
/// What:
/// Rebuild the shipped checks over the private commit index: rule facts stay real, the candidates come from the
/// private index against the recorded base.
/// Why:
/// The incumbent's rules ask real Git about the real index, while the content policies read what the transaction
/// corrects.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const facts = createPrivateIndexFacts({ gitPath, cwd, indexPath, paths, baseRevision, objectDirectory });
/// ```
pub fn private_checks<'a, F: RepositoryFacts>(
checks: &'a mut ShippedChecks<F>,
index: &Path,
object_directory: &Path,
candidates: CandidateRequest,
) -> ShippedChecks<PrivateIndexFacts<&'a mut F>> {
// The scanner settings and the Markdown linter move to the rebuilt checks; the originals are placeholders that
// nothing reads again, because the rebuilt checks run every policy of this transaction.
let scanner_settings = std::mem::replace(&mut checks.scanner_settings, default_scanner_settings());
let markdown = std::mem::replace(&mut checks.markdown, markdown_state(Box::new(executable_linter(&[]))));
let arguments: Vec<OsString> = checks.arguments.clone();
let allowed_worktree_dirs: Vec<PathBuf> = checks.allowed_worktree_dirs.clone();
let mut rebuilt: ShippedChecks<PrivateIndexFacts<&'a mut F>> = shipped_checks(
PrivateIndexFacts::new(&mut checks.facts, index, object_directory),
arguments,
LifecycleContent::Requested(candidates),
allowed_worktree_dirs,
);
rebuilt.scanner_settings = scanner_settings;
rebuilt.markdown = markdown;
return rebuilt;
}
/// What:
/// Whether the capture lists worktree changes that the commit stages: `-a` stages every tracked change and
/// `--include` stages the named paths.
/// Why:
/// The capture record names those paths, so recovery copies exactly the worktree files the commit staged.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// stagedAll || include
/// ```
pub fn stages_worktree(staged_all: bool, include: bool) -> bool {
return staged_all || include;
}
/// What:
/// How many passes changed the state when the convergence settled; any other ending counts none.
/// Why:
/// The fix summary names the passes of a settled convergence only.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// convergence.kind === 'settled' ? convergence.changedPasses : 0
/// ```
pub fn settled_passes(convergence: &Convergence) -> u64 {
return match convergence {
Convergence::Settled { changed_passes } => *changed_passes as u64,
_ => 0,
};
}
/// What:
/// The candidate request for the paths a private index changes against the recorded base.
/// Why:
/// The policies check exactly the paths the commit changes; each path is literal and rooted at the top level, so a
/// name with glob characters or a directory below the current one still names one file.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// createPrivateIndexFacts({ paths: candidatePaths, baseRevision })
/// ```
pub fn staged_at_base(base: Option<ObjectId>, paths: &[String]) -> CandidateRequest {
return CandidateRequest::StagedAtBase {
base,
pathspecs: paths.iter().map(|path| { return OsString::from(format!(":(top,literal){path}")); }).collect(),
};
}
/// What:
/// The recorded base as an object ID, or nothing for an unborn branch.
/// Why:
/// The candidate listing compares the private index with this commit.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// capture.base.kind === 'commit' ? capture.base.oid : undefined
/// ```
pub fn base_object(capture: &InvocationCapture) -> Result<Option<ObjectId>, TransactionStop> {
return match &capture.base {
Base::Unborn => Ok(None),
Base::Commit(oid) => match parse_object_id(oid.as_bytes()) {
Some(found) => Ok(Some(found)),
None => Err(failed(format!("the preparation base {oid} is not an object name"))),
},
};
}
/// What:
/// Run the pre-forward policies over the private index until they settle.
/// Why:
/// A read-only selection commits exactly what the person chose, so its single pass applies no patch.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// readOnlySelection ? { kind: 'settled', pass: firstPass } : await convergeCommitPolicies({ ... });
/// ```
fn converge<G: RepositoryFacts>(
request: &StageRequest,
checks: &mut ShippedChecks<PrivateIndexFacts<G>>,
context: &GitContext,
cwd: &Path,
workspace: &CommitWorkspace,
read_only: bool,
) -> Converged {
if read_only {
checks.can_apply_patches = false;
let pass: PassResult = run_policy_pass(request, checks);
return Converged {
pass,
changed_passes: 0,
changed_paths: Vec::new(),
};
}
let outcome: CommitConvergence = converge_commit_policies(CommitConvergenceInput {
request,
checks,
stage: super::private_index_stage::PrivateStage {
context,
cwd,
index: workspace.landing.commit_index.as_path(),
object_directory: Some(workspace.object_directory.as_path()),
},
});
let changed_passes: u64 = settled_passes(&outcome.convergence);
return Converged {
pass: outcome.pass,
changed_passes,
changed_paths: outcome.changed_paths,
};
}
/// What:
/// Run one commit through the transaction: capture, private preparation, serial landing and post-landing completion.
/// Why:
/// Every non-dry-run commit runs through it, including clean commits and `commit -a`, because native `git commit`
/// keeps `index.lock` on disk through hooks and the editor.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// return await runCommitTransaction({ args, gitPath, policyOptions, concurrency });
/// ```
pub fn run_commit_transaction<F: RepositoryFacts>(
invocation: &CommitInvocation<'_>,
checks: &mut ShippedChecks<F>,
request: &StageRequest,
) -> Result<TransactionResult, TransactionStop> {
let context: &GitContext = invocation.context;
let cwd: &Path = invocation.cwd;
let layout: InvocationLayout = capture_invocation_layout(context, cwd, invocation.region.amend)?;
let selection: Selection = decide_selection(invocation.region, layout.conclusion);
let mut workspace: CommitWorkspace = create_commit_workspace(&layout)?;
// Read after publication and before the base, so landed-capture pruning keeps every record this transaction
// may replay over.
let next_sequence_before_base: i64 = read_next_capture_sequence(layout.git_dir.as_path())
.map_err(|error| { return failed(error.0); })?;
let capture: InvocationCapture = capture_invocation_base(context, cwd, layout)?;
let base: String = base_revision(&capture);
let pathspecs: Vec<String> = pathspec_texts(invocation);
write_journal_record(
workspace.landing.directory.as_path(),
PREPARING_FILENAME,
encode_preparing(&PreparingRecord {
transaction_id: workspace.landing.transaction_id.clone(),
mode: selection.mode,
base: capture.base.clone(),
symbolic_head: capture.layout.symbolic_head.clone(),
target_ref: capture.layout.target_ref.clone(),
conclusion: capture.layout.conclusion,
repository_root: capture.layout.repository_root.display().to_string(),
git_dir: capture.layout.git_dir.display().to_string(),
common_dir: capture.layout.common_dir.display().to_string(),
real_index_path: capture.layout.real_index_path.display().to_string(),
object_directory: capture.layout.object_directory.display().to_string(),
ref_format: capture.layout.ref_format,
empty_tree_oid: capture.layout.empty_tree_oid.clone(),
shadow_path: workspace.landing.shadow.display().to_string(),
selected_pathspecs: pathspecs.clone(),
invoked_at: capture.layout.invoked_at.clone(),
})
.as_str(),
)?;
// Before the private index exists, so every object preparation writes lands in the shadow store, which a
// concurrent real `gc --prune=now` never sees.
super::shadow_create::create_shadow_repository(
context,
cwd,
&capture,
workspace.landing.transaction_id.as_str(),
workspace.landing.directory.as_path(),
)?;
let pathspec_file: Option<PathBuf> = materialize_pathspec_file(
workspace.landing.directory.as_path(),
cwd,
pathspec_file_source(invocation).as_deref(),
&mut std::io::stdin(),
)
.map_err(|error| { return failed(format!("reading the pathspec file failed: {error}")); })?;
let indexes: CaptureIndexes = CaptureIndexes {
captured_index: workspace.landing.captured_index.clone(),
commit_index: workspace.landing.commit_index.clone(),
object_directory: workspace.object_directory.clone(),
};
let index_selection: IndexSelection<'_> = IndexSelection {
mode: selection.mode,
pathspecs: pathspecs.as_slice(),
pathspec_file: pathspec_file.as_deref(),
pathspec_file_nul: invocation.region.pathspec_file_nul,
stage_into_index: invocation.region.include,
stage_tracked_changes: selection.staged_all,
base_revision: base.as_str(),
};
capture_commit_content(
context,
cwd,
capture.layout.git_dir.as_path(),
workspace.landing.directory.as_path(),
&indexes,
selection.mode,
stages_worktree(selection.staged_all, invocation.region.include),
base.as_str(),
next_sequence_before_base,
|| {
return initialize_commit_index(
context,
cwd,
capture.layout.real_index_path.as_path(),
&indexes,
&index_selection,
)
.map_err(CaptureOrderError::from);
},
)?;
if invocation.region.interactive || invocation.region.patch {
prepare_interactive_selection(
context,
cwd,
workspace.landing.commit_index.as_path(),
workspace.object_directory.as_path(),
invocation.region.patch,
&index_selection,
)?;
}
let unmerged: Vec<String> =
list_unmerged_index_paths(context, cwd, workspace.landing.commit_index.as_path())?;
if !unmerged.is_empty() {
return Ok(TransactionResult {
events: vec![PolicyEvent::EngineFailure {
code: EngineFailureCode::ContentUnavailable,
message: format!(
"Automatic commit fixes do not support unmerged index paths: {}",
unmerged.join(", ")
),
trigger: Some(Trigger::PreForward),
policy: None,
path: None,
}],
exit_code: 2,
landed_oid: None,
});
}
let candidate_paths: Vec<String> = list_changed_index_paths(
context,
cwd,
workspace.landing.commit_index.as_path(),
base.as_str(),
workspace.object_directory.as_path(),
)?;
let mut private: ShippedChecks<PrivateIndexFacts<&mut F>> = private_checks(
checks,
workspace.landing.commit_index.as_path(),
workspace.object_directory.as_path(),
staged_at_base(base_object(&capture)?, candidate_paths.as_slice()),
);
let converged: Converged = converge(request, &mut private, context, cwd, &workspace, selection.read_only);
let code: i32 = pass_exit_code(converged.pass.events.as_slice(), converged.pass.end);
if code != 0 {
debug(TAG, "policies blocked the commit before preparation");
return Ok(TransactionResult {
events: converged.pass.events,
exit_code: code,
landed_oid: None,
});
}
return conclude_commit_transaction(ConclusionInput {
invocation,
workspace: &mut workspace,
capture: &capture,
selection: &selection,
request,
checks: &mut private,
pass: converged.pass,
changed_passes: converged.changed_passes,
changed_paths: converged.changed_paths,
committed_paths: candidate_paths,
pathspecs: pathspecs.as_slice(),
});
}
/// Controls for this module stay out of the release executable.
#[cfg(test)]
#[path = "commit_transaction_tests.rs"]
mod tests;