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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//! Remote configuration, refspecs, URLs and credentials.
//!
//! A *remote* is a named entry in a repository's configuration that says where another repository
//! lives and which references to exchange with it:
//!
//! ```text
//! [remote "origin"]
//! url = https://example.com/project.git
//! pushurl = ssh://git@example.com/project.git
//! fetch = +refs/heads/*:refs/remotes/origin/*
//! ```
//!
//! This module reads and edits that configuration, maps reference names through refspecs,
//! interprets URLs and endpoints, and obtains credentials. It performs no network I/O and never
//! starts a transfer. [`crate::workflow`] composes these pieces into `git fetch`-like operations;
//! the core [`crate::fetch`], [`crate::push`] and [`crate::clone`] APIs take their results as
//! explicit inputs.
//!
//! # Which type to use
//!
//! | Task | Start with |
//! | --- | --- |
//! | Fetch or push by remote name, as Git does | [`crate::workflow`], which uses this module internally |
//! | Read a remote's URLs and refspecs to drive [`crate::fetch::FetchRequest`] | [`Remote`] |
//! | Choose the endpoint to connect to, applying `insteadOf`, `pushurl` and `protocol.allow` | [`Remote::fetch_destination`], [`Remote::push_destinations`] |
//! | List or display remotes as the user configured them | [`ConfiguredRemote`], [`ConfiguredRemoteRecord`], [`Remote::names`] |
//! | Read only a remote's URLs, even if its refspecs use syntax girt cannot map | [`RemoteUrls`] |
//! | Add, rename or remove a remote, or edit its URLs and refspecs | [`RemoteConfig`] |
//! | Map reference names through refspecs | [`Refspecs::map`], [`Refspecs::map_advertisement`] |
//! | Interpret a URL a user typed, or show a URL's host and path | [`canonicalize_user_url`], [`ParsedUrl`], [`parse_configured_url`] |
//! | Look up, save or forget credentials | [`CredentialSession`], [`CredentialStore`] |
//! | Check a name before creating a remote | [`validate_name`] |
//!
//! [`Remote::find`] parses every fetch and push refspec with the strict [`Refspec`] grammar and
//! fails if any is outside it, for example shorthand such as `fetch = main`. Such a remote cannot
//! drive a transfer, but [`RemoteUrls`], [`ConfiguredRemote`] and [`ConfiguredRemoteRecord`] can
//! still read and display it.
//!
//! The configuration readers ([`Remote`], [`RemoteUrls`], [`ConfiguredRemote`] and
//! [`ConfiguredRemoteRecord`]) take an explicit [`Config`](crate::Config) snapshot, usually
//! [`Repository::config`](crate::Repository::config), and read no files or environment variables
//! themselves. They return owned snapshots that do not change if the configuration file is edited
//! later. The other parts of the module differ: [`ParsedUrl`] borrows the bytes it parses,
//! [`canonicalize_user_url`] inspects the filesystem to resolve local paths, and
//! [`CredentialSession`] reads credential files and runs helper programs, blocking while it
//! does.
//!
//! # Refspecs
//!
//! A refspec says which references to transfer and what to call them on the other side. `git
//! fetch`'s default, `+refs/heads/*:refs/remotes/origin/*`, reads "for every branch on the remote,
//! update the local remote-tracking reference with the same suffix, even if it does not
//! fast-forward". The parts are:
//!
//! - `<src>:<dst>`: the source name on the sending side and the destination on the receiving side.
//! A fetch maps remote names to local names; a push maps local names to remote names.
//! - `*`: one wildcard per side, matching any bytes (including `/`). The destination receives what
//! the source matched.
//! - A leading `+`: *force intent*, meaning the destination may move to a commit that does not
//! descend from its current value.
//! - A leading `^` (fetch only): exclude matching sources, as in `^refs/heads/wip/*`.
//! - A fetch source with no destination (`refs/heads/main`) downloads objects without recording a
//! reference. A push of `:refs/heads/old` deletes the remote reference.
//!
//! girt has two refspec parsers because configurations contain more syntax than can be mapped
//! without further context. [`Refspec`] and [`Refspecs`] accept the subset that can be mapped
//! purely: full `refs/…` names, `HEAD` as a source, wildcards, exclusions and deletions. They are
//! what transfers use. [`ConfiguredRefspec`] accepts everything Git accepts in configuration,
//! including shorthand such as `main`, matching push (`:`), and object IDs, and describes it
//! without resolving it. Use it to show or validate configuration; use [`Refspecs`] to plan
//! transfers.
//!
//! Force intent is not permission. A `+` is recorded as [`ForceIntent::Requested`], but each
//! operation that publishes references decides separately whether to honor it:
//! [`crate::fetch::FetchRequest::prepare`] requires the caller to authorize each forced
//! destination by name, and [`crate::push::PushCommand`] takes an explicit
//! [`ForcePolicy`](crate::push::ForcePolicy). Mapping a refspec never reads objects or references,
//! so it cannot know whether an update would fast-forward.
//!
//! # URLs and endpoints
//!
//! A remote's `url` can be an `https://`, `ssh://` or `file://` URL, an scp-like `user@host:path`,
//! a local path, or a `<helper>::<address>` for a remote helper. Before connecting, Git rewrites it
//! with `url.<base>.insteadOf` (and `pushInsteadOf` for pushes without a `pushurl`) and checks
//! `protocol.allow`. [`Remote::fetch_destination`] and [`Remote::push_destinations`] do the same
//! and return a [`Destination`] whose [`Protocol`] selects the transport. By default local paths
//! and `file://` are allowed unless `GIT_PROTOCOL_FROM_USER=0` marks the operation as automatic,
//! remote helpers are refused, and the network protocols are allowed. A `GIT_ALLOW_PROTOCOL`
//! list, passed in through [`ProtocolEnvironment`], replaces that policy entirely.
//!
//! URLs can contain credentials (`https://user:token@host/…`), and query strings can contain
//! tokens. The byte accessors return URLs unchanged so a transfer can use them. Error messages in
//! this module never include URL bytes, and the `Debug` output of [`Destination`],
//! [`RemoteUrls`], [`ConfiguredRemote`], [`ConfiguredRemoteRecord`], [`ParsedUrl`] and the
//! credential types omits them. [`Remote`]'s `Debug` output does include its URLs, so don't log
//! it. To show a URL, build it from [`ParsedUrl::host`] and [`ParsedUrl::path`], which drop the
//! username and password, and remove any query string from the path yourself.
//!
//! # Credentials
//!
//! Git's credential model has three parts:
//!
//! - A *context* identifies what the credential is for: protocol, host and, if
//! `credential.useHttpPath` is set, the repository path ([`CredentialContext`]).
//! - *Helpers*, configured with `credential.helper`, are programs that look up, save and erase
//! credentials, such as `osxkeychain`, `manager` or `store`. Git asks them in order until one
//! answers ([`CredentialHelper`]).
//! - If no helper answers, the user is prompted. After the server accepts or rejects the
//! credential, every helper is told, so it can save or forget it.
//!
//! [`CredentialSession::fill`] runs that lookup and returns a session holding the credential;
//! [`CredentialSession::approve`] and [`CredentialSession::reject`] report the outcome.
//!
//! The session never runs a program just because configuration names it. A repository's
//! configuration can come from an untrusted clone, so the application maps each configured helper
//! name to a [`CredentialBackend`] it has approved: a [`CredentialProgram`] with an explicit
//! executable and arguments, or the native [`CredentialStore`]. A configured helper without a
//! mapping fails with [`CredentialError::UnapprovedHelper`] before anything runs.
//!
//! [`crate::workflow`] grants that approval automatically, the way Git does: a `!command` value
//! runs as a shell command, an absolute path runs that program, and any other name runs
//! `git-credential-<name>` from `PATH` or Git's exec path. Most applications therefore never
//! construct helpers themselves, but they must trust the configuration they give `workflow`, or
//! filter its `credential.helper` values first. The refusal above protects only applications that
//! call [`CredentialSession::fill`] with their own mappings.
//!
//! [`CredentialStore`] answers Git's `store` helper without starting a process, reading and
//! writing the same plain-text `~/.git-credentials` files with the same locking. It is useful for
//! tests and for environments where Git is not installed. Its files hold passwords unencrypted.
//!
//! # Errors and recovery
//!
//! - [`EndpointError::Refused`]: `protocol.allow`, `protocol.<name>.allow` or `GIT_ALLOW_PROTOCOL`
//! forbids the remote's protocol. This is policy, not a malformed URL; show the user which
//! setting applies rather than retrying.
//! - [`ConfiguredRemoteError::UnsupportedUrlSyntax`]: the URL is valid Git syntax that girt does
//! not yet interpret. Treat the whole remote as unsupported, rather than as invalid.
//! - [`RemoteError::Refspec`]: a refspec is outside the subset that can drive a transfer; the
//! remote can still be displayed with [`ConfiguredRemote`].
//! - [`CredentialError`]: a lookup was refused, failed or was cancelled. A successful
//! [`CredentialSession::fill`] can still return an incomplete credential when no prompt is
//! supplied, and helpers that exit unsuccessfully are listed in
//! [`CredentialSession::helper_failures`] while later helpers still run. Approving a credential
//! only reports that the server accepted it; it says nothing about whether the transfer finished.
//!
//! # Resolving where to connect
//!
//! Find a remote and resolve the endpoint a transfer would use. Resolution applies `insteadOf` and
//! protocol policy but performs no I/O; [`crate::workflow::fetch`] shows the transfer itself.
//!
//! ```
//! use girt::remote::{Protocol, ProtocolEnvironment, Remote, RemoteConfig};
//! use girt::{InitKind, ObjectFormat, Repository};
//!
//! let directory = tempfile::tempdir()?;
//! let path = directory.path().join("project");
//! Repository::init(ObjectFormat::Sha1, &path, InitKind::Worktree)?;
//! let mut edit = Repository::open(&path)?.edit_config(1 << 20)?;
//! RemoteConfig::new(edit.document_mut()).add(
//! b"origin",
//! b"https://example.com/project.git",
//! &[b"+refs/heads/*:refs/remotes/origin/*"],
//! )?;
//! edit.commit()?;
//!
//! let repo = Repository::open(&path)?;
//! let origin = Remote::find(repo.config(), b"origin")?.ok_or("no remote named origin")?;
//! let destination = origin.fetch_destination(repo.config(), &ProtocolEnvironment::default())?;
//! match destination.protocol() {
//! Protocol::Local | Protocol::File => println!("local path {:?}", destination.local_path()?),
//! Protocol::Https | Protocol::Http => println!("needs the `http` feature"),
//! Protocol::Ssh => println!("needs the `ssh` feature"),
//! _ => println!("unsupported transport"),
//! }
//! # assert_eq!(destination.protocol(), Protocol::Https);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # Mapping a configured refspec
//!
//! Map local tips through a remote's configured fetch refspec. Mapping is pure: it reads no
//! storage and contacts no server.
//!
//! ```
//! use girt::Config;
//! use girt::refs::RefName;
//! use girt::remote::{RefSource, Remote};
//!
//! let config = Config::parse(
//! b"[remote \"origin\"]\n\
//! url = https://example.invalid/project\n\
//! fetch = +refs/heads/*:refs/remotes/origin/*\n",
//! )?;
//! let origin = Remote::find(&config, b"origin")?.expect("origin is configured");
//! assert_eq!(
//! origin.fetch_url(),
//! Some(&b"https://example.invalid/project"[..])
//! );
//!
//! let id = girt::ObjectFormat::Sha1.hash_object(girt::ObjectKind::Commit, b"example");
//! let sources = [RefSource {
//! name: RefName::new("refs/heads/main")?,
//! id,
//! }];
//! let mappings = origin.fetch_refspecs().map(&sources)?;
//! assert_eq!(
//! mappings[0].destination,
//! Some(RefName::new("refs/remotes/origin/main")?)
//! );
//! assert_eq!(mappings[0].force, girt::remote::ForceIntent::Requested);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # How jj uses this module
//!
//! jj's girt backend uses [`Remote::names`] and [`ConfiguredRemoteRecord`] to list remotes,
//! including ones without URLs, and [`ConfiguredRemote`] to show each remote's effective URLs
//! after `insteadOf` rewriting. It reads [`ConfiguredRefspec::kind`] to translate fetch refspecs
//! into bookmark patterns, refusing kinds it cannot represent. `jj git remote add` and `jj git
//! clone` pass the URL the user typed through [`canonicalize_user_url`], which makes local paths
//! absolute, and `jj git remote add` checks the name with [`validate_name`]. jj builds a browser
//! link for a remote from [`ParsedUrl::host`] and [`ParsedUrl::path`]. Transfers themselves go
//! through [`crate::workflow`].
//!
//! # Not covered
//!
//! Tag following (`remote.<name>.tagOpt` is reported but not acted on), mirror remotes,
//! `remote.<name>.prune`, `push.default` and other options that change what a bare `git fetch` or
//! `git push` would do remain the caller's policy. This module is not a complete interpretation
//! of `git fetch <remote>` or `git push <remote>`.
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use CredentialStore;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;