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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! Streaming `git clone --progress` runner that emits byte/object percentages
//! as fine-grained provisioning detail (#2605).
//!
//! Why: the clone is the long pole of a large-repo spawn — minutes during which
//! the coarse `CloningRepo` stage alone shows no movement. Forcing
//! `git clone --progress` and parsing its stderr lets the daemon emit
//! `emit_with_detail(CloningRepo, "Receiving objects: 63%")` so the CLI spinner
//! shows real progress within the clone. Runs on the same task as the (already
//! scoped) provision, so the `provisioning_stage` task-local emitter is visible
//! to [`emit_with_detail`] without threading any handle through the backend.
//! What: [`clone_with_progress`] spawns a prepared `git` command with stderr
//! piped, splits the stream on `\r`/`\n` (git rewrites progress lines with a
//! carriage return), emits deduplicated percentage detail, and returns the exit
//! status plus the full captured stderr for the caller's error path.
//! [`parse_git_progress`] is the pure line parser, extracted so it is testable
//! without a subprocess.
//! Test: `parse_git_progress_extracts_known_phases`,
//! `parse_git_progress_ignores_non_progress_lines`.
//!
//! `clone_with_progress` spawns via
//! [`crate::core::spawn_disclaim::disclaimed_stderr_piped_spawn`] rather than
//! a raw `Command::spawn()` so the clone's TCC responsibility is disclaimed
//! on macOS — see that function's docs (issue #3267, #2997 part 6).
use Read;
use Command;
use crate;
use cratedisclaimed_stderr_piped_spawn;
/// Outcome of a streamed clone: whether it succeeded and its full stderr.
///
/// Why: the caller formats a descriptive `ProvisionError` from the exit status
/// and the captured stderr, exactly as the previous `.output()` path did.
/// What: `success` (the child's exit status) and `stderr` (the complete stream,
/// progress lines included).
/// Test: exercised via the `#[ignore]` clone integration tests; the parse logic
/// is unit-tested directly.
pub
/// Parse a single git stderr line into a `"<Phase>: <pct>%"` detail string.
///
/// Why: only a handful of git progress phases carry a meaningful percentage;
/// isolating the match+parse keeps the streaming loop trivial and lets the
/// (drift-prone) parsing be unit-tested with plain fixtures.
/// What: strips an optional `remote: ` prefix, splits on the first `:`, and —
/// for a recognized progress phase — extracts the integer percent preceding the
/// first `%`. Returns `None` for any other line (banners, non-progress output).
/// Test: `parse_git_progress_extracts_known_phases`,
/// `parse_git_progress_ignores_non_progress_lines`.
pub
/// Run a prepared `git clone` command, streaming its progress as stage detail.
///
/// Why: replaces the backend's blocking `.output()` so the clone's byte/object
/// percentages reach the client live, while preserving the exact
/// success/stderr contract callers already depend on.
/// What: spawns `cmd` via [`disclaimed_stderr_piped_spawn`] (stderr piped,
/// stdout discarded, TCC responsibility disclaimed on macOS — issue #3267),
/// reads stderr to EOF splitting on `\r`/`\n`, emits deduplicated
/// [`parse_git_progress`] detail on the scoped `CloningRepo` stage, then waits
/// and returns a [`CloneOutcome`]. Callers must have already added `--progress`
/// to `cmd` (git suppresses progress on a non-TTY stderr otherwise).
/// Test: no dedicated unit test of `clone_with_progress` itself — its
/// contract (stream stderr, discard stdout, preserve success/stderr) is
/// unchanged by the #3267 disclaim-wrapper conversion, so there is no
/// meaningful failing-first test for that change; the underlying
/// `disclaimed_stderr_piped_spawn` wrapper is unit-tested directly (see its
/// own doc). Exercised end-to-end via a real `git clone` through this exact
/// function by `provisioner::workspace::tests::ensure_base_checkout_recovers_from_concurrent_race`
/// and `ensure_base_checkout_rejects_stale_non_bare_directory`; the pure
/// parse logic is unit-tested by `parse_git_progress`'s own `tests` module
/// below.
pub
/// Emit one completed stderr segment as progress detail, deduplicating repeats.
///
/// Why: git rewrites a progress line many times per percent; emitting only when
/// the formatted `"<Phase>: <pct>%"` changes keeps the broadcast channel from
/// flooding while still tracking every percentage step.
/// What: parses `segment`; if it yields a detail distinct from `last_emit`,
/// emits it on the scoped `CloningRepo` stage and records it as the new last.
/// Test: covered transitively by the clone integration tests.