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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! Free-text project entry for the `tm ls` new-session picker (#7488).
//!
//! Why: the picker could only start a session in a project the registry already
//! held, or in a checkout already on this host. A project the operator has not
//! cloned yet — the overwhelmingly common case for "work on this repo someone
//! just sent me" — had no way in at all, so they left the surface and typed
//! `tm session new <url>`.
//!
//! What: [`parse_project_entry`] is the pure recogniser for the three spellings
//! the owner asked for — a full clone URL, a bare `owner/repo` (defaulting to
//! `github.com`), and an explicit `domain/owner/repo`. [`request_for_entry`]
//! turns a recognised one into the SAME
//! [`NewSessionRequest`](super::new_session::NewSessionRequest) a typed checkout
//! path produces, so the registration runs through
//! [`perform`](super::new_session::perform) — the one register-then-create
//! driver — rather than a second implementation of it.
//!
//! #7898: what the create leg carries is the project's LOCAL CHECKOUT, not the
//! clone URL. Since ADR-0055 the daemon clones nothing, so the URL was accepted
//! by the registration and then refused by the spawn, leaving a registration
//! behind for a session that never existed. A project that is not cloned yet is
//! refused up front with the same `git clone <url> <path>` instruction the
//! registered-row path prints (#7887) — the picker does not clone on the
//! operator's behalf, because a multi-minute clone inside a keystroke handler
//! has nowhere to report progress.
//!
//! Nothing here touches the network or the disk: a rejected entry is a
//! `Result::Err` the overlay shows inline, never a silently dropped keystroke.
//!
//! Test: `new_session_entry_*` in `super::tests`.
use PathBuf;
use parse_remote_url;
use redact_stored_url;
use ;
/// How an unregistered project's clone URL becomes a local checkout (#7898).
///
/// Why: the production answer
/// ([`local_checkout_for_url`](trusty_mpm::project::local_checkout_for_url))
/// depends on this host's projects root, which no unit test should read. A
/// function pointer keeps the whole unregistered branch decidable with a stub.
/// Test: `new_session_entry_builds_a_clone_and_register_request`.
pub type CheckoutForUrl = fn ;
/// Forge a bare `owner/repo` entry means.
///
/// GitHub is the only host the two-segment shorthand can mean unambiguously —
/// every other host has to be spelled, which is what the three-segment form is
/// for.
const DEFAULT_HOST: &str = "github.com";
/// A project the operator named by typing rather than by picking a row.
///
/// Why: registering a project needs a name and a clone URL, and both come from
/// one look at the typed text, so they travel together — the same shape
/// [`ProjectIdentity`](super::new_session::ProjectIdentity) has for a path,
/// minus the local root a project that is not cloned yet does not have.
/// What: `name` is the repo leaf (the registry's naming convention), `repo_url`
/// the clone URL the daemon is handed.
/// Test: `new_session_entry_parses_the_three_shapes`.
pub
/// Recognise one typed project spelling, or refuse it.
///
/// Why (#7488 acceptance 2): the picker must tell "this is a project I can
/// clone" from "this is a typo" WITHOUT a round trip, so the whole decision is
/// one pure function the tests can table-drive.
/// What: `Some` for exactly three shapes —
/// - a clone URL [`parse_remote_url`] accepts (`https`, `http`, `ssh`, `git`
/// schemes and the scp-style `git@host:owner/repo`), kept VERBATIM so an
/// `ssh` entry still clones over ssh;
/// - `owner/repo`, which means [`DEFAULT_HOST`];
/// - `domain/owner/repo`, where the first segment must look like a host.
///
/// `None` for everything else, including a local path (`/…`, `~/…`, `./…`,
/// which the checkout resolver owns), an entry carrying whitespace, and a
/// two-segment entry whose first segment is a domain — `example.com/repo` names
/// no owner, and guessing one would clone the wrong thing.
/// Test: `new_session_entry_parses_the_three_shapes`,
/// `new_session_entry_rejects_malformed_text`.
pub
/// Build the `https` entry for a host/owner/repo triple.
/// True when the text is a git remote URL rather than a slash-separated triple.
///
/// A scheme (`https://`) or a colon before the first slash (scp-syntax
/// `git@host:owner/repo`) is what distinguishes one; both are shapes
/// [`parse_remote_url`] owns, so recognising them here only decides WHICH
/// parser answers.
/// True when a path segment can be an owner or a repository name.
/// True when a first segment names a host rather than an owner.
/// Turn typed text into a create request, or say why it cannot be one.
///
/// Why (#7488 acceptance 2 and 3): a project the registry already holds must
/// NOT be registered a second time — `register` is an unqualified upsert, so a
/// redundant call would replace the stored record's optional fields with the
/// two this flow can supply. Comparing against the targets already in hand
/// answers that without another round trip, exactly as
/// [`request_for_path`](super::new_session::request_for_path) does for a path.
/// What: an unrecognised entry is an `Err` the overlay shows inline and nothing
/// is created. A recognised one whose name or URL is already a registered
/// target starts a session in that row's own checkout and skips the
/// registration — through
/// [`request_for_registered`](super::new_session::request_for_registered), the
/// same builder the arrow-key confirm uses (#7887), so typing a registered
/// `owner/repo` cannot send something different from picking its row. Anything
/// else carries a `register` leg beside the LOCAL CHECKOUT that project's clone
/// URL resolves to (#7898), never the URL itself.
/// Test: `new_session_entry_builds_a_clone_and_register_request`,
/// `new_session_entry_reuses_a_registered_project`,
/// `new_session_entry_without_a_checkout_names_the_clone_step`,
/// `new_session_entry_rejects_malformed_text`.
pub
/// [`request_for_entry`] with an explicit checkout resolver (the test seam).
///
/// Why (#7989): the production resolver reads this host's projects root —
/// `$TRUSTY_MPM_REPOS_ROOT` / `$TRUSTY_MPM_WORKSPACE_ROOT` / config / real
/// `$HOME` — which sibling tests in this binary `set_var`, so a test that
/// called it would race them under the parallel harness. The same seam
/// [`targets_from_with`](super::new_session::targets_from_with) is for.
/// What: see [`request_for_entry`]; `checkout_for` answers where an
/// unregistered project's clone URL is checked out on this host.
/// Test: `new_session_entry_builds_a_clone_and_register_request`,
/// `new_session_entry_without_a_checkout_names_the_clone_step`,
/// `new_session_entry_with_no_checkout_masks_the_typed_password_9285`.
pub