1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! What:
//! Start the real Git with every hook switched off.
//! Why:
//! The wrapper supports no external hook program: not a traditional hook (an executable file in
//! `<git-common-dir>/hooks` or a `core.hooksPath` directory; `git lfs` writes its four shims there again whenever it
//! cleans a file) and not a hook defined in the configuration (`hook.<name>.event` with `hook.<name>.command`).
//! Every real-Git start of the wrapper therefore carries `-c core.hooksPath=<a directory that never exists>`, which
//! finds no hook file, and `-c hook.<event>.enabled=false` for every hook event Git knows, which switches every
//! configured hook off whatever its name.
//! `core.fsmonitor` is left as configured: it is a program Git starts for its own feature, and the event switches
//! do not reach it (measured on Git 2.56.0, doc/planning/cli-git-no-self-invocation-dispatcher.md).
//! They follow every global option of the caller and precede the subcommand, so no `-c` or `--config-env` of the caller
//! can switch a hook or the hooks path back on.
//! The options are part of the argument vector, so they add no system call and no process start.
//! The wrapper injects no hook of its own, so `pre-push` is switched off like every other event.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! spawn(realGit, ['-c', `core.hooksPath=${NO_HOOKS_DIRECTORY}`, ...HOOK_EVENTS.flatMap((e) => ['-c', `hook.${e}.enabled=false`]), ...args]);
//! ```
/// The global-option parser, to find the subcommand behind the caller's global options.
use crate;
/// Owned operating-system text for the arguments.
use OsString;
/// The switch that turns the option on for the process.
use ;
/// The child that gets the option.
use Command;
/// The `core.hooksPath` value of every start: below `/dev/null`, which is a device, so nothing can create it.
pub const NO_HOOKS_DIRECTORY: &str = "/dev/null/cli-git-no-hooks";
/// Every hook event of Git 2.56.0 (`githooks(5)`), server-side events included.
/// A configured hook for an event missing here would run, so the control `every_listed_event_is_a_hook_event_of_real_git`
/// asks the host Git whether each name is one.
pub const HOOK_EVENTS: & = &;
/// The events a real-Git start of the wrapper switches off: every hook event Git knows.
/// What:
/// Whether `arguments` run `git config` to report or change configuration, without opening an editor.
/// Why:
/// A configuration query reports what the repository configures, and the wrapper itself reads `core.hooksPath` that
/// way; the injected value must not answer for it.
/// A query starts no program, so leaving the options out costs no protection.
/// `--edit` (or `-e`) starts the editor, and a Git the editor starts inherits the options through
/// `GIT_CONFIG_PARAMETERS`, so it gets them.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// layout.outcome === 'command' && args[layout.prefixLen] === 'config' && !opensEditor(args.slice(layout.prefixLen + 1))
/// ```
/// Whether this process starts real Git with hooks off; the executable switches it on before any start.
static ACTIVE: AtomicBool = new;
/// What:
/// Switch the hooks-off option on for every real-Git start this process makes from now on.
/// Why:
/// The executable's entry calls it first, so no production start misses it.
/// The library's unit controls, which fake Git with scripts that record the exact arguments they receive, leave it
/// off, and the binary-level controls prove the option on the built executable.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// hooksOff.active = true;
/// ```
/// What:
/// The arguments a real-Git start gets, with the hooks-off options in front of the subcommand.
/// Why:
/// The options follow every global option of the caller (`-c`, `--config-env`, `--git-dir`), so a caller cannot
/// switch a hook event or the hooks path back on, and they precede the subcommand, so they apply to it.
/// A configuration query gets none, because it must report what the repository configures, and a start that names no
/// subcommand (`--version`, a help query) has no global options to follow, so it gets them first.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// [...args.slice(0, layout.prefixLen), ...hooksOffOptions, ...args.slice(layout.prefixLen)]
/// ```
/// What:
/// Add `arguments` to a real-Git command, with the hooks-off options once the process has switched them on.
/// Why:
/// One function serves both builders of real-Git commands, so no start can miss the options.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// command.args(hooksOff.active ? hooksOffArguments(args) : args);
/// ```