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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
//! Transports, protocol versions, resource limits, and cancellation and deadlines.
//!
//! A transfer reaches another repository through one of three transports. This module holds the
//! network transports' endpoint types, the [`TransportControl`] that cancels a transfer or imposes
//! a deadline, and the [`RequestedProtocol`] for fetches. The transfer operations themselves live
//! in [`crate::fetch`], [`crate::push`] and [`crate::clone`]; [`crate::workflow`] picks a transport
//! from configuration automatically.
//!
//! # Choosing a transport
//!
//! | Remote | Transport | Feature | Endpoint |
//! | --- | --- | --- | --- |
//! | Local path or `file://` URL | Native local | none | a path |
//! | `http://`, `https://` | Smart HTTP | `http` | [`http::HttpRemote`] |
//! | `ssh://`, `user@host:path` | System OpenSSH, macOS and Linux | `ssh` | [`ssh::SshRemote`] |
//!
//! **Native local** transfers read the other repository's object storage directly. They never
//! start `git upload-pack` or `git receive-pack`, need no Git installation and no async runtime,
//! and are synchronous. A local push runs the destination's receive hooks and follows its
//! `receive.*` settings.
//!
//! **Smart HTTP** uses reqwest with rustls. [`http::HttpRemote::configured`] applies Git's `http.*`
//! configuration: TLS verification and CA bundles, proxies and same-origin redirects.
//! [`http::HttpRemote::new`] instead takes explicit headers, for applications that supply their
//! own `Authorization` header.
//!
//! **SSH** runs the system `ssh` executable, so keys, agents, `known_hosts` and `~/.ssh/config`
//! work as they do for the `ssh` command. [`ssh::SshRemote::openssh`] keeps the user's ordinary
//! OpenSSH configuration, which is what an interactive client such as jj wants.
//! [`ssh::SshRemote::configured`] and [`ssh::SshRemote::new`] instead pin an explicit
//! configuration file and force restrictive options (no password prompts, forwarding or
//! connection sharing, and agents or askpass only when the application supplies them), for
//! services that must not depend on a user's settings.
//!
//! `git://` and remote helpers (`<transport>::<address>`) are not supported.
//!
//! # Async network, synchronous validation
//!
//! The HTTP and SSH adapters are `async` and run on a Tokio runtime the caller owns, with I/O and
//! time enabled. girt creates no runtime and spawns no tasks. A network fetch returns an owned
//! download, such as [`crate::fetch::FetchDownload`], rather than validated objects. Validation
//! inflates, hashes and parses every object, which can take seconds of CPU time, so it is a
//! separate synchronous call. Run it on a blocking worker (`tokio::task::spawn_blocking` or your
//! own pool), not on an executor thread. The caller bounds how many downloads wait and how many
//! validations run at once, and joins each worker even after cancelling it.
//!
//! [`crate::workflow`] hides this split by creating a private single-threaded runtime per call;
//! do not call it from inside another Tokio runtime.
//!
//! # Protocol versions
//!
//! Git has three versions of the fetch (upload-pack) protocol. Version 2 sends capabilities first
//! and lets the client run separate `ls-refs` and `fetch` commands; versions 0 and 1 begin with the
//! full reference advertisement. [`RequestedProtocol`] selects which version an HTTP or SSH fetch
//! asks for. A server asked for v2 may answer with v0 or v1, and girt accepts that, so requesting
//! v2 from an older server is safe. A v2 answer to a v0 or v1 request is refused. The core
//! adapters request v0 by default; [`crate::workflow`] follows `protocol.version`, which defaults
//! to v2 as in Git. Push uses the v0 or v1 receive-pack protocol, which has no v2, and native local
//! transfers use no wire protocol.
//!
//! # Limits and trust
//!
//! A fetch validates what it receives, whatever its limits. Every received object's pack framing,
//! checksum, delta chain and identity are checked. Then the new history of every selected tip is
//! walked, down to any declared shallow boundary and to history the destination already has, which
//! is trusted rather than re-checked: each object reached must be present and well-formed, with
//! edges of the right kinds. Malformed objects outside that history, such as
//! unreachable extras a server included, are not parsed further and do not fail the fetch. Nothing
//! is installed until validation succeeds, and changing the limits never relaxes these checks.
//!
//! Resource limits answer a different question: how much *well-formed* work may the other side
//! ask for? A valid pack can be 50 GB, hold ten million objects or chain deltas thousands deep.
//! `git fetch` accepts all of that from the remotes its user configured. A service that fetches
//! URLs supplied by its users must not, or one user can exhaust its disk, memory or CPU.
//!
//! Each phase of a transfer therefore takes explicit limits, and each limits type has two
//! constructors:
//!
//! | Limits | Bounds | Untrusted input | Trusted input |
//! | --- | --- | --- | --- |
//! | [`FetchLimits`](crate::fetch::FetchLimits) | advertisement, wire bytes, pack size, objects, decoding, connectivity | `default()` | `trusted()` |
//! | [`FetchUpdateLimits`](crate::fetch::FetchUpdateLimits) | checking installed objects and fast-forwards before publishing | `default()` | `trusted()` |
//! | [`PushLimits`](crate::push::PushLimits) | advertisement, commands, selected history, pack writing, status | `default()` | `trusted()` |
//! | [`PackLimits`](crate::PackLimits) | opening an object store: index bytes, packs, files | `default()` | `trusted()` |
//!
//! `default()` suits a peer you don't control: for example, a fetch may receive at most 512 MiB
//! on the wire and a 256 MiB pack of at most one million objects, with each object at most 64 MiB
//! and deltas at most 64 deep. `trusted()` raises the size and count limits until available memory
//! and disk are what bound the work, matching Git; [`crate::workflow`] uses it for the remotes a
//! user configured. It keeps some finite bounds: for example, delta chains and tag chains may be at
//! most 10,000 deep, at most 256 commits are offered during negotiation, a transfer adds at most 8
//! worker threads, and an object store may have at most 100,000 packs and 4,096 open files.
//! The defaults also refuse large *trusted* repositories. The default
//! [`PackLimits`](crate::PackLimits) admits 64 MiB of pack index, while a Linux kernel clone has
//! about 300 MB, so use `trusted()` when opening big repositories you control, as in
//! [`FetchLimits::source_packs`].
//!
//! Exceeding a [`FetchLimits`](crate::fetch::FetchLimits) bound while receiving or validating
//! fails the transfer before anything is installed, with an error that names the bound, such as
//! [`FetchError::Limit`](crate::fetch::FetchError::Limit); the repository is unchanged.
//! [`FetchUpdateLimits`](crate::fetch::FetchUpdateLimits) apply later, in
//! [`FetchReady::finish`](crate::fetch::FetchReady::finish). Its snapshot limits bound opening the
//! destination to check dependencies before installation; its verification and history limits
//! bound checking the installed objects and fast-forwards afterwards. Exceeding either publishes
//! no references. Read what was installed from the report in
//! [`FetchFinishError`](crate::fetch::FetchFinishError). Limits bound input and work, not
//! wall-clock time or total process memory: pair them with a deadline, and bound how many
//! transfers run at once.
//!
//! [`FetchLimits::source_packs`]: crate::fetch::FetchLimits::source_packs
//!
//! # Cancellation and deadlines
//!
//! [`TransportControl`] carries a cancellation flag and an optional absolute deadline into every
//! core transfer. Set the [`AtomicBool`](std::sync::atomic::AtomicBool) from another thread to stop
//! a transfer; the deadline is never extended by traffic. Both are checked between units of work
//! and, for the network adapters, every 20 ms while waiting for the peer. A single hash, inflate or
//! filesystem call is not interrupted.
//!
//! What an interruption means depends on how far the transfer got. A fetch interrupted before
//! installation leaves the repository unchanged. One interrupted during installation can leave a
//! pack without its index, which readers ignore; see
//! [`ReceivedFetch::install`](crate::fetch::ReceivedFetch::install). One interrupted after
//! installation keeps the new objects, and [`FetchFinishError`](crate::fetch::FetchFinishError)
//! reports what was done. Cancellation is not checked once a reference transaction has started,
//! so a late cancellation does not stop publication. A push interrupted before any command was
//! sent returns [`PushError::NotSent`](crate::push::PushError::NotSent): it attempted no reference
//! updates, though a push to a local path may already have stored objects or run receive hooks
//! there. One interrupted after sending began returns
//! [`PushError::Uncertain`](crate::push::PushError::Uncertain), because the remote may already have
//! applied some updates. Read the remote's references before retrying it.
//!
//! A deadline covers only the call it is passed to. Validating a network download and
//! [`FetchReady::finish`](crate::fetch::FetchReady::finish) take just the cancellation flag; to
//! bound a whole fetch, set the flag from a timer and wait for the call to return.
//!
//! [`crate::workflow`] takes no control: its transfers can be neither cancelled nor bounded in
//! time.
//!
//! ## Example: a deadline before sending
//!
//! A deadline that has already passed stops a native local push (setup hidden) before any command
//! is sent, so `NotSent` guarantees the destination references are unchanged. An interruption
//! after sending begins is reported as `Uncertain` instead; inspect the destination before
//! retrying.
//!
//! ```
//! use std::sync::atomic::AtomicBool;
//! use std::time::Instant;
//!
//! use girt::PackLimits;
//! use girt::push::{
//! ForcePolicy, PreparedPush, PushCommand, PushError, PushFailure, PushLimits,
//! send_local,
//! };
//! use girt::transport::TransportControl;
//! # use girt::refs::{Expected, RefName, Target};
//! # use girt::{Commit, CommitFields, InitKind, ObjectFormat, Repository, Signature, Tree};
//! # let directory = tempfile::tempdir()?;
//! # let source_path = directory.path().join("source.git");
//! # let source = Repository::init(ObjectFormat::Sha1, source_path, InitKind::Bare)?;
//! # let person = Signature {
//! # name: b"A. Writer".to_vec(),
//! # email: b"writer@example.com".to_vec(),
//! # seconds: 1_700_000_000,
//! # offset_minutes: 0,
//! # };
//! # let loose = source.loose_objects();
//! # let commit = loose.write_commit(&Commit::new(CommitFields {
//! # tree: loose.write_tree(&Tree::new(ObjectFormat::Sha1, vec![])?)?,
//! # parents: vec![],
//! # author: person.clone(),
//! # committer: person.clone(),
//! # extra_headers: vec![],
//! # message: b"Initial commit\n".to_vec(),
//! # })?)?;
//! # let main = RefName::new("refs/heads/main")?;
//! # source.references()?.update_without_reflog(&main, Target::Direct(commit), Expected::Absent)?;
//! # let destination_path = directory.path().join("remote.git");
//! # let destination = Repository::init(ObjectFormat::Sha1, destination_path, InitKind::Bare)?;
//! # let objects = source.objects_with(PackLimits::default())?;
//! # let command = PushCommand::new(main.clone(), None, commit).with_force(ForcePolicy::FastForwardOnly);
//! let cancel = AtomicBool::new(false);
//! let prepared = PreparedPush::new(
//! &objects, vec![command], &[], PushLimits::default(), &cancel,
//! )?;
//! let control = TransportControl::new(&cancel).with_deadline(Some(Instant::now()));
//! let result = send_local(
//! destination.git_dir(),
//! girt::push::LocalPushRequest::new(&prepared),
//! control,
//! |_| {}
//! );
//! assert!(matches!(result, Err(PushError::NotSent(PushFailure::Deadline))));
//! assert_eq!(destination.references()?.read(&main)?, None);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! <!-- Feature-dependent link targets: the item when it is compiled, docs.rs otherwise. -->
pub use RequestedProtocol;
pub use TransportControl;
pub use ;
pub use Server;