Skip to main content

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;