git_policy_cli/action.rs
1//! What:
2//! The decision one wrapper invocation ends in.
3//! Why:
4//! Forwarding replaces the process and cannot run inside a unit test;
5//! returning
6//! the decision as data lets every path be checked directly.
7//!
8//! In TS you'd write (pseudocode):
9//! ```ts
10//! // type Action = { kind: 'forward'; ... } | { kind: 'exit'; code: number; stdout: string; stderr: string };
11//! ```
12
13/// What:
14/// `OsString` is owned operating-system text of raw OS bytes (sibling `String`
15/// must be UTF-8).
16/// Why:
17/// Environment additions are passed to Git without decoding.
18///
19/// In TS you'd write (pseudocode):
20/// ```ts
21/// // [string, string][] environment pairs, but byte-preserving.
22/// ```
23use std::ffi::OsString;
24/// `PathBuf` is an owned filesystem path of raw OS bytes.
25use super::index_writer_forward::CoordinatedForward;
26use super::worktree_copy_forward::CopyFacts;
27use std::path::PathBuf;
28
29/// What:
30/// Exit code for a usage,
31/// configuration or wrapper failure that kept Git from running.
32/// `i32` is a signed 32-bit integer,
33/// the type of process exit codes.
34/// Why:
35/// The wrapper's contract reserves 2 for these,
36/// distinct from 1 for policy findings.
37///
38/// In TS you'd write (pseudocode):
39/// ```ts
40/// const ENGINE_FAILURE_EXIT_CODE = 2;
41/// ```
42pub const ENGINE_FAILURE_EXIT_CODE: i32 = 2;
43
44/// What:
45/// What a plain push that may be integrated needs to run and, when it fails on a divergence, to merge.
46/// Why:
47/// The run keeps the caller's streams like the settled one does, so the integration step starts from Git's status
48/// and the same arguments, environment and (inside a linked worktree) settlement as an unstacked push would have had.
49///
50/// In TS you'd write (pseudocode):
51/// ```ts
52/// type PushForward = { realGit: string; args: string[]; overlay: [string, string][]; copy?: CopyFacts; subcommandIndex: number; environment: [string, string][] };
53/// ```
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct PushForward {
56 /// The selected real Git executable.
57 pub real_git: PathBuf,
58 /// The arguments Git receives, global options included.
59 pub arguments: Vec<OsString>,
60 /// Variables added to the inherited environment.
61 pub overlay: Vec<(OsString, OsString)>,
62 /// The linked-worktree settlement the run keeps when one applies.
63 pub copy: Option<Box<CopyFacts>>,
64 /// Index of `push` in `arguments`.
65 pub subcommand_index: usize,
66 /// The invocation's environment, which the integration step starts Git with.
67 pub environment: Vec<(OsString, OsString)>,
68 /// Whether this push must upload LFS objects first, by a `git-lfs push` the parent runs before the push.
69 /// Only the push itself is preceded by the upload; the integration step's dry-run queries are not.
70 pub lfs: bool,
71 /// Whether a push that fails on a divergence is integrated and pushed again.
72 /// A push that only needs the LFS upload explained keeps its own status.
73 pub stack: bool,
74}
75
76/// What:
77/// What the executable must do for one invocation.
78/// `Vec<OsString>` is an owned argument list;
79/// `Vec<(OsString, OsString)>` is an
80/// owned list of name/value pairs;
81/// `String` is owned UTF-8 text.
82/// Why:
83/// The executable performs exactly one of these:
84/// become Git,
85/// run Git and then add text,
86/// or print and exit.
87///
88/// In TS you'd write (pseudocode):
89/// ```ts
90/// type Action = { kind: 'forward'; realGit: string; args: string[]; overlay: [string, string][]; stderr: string }
91/// | { kind: 'exit'; code: number; stdout: string; stderr: string };
92/// ```
93#[derive(Clone, Debug, Eq, PartialEq)]
94pub enum Action {
95 /// Become real Git.
96 Forward {
97 /// The selected real Git executable.
98 real_git: PathBuf,
99 /// The arguments Git receives:
100 /// no wrapper control,
101 /// fixed transforms applied.
102 arguments: Vec<OsString>,
103 /// Variables added to the inherited environment.
104 overlay: Vec<(OsString, OsString)>,
105 /// Complete warning event lines,
106 /// written to standard error before Git starts.
107 stderr: String,
108 },
109 /// Forward an index writer under its landing lock:
110 /// coordinate,
111 /// run Git as a child,
112 /// release.
113 ForwardCoordinated {
114 /// What the coordinated run needs.
115 forward: Box<CoordinatedForward>,
116 /// Warning events written to standard error before the coordination starts.
117 stderr: String,
118 },
119 /// Forward a command that the linked-worktree copy settles:
120 /// run Git as a child that carries the settlement lease,
121 /// recover an interrupted copy first,
122 /// copy the ignored state into the worktrees the command created.
123 ForwardSettled {
124 /// The selected real Git executable.
125 real_git: PathBuf,
126 /// The arguments Git receives.
127 arguments: Vec<OsString>,
128 /// Variables added to the inherited environment.
129 overlay: Vec<(OsString, OsString)>,
130 /// What the copy needs to know about the invocation.
131 copy: Box<CopyFacts>,
132 /// Warning events written to standard error before Git starts.
133 stderr: String,
134 },
135 /// Forward a plain push as a child, so a push that lost a race to another session can be integrated:
136 /// run Git with the caller's streams, then merge in what landed on the remote and publish the merge.
137 ForwardPush {
138 /// What the run and the integration need.
139 forward: Box<PushForward>,
140 /// Warning events written to standard error before Git starts.
141 stderr: String,
142 },
143 /// Run Git as a child for a top-level `--version`, `-v`, `--help` or `-h`,
144 /// and print the announcement after Git's own answer when Git exited with status 0.
145 ForwardAnnounced {
146 /// The selected real Git executable.
147 real_git: PathBuf,
148 /// The arguments Git receives.
149 arguments: Vec<OsString>,
150 /// Variables added to the inherited environment.
151 overlay: Vec<(OsString, OsString)>,
152 /// Complete text for standard output after Git's answer,
153 /// already line-terminated.
154 announcement: String,
155 /// Warning events written to standard error before Git starts.
156 stderr: String,
157 },
158 /// Stop without running the caller's command.
159 Exit {
160 /// Process exit code.
161 code: i32,
162 /// Complete text for standard output,
163 /// already line-terminated.
164 stdout: String,
165 /// Complete text for standard error,
166 /// already line-terminated.
167 stderr: String,
168 },
169}
170
171/// What:
172/// Build the stop action for a wrapper failure described in prose.
173/// `&str` borrows the message;
174/// the action owns its formatted copy.
175/// Why:
176/// These failures (no real Git,
177/// a forwarding loop,
178/// an unreadable repository)
179/// print one prefixed,
180/// line-terminated diagnostic on standard error and exit 2.
181///
182/// In TS you'd write (pseudocode):
183/// ```ts
184/// function failure(message: string): Action;
185/// ```
186pub fn failure(message: &str) -> Action {
187 return Action::Exit {
188 code: ENGINE_FAILURE_EXIT_CODE,
189 // `String::new()` is empty owned text: nothing goes to standard output.
190 stdout: String::new(),
191 // `format!` builds the owned, line-terminated text.
192 stderr: format!("cli-git: {message}\n"),
193 };
194}