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;