Skip to main content

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    /// The wrapper executable the Git LFS pre-push hook names, when this push must upload LFS objects first.
69    ///  Only the push itself carries the hook; the integration step's dry-run queries do not.
70    pub lfs_pre_push: Option<PathBuf>,
71}
72
73/// What:
74///  What the executable must do for one invocation.
75///       `Vec<OsString>` is an owned argument list;
76///  `Vec<(OsString, OsString)>` is an
77///       owned list of name/value pairs;
78///  `String` is owned UTF-8 text.
79/// Why:
80///   The executable performs exactly one of these:
81///  become Git,
82///  or print and exit.
83///
84/// In TS you'd write (pseudocode):
85/// ```ts
86/// type Action = { kind: 'forward'; realGit: string; args: string[]; overlay: [string, string][]; stderr: string }
87///   | { kind: 'exit'; code: number; stdout: string; stderr: string };
88/// ```
89#[derive(Clone, Debug, Eq, PartialEq)]
90pub enum Action {
91    /// Become real Git.
92    Forward {
93        /// The selected real Git executable.
94        real_git: PathBuf,
95        /// The arguments Git receives:
96        ///  no wrapper control,
97        ///  fixed transforms applied.
98        arguments: Vec<OsString>,
99        /// Variables added to the inherited environment.
100        overlay: Vec<(OsString, OsString)>,
101        /// Complete warning event lines,
102        ///  written to standard error before Git starts.
103        stderr: String,
104    },
105    /// Forward an index writer under its landing lock:
106    ///  coordinate,
107    ///  run Git as a child,
108    ///  release.
109    ForwardCoordinated {
110        /// What the coordinated run needs.
111        forward: Box<CoordinatedForward>,
112        /// Warning events written to standard error before the coordination starts.
113        stderr: String,
114    },
115    /// Forward a command that the linked-worktree copy settles:
116    ///  run Git as a child that carries the settlement lease,
117    ///  recover an interrupted copy first,
118    ///  copy the ignored state into the worktrees the command created.
119    ForwardSettled {
120        /// The selected real Git executable.
121        real_git: PathBuf,
122        /// The arguments Git receives.
123        arguments: Vec<OsString>,
124        /// Variables added to the inherited environment.
125        overlay: Vec<(OsString, OsString)>,
126        /// What the copy needs to know about the invocation.
127        copy: Box<CopyFacts>,
128        /// Warning events written to standard error before Git starts.
129        stderr: String,
130    },
131    /// Forward a plain push as a child, so a push that lost a race to another session can be integrated:
132    ///  run Git with the caller's streams, then merge in what landed on the remote and publish the merge.
133    ForwardPush {
134        /// What the run and the integration need.
135        forward: Box<PushForward>,
136        /// Warning events written to standard error before Git starts.
137        stderr: String,
138    },
139    /// Stop without running the caller's command.
140    Exit {
141        /// Process exit code.
142        code: i32,
143        /// Complete text for standard output,
144        ///  already line-terminated.
145        stdout: String,
146        /// Complete text for standard error,
147        ///  already line-terminated.
148        stderr: String,
149    },
150}
151
152/// What:
153///  Build the stop action for a wrapper failure described in prose.
154///       `&str` borrows the message;
155///  the action owns its formatted copy.
156/// Why:
157///   These failures (no real Git,
158///  a forwarding loop,
159///  an unreadable repository)
160///       print one prefixed,
161///  line-terminated diagnostic on standard error and exit 2.
162///
163/// In TS you'd write (pseudocode):
164/// ```ts
165/// function failure(message: string): Action;
166/// ```
167pub fn failure(message: &str) -> Action {
168    return Action::Exit {
169        code: ENGINE_FAILURE_EXIT_CODE,
170        // `String::new()` is empty owned text: nothing goes to standard output.
171        stdout: String::new(),
172        // `format!` builds the owned, line-terminated text.
173        stderr: format!("cli-git: {message}\n"),
174    };
175}