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
//! What:
//! One policy-proposed patch applied to the private commit index with `git apply --cached --3way`.
//! Why:
//! Corrections land in the private index, never the real one.
//! A patch that no longer applies is a `patch-conflict` for the transaction, not a silent skip.
//! The incumbent applies the same command in `applyPrivatePatch`
//! (`src/policy-engine/commit-transaction-git.ts`).
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! await applyPrivatePatch({ workspace, gitPath, cwd, patch, candidateRevision, ordinal });
//! ```
use super::policy_patch_validate::{PatchCheck, validate_policy_patch};
use super::private_storage::write_exclusive_synced;
use super::transaction_git::{GitContext, GitOutput, GitRequest, run_git};
use std::path::{Path, PathBuf};
/// What:
/// A policy patch proposal for one ordinary text path.
/// Why:
/// The path and bytes are exactly what the policy proposed, and are validated before Git runs.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type PolicyPatch = { kind: 'git-unified'; targetId: string; path: string; bytes: Uint8Array };
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PolicyPatch {
/// Exact candidate target ID the patch corrects.
pub target_id: String,
/// Repository path the patch names.
pub path: String,
/// Unified patch bytes.
pub bytes: Vec<u8>,
}
/// What:
/// Why a policy patch was not applied.
/// Why:
/// The convergence loop maps `Rejected` to a grammar failure and `Conflict` to `patch-conflict`.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// class PatchApplicationError extends Error {}
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PatchError {
/// The patch broke the supported grammar; Git never saw it.
Rejected(String),
/// Git refused the patch against the private index.
Conflict(String),
/// The patch file or the Git process failed.
Failed(String),
}
/// What:
/// The text of a patch failure.
/// Why:
/// Diagnostics print the reason unchanged.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// error.message;
/// ```
impl std::fmt::Display for PatchError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message: &String = match self {
Self::Rejected(message) | Self::Conflict(message) | Self::Failed(message) => message,
};
return write!(formatter, "{message}");
}
}
/// What:
/// Inputs of one private patch application.
/// Why:
/// One object keeps the Git context, the private index and the patch separate at the call site.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type PrivatePatchInput = { context; cwd; index; objectDirectory; directory; patch; expectedRevision; ordinal };
/// ```
pub struct PrivatePatchInput<'a> {
/// Real Git executable and the invocation's overlay.
pub context: &'a GitContext,
/// Effective repository directory the patch is applied from.
pub cwd: &'a Path,
/// Private commit index receiving the correction.
pub index: &'a Path,
/// Object store receiving written blobs; the real store when absent.
pub object_directory: Option<&'a Path>,
/// Private transaction directory holding the patch file.
pub directory: &'a Path,
/// Patch to apply.
pub patch: &'a PolicyPatch,
/// Exact candidate blob revision the patch's index line names as its base.
pub expected_revision: &'a str,
/// Position of this patch in the pass, which names its patch file.
pub ordinal: u32,
}
/// What:
/// Validate a policy patch, write it privately, and apply it to the private index.
/// Why:
/// The patch file is created exclusively with private permissions, so a re-run never overwrites an earlier one.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// validatePolicyPatch({ patch, expectedRevision });
/// await runTransactionGit({ args: ['apply', '--cached', '--3way', patchPath], indexPath });
/// ```
pub fn apply_private_patch(input: PrivatePatchInput<'_>) -> Result<(), PatchError> {
validate_policy_patch(PatchCheck {
patch: input.patch,
expected_revision: input.expected_revision,
})?;
let patch_path: PathBuf = input.directory.join(format!("patch-{}.diff", input.ordinal));
if let Err(error) = write_exclusive_synced(patch_path.as_path(), input.patch.bytes.as_slice()) {
return Err(PatchError::Failed(format!("writing {}: {error}", patch_path.display())));
}
let mut request: GitRequest = GitRequest::new(input.cwd, &["apply", "--cached", "--3way"]);
request.arguments.push(patch_path.as_os_str().to_owned());
request.without_prefix = true;
request.index = Some(input.index.to_path_buf());
request.object_directory = input.object_directory.map(Path::to_path_buf);
let output: GitOutput = match run_git(input.context, &request) {
Ok(found) => found,
Err(error) => return Err(PatchError::Failed(format!("starting git apply: {error}"))),
};
if output.succeeded() {
return Ok(());
}
return Err(PatchError::Conflict(output.error_text()));
}
#[cfg(test)]
#[path = "policy_patch_tests.rs"]
mod tests;