git_policy_cli/command_push.rs
1//! What:
2//! The complete `git push` option table of Git 2.56.0 and the facts read from it.
3//! Why:
4//! The atomic-push transform must know whether the caller already chose atomicity,
5//! and the manual-push gate whether the push is a dry run;
6//! both in every spelling Git
7//! accepts (`--at`,
8//! `--no-at`,
9//! `-nf`),
10//! which a list of exact tokens misses.
11//!
12//! In TS you'd write (pseudocode):
13//! ```ts
14//! // parsePushRegion(postSubcommandArgs): PushRegion (throws OptionError)
15//! ```
16
17/// What:
18/// Bring the tokenizer,
19/// its table builder and its questions into this file.
20/// Why:
21/// This module only declares Git's table and interprets what the tokenizer found.
22///
23/// In TS you'd write (pseudocode):
24/// ```ts
25/// import { parseOptions, row } from './command_options.ts';
26/// ```
27use super::command_options::{
28 Arity, DEFAULT_MODE, OptionError, OptionSpec, ParsedOptions, UNREAD, WrapperOccurrence,
29 parse_options, row,
30};
31use super::command_options_query::{is_enabled, is_stated};
32/// `OsString` is owned operating-system text of raw bytes (sibling `String` must be UTF-8).
33use std::ffi::OsString;
34
35/// `--atomic`:
36/// request an atomic transaction on the remote side.
37pub const ATOMIC: u16 = 1;
38/// `-n`,
39/// `--dry-run`:
40/// do everything except send the updates.
41pub const DRY_RUN: u16 = 2;
42
43/// What:
44/// Rows of `options[]` in `cmd_push` (builtin/push.c:707-743),
45/// in source order.
46/// `&[OptionSpec]` is a borrowed table baked into the program.
47/// Why:
48/// `--branches` is an `OPT_ALIAS` of `--all`;
49/// the two names share no prefix,
50/// so the
51/// alias needs no special ambiguity handling and is a row of its own.
52///
53/// In TS you'd write (pseudocode):
54/// ```ts
55/// export const PUSH_TABLE = [row(UNREAD, 'v', 'verbose', 'none', true), /* ... */] as const;
56/// ```
57pub const PUSH_TABLE: &[OptionSpec] = &[
58 row(UNREAD, Some(b'v'), Some("verbose"), Arity::None, true),
59 row(UNREAD, Some(b'q'), Some("quiet"), Arity::None, true),
60 row(UNREAD, None, Some("repo"), Arity::Required, true),
61 row(UNREAD, None, Some("all"), Arity::None, true),
62 row(UNREAD, None, Some("branches"), Arity::None, true),
63 row(UNREAD, None, Some("mirror"), Arity::None, true),
64 row(UNREAD, Some(b'd'), Some("delete"), Arity::None, true),
65 row(UNREAD, None, Some("tags"), Arity::None, true),
66 row(DRY_RUN, Some(b'n'), Some("dry-run"), Arity::None, true),
67 row(UNREAD, None, Some("porcelain"), Arity::None, true),
68 row(UNREAD, Some(b'f'), Some("force"), Arity::None, true),
69 // `PARSE_OPT_OPTARG`: only `--force-with-lease=<refname>:<expect>` carries a value.
70 row(
71 UNREAD,
72 None,
73 Some("force-with-lease"),
74 Arity::Optional,
75 true,
76 ),
77 row(UNREAD, None, Some("force-if-includes"), Arity::None, true),
78 row(
79 UNREAD,
80 None,
81 Some("recurse-submodules"),
82 Arity::Required,
83 true,
84 ),
85 row(UNREAD, None, Some("thin"), Arity::None, true),
86 row(UNREAD, None, Some("receive-pack"), Arity::Required, true),
87 row(UNREAD, None, Some("exec"), Arity::Required, true),
88 row(UNREAD, Some(b'u'), Some("set-upstream"), Arity::None, true),
89 row(UNREAD, None, Some("progress"), Arity::None, true),
90 row(UNREAD, None, Some("prune"), Arity::None, true),
91 row(UNREAD, None, Some("no-verify"), Arity::None, true),
92 row(UNREAD, None, Some("follow-tags"), Arity::None, true),
93 // `PARSE_OPT_OPTARG`: only `--signed=<mode>` carries a value.
94 row(UNREAD, None, Some("signed"), Arity::Optional, true),
95 row(ATOMIC, None, Some("atomic"), Arity::None, true),
96 row(
97 UNREAD,
98 Some(b'o'),
99 Some("push-option"),
100 Arity::Required,
101 true,
102 ),
103 // `OPT_IPVERSION` rows are `PARSE_OPT_NONEG` (parse-options.h:636-640).
104 row(UNREAD, Some(b'4'), Some("ipv4"), Arity::None, false),
105 row(UNREAD, Some(b'6'), Some("ipv6"), Arity::None, false),
106];
107
108/// What:
109/// Final-state facts of one `git push` region.
110/// `bool` is true or false;
111/// `Vec<WrapperOccurrence>` is an owned list of wrapper-flag positions.
112/// Why:
113/// `atomic_stated` is "the caller chose",
114/// in either direction;
115/// `dry_run` is the state
116/// after the last `-n`,
117/// `--dry-run` or `--no-dry-run`.
118///
119/// In TS you'd write (pseudocode):
120/// ```ts
121/// type PushRegion = { hasAtomicChoice: boolean; isDryRun: boolean };
122/// ```
123#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct PushRegion {
125 /// `--atomic` or `--no-atomic` was written,
126 /// in any accepted abbreviation.
127 pub atomic_stated: bool,
128 /// The push sends no updates.
129 pub dry_run: bool,
130 /// Wrapper-only flags in option position;
131 /// `flag` indexes the caller's list.
132 pub wrapper: Vec<WrapperOccurrence>,
133}
134
135/// What:
136/// Parse the region after `push`.
137/// `Result<A, B>` is "either success `A` or failure `B`".
138/// Why:
139/// A region Git itself would refuse yields the refusal,
140/// never a guessed fact set.
141///
142/// In TS you'd write (pseudocode):
143/// ```ts
144/// function parsePushRegion(region: string[], wrapperFlags: string[]): PushRegion; // throws
145/// ```
146pub fn parse_push_region(
147 region: &[OsString],
148 wrapper_flags: &[&[u8]],
149) -> Result<PushRegion, OptionError> {
150 // `?` returns Git's refusal to our caller, or unwraps the tokenized region.
151 let parsed: ParsedOptions = parse_options(region, PUSH_TABLE, DEFAULT_MODE, wrapper_flags)?;
152 // `Ok(x)` is the success case of `Result`.
153 return Ok(PushRegion {
154 atomic_stated: is_stated(&parsed, ATOMIC),
155 dry_run: is_enabled(&parsed, DRY_RUN),
156 wrapper: parsed.wrapper,
157 });
158}
159
160/// Facts in every accepted spelling,
161/// and real-Git controls of the table and its abbreviations.
162#[cfg(test)]
163#[path = "command_push_tests.rs"]
164mod tests;