git_policy_cli/rule_commit_index.rs
1//! What:
2//! The pure core of the index-against-`HEAD` check used by the commit-only transform.
3//! Why:
4//! The caller runs real Git;
5//! this module only says which command to run and what its
6//! exit status means,
7//! so the decision is testable without a process.
8//!
9//! In TS you'd write (pseudocode):
10//! ```ts
11//! // indexStateFromExit(exitCode): 'differs' | 'matches' | 'unknown'
12//! ```
13
14/// `OsString` is owned operating-system text of raw bytes (sibling `String` must be UTF-8).
15use std::ffi::OsString;
16
17/// What:
18/// Comparison of staged content with `HEAD`.
19/// An `enum` is a closed set of alternatives.
20/// Why:
21/// `Unknown` means Git could not answer (unborn `HEAD`,
22/// no repository);
23/// the transform
24/// then defers to real Git instead of rejecting.
25///
26/// In TS you'd write (pseudocode):
27/// ```ts
28/// type IndexVsHeadState = 'differs' | 'matches' | 'unknown';
29/// ```
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum IndexVsHead {
32 /// Staged changes exist.
33 Differs,
34 /// The index equals `HEAD`.
35 Matches,
36 /// Git could not compare.
37 Unknown,
38}
39
40/// The query whose exit status answers the question:
41/// 0 for equal,
42/// 1 for staged changes.
43pub const INDEX_QUERY: &[&str] = &["diff-index", "--quiet", "--cached", "HEAD", "--"];
44
45/// What:
46/// Build the real-Git argument list:
47/// the caller's global options,
48/// then the query.
49/// `&[OsString]` borrows the global prefix;
50/// `Vec<OsString>` is the owned result.
51/// Why:
52/// The check must inspect the same repository the commit will run in,
53/// so `-C`,
54/// `--git-dir` and the other global options are forwarded unchanged.
55///
56/// In TS you'd write (pseudocode):
57/// ```ts
58/// const args = [...preSubcommandArgs, 'diff-index', '--quiet', '--cached', 'HEAD', '--'];
59/// ```
60pub fn index_query_arguments(global_prefix: &[OsString]) -> Vec<OsString> {
61 // `.to_vec()` copies the borrowed prefix into a list this function owns.
62 let mut arguments: Vec<OsString> = global_prefix.to_vec();
63 // `for token in INDEX_QUERY` borrows each spelling; `OsString::from` copies it.
64 for token in INDEX_QUERY {
65 arguments.push(OsString::from(token));
66 }
67 return arguments;
68}
69
70/// What:
71/// Interpret the query's exit status.
72/// `Option<i32>` is "an exit code,
73/// or nothing when
74/// a signal ended the process";
75/// `i32` is the platform's exit-code integer type.
76/// Why:
77/// Only the two documented codes are answers;
78/// everything else is "Git cannot say".
79///
80/// In TS you'd write (pseudocode):
81/// ```ts
82/// return exitCode === 0 ? 'matches' : exitCode === 1 ? 'differs' : 'unknown';
83/// ```
84pub fn index_state_from_exit(exit_code: Option<i32>) -> IndexVsHead {
85 // `Some(0)` is "the process exited with code 0".
86 if exit_code == Some(0) {
87 return IndexVsHead::Matches;
88 }
89 if exit_code == Some(1) {
90 return IndexVsHead::Differs;
91 }
92 return IndexVsHead::Unknown;
93}
94
95/// Exit-status mapping and a real-Git control of the query.
96#[cfg(test)]
97#[path = "rule_commit_index_tests.rs"]
98mod tests;