Skip to main content

git_policy_cli/
rule_atomic_push.rs

1//! What:
2//!  The pure decision of the atomic-push transform:
3//!  insert `--atomic` after `git push`
4//!       unless the caller already chose.
5//! Why:
6//!  An atomic push updates every ref on the remote or none,
7//!  which prevents partially
8//!      applied pushes.
9//!
10//! In TS you'd write (pseudocode):
11//! ```ts
12//! // atomicPush(['push', 'origin', 'main']) => ['push', '--atomic', 'origin', 'main']
13//! ```
14
15/// What:
16///  Bring the push facts,
17///  the global-option boundary and the rewrite helpers into
18///       this file.
19/// Why:
20///   The decision combines them;
21///  it owns no parsing of its own.
22///
23/// In TS you'd write (pseudocode):
24/// ```ts
25/// import { parsePushRegion } from '../parser/push.ts';
26/// import { parseGlobalOptions } from '../parse-global-options.ts';
27/// ```
28use super::command_options::OptionError;
29use super::command_push::{PushRegion, parse_push_region};
30use super::global_arguments::{GlobalLayout, GlobalOutcome, global_layout};
31use super::rule_argument_rewrite::{ArgumentRewrite, insert_tokens};
32/// `OsString` is owned operating-system text of raw bytes (sibling `String` must be UTF-8).
33use std::ffi::OsString;
34
35/// What:
36///  Decide the transform for one command line.
37///  `Result<A, B>` is "either success `A`
38///       or failure `B`";
39///  `&[&[u8]]` borrows the caller's wrapper-only spellings.
40/// Why:
41///   The flag goes immediately after `push`,
42///  so global options before it survive.
43/// Gotcha:
44///  `Err` means Git 2.56.0 itself refuses the push options;
45///  the caller forwards such
46///         a command unchanged so Git prints its own error.
47///
48/// In TS you'd write (pseudocode):
49/// ```ts
50/// function atomicPush(args: readonly string[]): readonly string[];
51/// ```
52pub fn atomic_push(
53    arguments: &[OsString],
54    wrapper_flags: &[&[u8]],
55) -> Result<ArgumentRewrite, OptionError> {
56    let layout: GlobalLayout = global_layout(arguments);
57    if layout.outcome != GlobalOutcome::Command
58        || arguments[layout.prefix_len].as_encoded_bytes() != b"push"
59    {
60        // `Ok(x)` is the success case of `Result`.
61        return Ok(ArgumentRewrite::Unchanged);
62    }
63    // `usize` is the list index type; the region starts after the `push` token.
64    let region_start: usize = layout.prefix_len + 1;
65    // `&arguments[region_start..]` borrows the tokens after `push`; `?` returns Git's
66    // refusal to our caller, or unwraps the facts.
67    let region: PushRegion = parse_push_region(&arguments[region_start..], wrapper_flags)?;
68    if region.atomic_stated {
69        return Ok(ArgumentRewrite::Unchanged);
70    }
71    return Ok(ArgumentRewrite::Rewritten(insert_tokens(
72        arguments,
73        region_start,
74        &["--atomic"],
75    )));
76}
77
78/// Every `atomic-push.unit.test.ts` case and the spellings the incumbent missed.
79#[cfg(test)]
80#[path = "rule_atomic_push_tests.rs"]
81mod tests;