girt 0.4.2

An incremental, idiomatic Rust implementation of Git
Documentation
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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
//! Native local and upload-pack object discovery, plus v0 object transfer.
//!
//! # Discovery, transfer, and publication
//!
//! - [`discover_local`] inspects an endpoint's advertised refs, object format, and HEAD without
//!   transferring objects. Discovery is a preview, not permission for a later ref update.
//! - [`receive_local`] or [`receive`] transfers selected advertised IDs and returns
//!   [`ReceivedFetch`]. Install the result and update refs separately when the caller owns policy.
//!   [`ReceivedFetch::install_retained`] protects a new complete pack with an owned Git `.keep`
//!   marker through caller-controlled reference publication.
//! - [`FetchRequest`] captures a destination snapshot, refspecs, force authorization, and reflog
//!   policy. Its adapters return [`FetchReady`]; [`FetchReady::finish`] installs objects and
//!   conditionally publishes selected refs. [`FetchReady::install_retained`] instead holds a
//!   shallow lock and pack retention while the caller checks installed objects, before
//!   [`RetainedFetchReady::finish`] publishes boundaries and refs. This supports self-contained
//!   complete or depth-limited transfers, including refresh with an existing shallow boundary.
//!
//! HTTP and SSH downloads keep network I/O separate from synchronous pack validation. Callers
//! bound worker concurrency and join validation work before dropping its owned result.
//!
//! Only remote-tracking and tag destinations are supported by default, with explicit caller-owned
//! tag namespaces available; see [`FetchRequest`] for worktree safety and caller coordination.
//! `FETCH_HEAD` and implicit tags remain caller policy.
//!
//! The lower-level receive APIs do not write refs or reflogs. Install their result explicitly
//! and choose reference policy yourself, or use [`FetchRequest`] for the supported workflow.
//!
//! Wants must be advertised IDs. Native local receive reads girt storage directly. [`receive`]
//! requests full histories over a caller-owned stream; [`receive_with_known`] uses
//! bounded [`KnownHistory`] to negotiate incremental transfers. Verified external delta bases in a
//! thin pack are resolved and rewritten as a self-contained pack before installation. Selected-tip
//! connectivity can depend on verified local objects; installation rechecks those dependencies.
//! [`receive_with_known_depth`] and the owned network depth variants report shallow boundaries;
//! [`FetchRequest::with_depth`] coordinates their publication. Direct installation still refuses
//! shallow results. Transfer requests `side-band-64k` and available `ofs-delta`, `thin-pack`,
//! `multi_ack`, `shallow`, and SHA-256 object-format capabilities as needed. Filtering, automatic
//! tags and protocol v2 transfer remain outside this boundary.
//! [`discover_local`], feature-gated `discover_http`/`discover_ssh`, and caller-owned
//! [`discover_session`] report references, object format and deterministic HEAD interpretation
//! without transferring objects. The owned network endpoints currently request v0; caller-owned
//! streams accept v0/v1 and v2 `ls-refs`. V0/v1 wire transfer supports both object formats. A
//! preview can become stale and never authorizes a later mutation.
//! The `http` and `ssh` features add owned async network downloads with separate synchronous
//! validation. They accept optional shared [`KnownHistory`] ownership so a download can move to
//! a caller-managed blocking worker without borrowing its initiating scope.
//! SSH requires macOS/Linux and a caller-selected OpenSSH configuration. Peeling hints are exposed
//! separately from selectable reference tips.

#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
mod ssh;
#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
pub use ssh::{receive_ssh, receive_ssh_with_depth, receive_ssh_with_progress};

#[cfg(any(
    feature = "http",
    all(feature = "ssh", any(target_os = "macos", target_os = "linux"))
))]
mod live_progress;

#[cfg(feature = "http")]
mod http;
#[cfg(feature = "http")]
pub use http::{
    receive_http, receive_http_with_depth, receive_http_with_depth_and_progress,
    receive_http_with_progress,
};

#[cfg(any(
    feature = "http",
    all(feature = "ssh", any(target_os = "macos", target_os = "linux"))
))]
mod download;
#[cfg(any(
    feature = "http",
    all(feature = "ssh", any(target_os = "macos", target_os = "linux"))
))]
pub use download::DownloadedFetch;

#[cfg(any(
    feature = "http",
    all(feature = "ssh", any(target_os = "macos", target_os = "linux"))
))]
mod workflow_network;
#[cfg(any(
    feature = "http",
    all(feature = "ssh", any(target_os = "macos", target_os = "linux"))
))]
pub use workflow_network::FetchDownload;
mod update;
mod verified;
pub use update::{FetchUpdateError, FetchUpdateLimits};
mod workflow;
mod worktree;
pub use workflow::{
    FetchFinishError, FetchFinishFailure, FetchPlanError, FetchReady, FetchReport, FetchRequest,
    FetchUpdate, FetchUpdateKind, FetchWorkflowError, RetainedFetchFinishError, RetainedFetchReady,
};

mod bytes;
mod connectivity;
mod discovery;
#[cfg(feature = "http")]
pub use discovery::discover_http;
pub(crate) use discovery::interpret_head;
pub use discovery::{
    ProtocolVersion, RemoteDiscovery, RemoteHead, discover, discover_local, discover_session,
};
#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
pub use discovery::{discover_ssh, discover_ssh_with_diagnostics};
mod progress;
pub use progress::{LocalFetchProgress, ValidationProgress};

mod import;
pub(crate) use import::index_self_contained;
mod install;
mod retention;
pub use retention::{FetchRetention, RetainedFetchError};
mod shallow;
pub use shallow::FetchShallowError;
mod known;
mod local;
mod local_native;
mod protocol;

use std::num::NonZeroU32;
use std::sync::atomic::{AtomicBool, Ordering};

pub(crate) use bytes::Bytes;
pub(crate) use install::NativeContents;
#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
pub(crate) use install::PackInput;
pub use install::{FetchInstalled, ReceivedFetch};
pub use known::KnownHistory;
pub use local::{
    receive_local, receive_local_with_control, receive_local_with_known,
    receive_local_with_known_and_progress,
};
pub(crate) use progress::ValidationObserver;
pub use protocol::{
    AdvertisedRef, Advertisement, receive, receive_with_known, receive_with_known_depth,
};

/// Resource bounds and optional positive commit depth for one wire fetch.
///
/// `depth` counts commits from each selected tip. `None` requests no depth change. A positive
/// depth can return shallow boundaries; ordinary installation rejects such a result until
/// coordinated boundary publication is available.
#[derive(Debug, Clone, Copy)]
pub struct FetchOptions {
    /// Wire, decoding, and graph budgets.
    pub limits: FetchLimits,
    /// Requested commit depth, or no depth change.
    pub depth: Option<NonZeroU32>,
}

/// Bounds for one advertisement, transfer, import, and connectivity check.
///
/// Wire transfers spool the response and pack to temporary files, each bounded by `max_wire_bytes`
/// and `max_pack_bytes` respectively. Decoded delta bases use a 96 MiB cache; live reconstruction
/// buffers are bounded by `max_decode_bytes` and per-object limits. Protocol prefixes and
/// advertisements are bounded by `max_shallow_roots` and `max_advertisement_bytes`. Entry/graph
/// metadata is
/// O(`max_objects` + `max_connectivity_edges`), plus separately retained [`KnownHistory`] payloads
/// bounded by `max_known_bytes` and metadata bounded by `max_known_objects`. Index generation needs
/// at most 44 bytes per object plus 1072 bytes. Native local construction spools pack bytes and
/// retains graph/index metadata. Graph parsing can temporarily copy a structured payload and its
/// fields. These input/work
/// bounds exclude allocator overhead, stream-owned buffers, fixed zlib scratch space, and server
/// memory; they are not a hard process heap or wall-clock limit. Zero bounds allow only the
/// corresponding empty operation. Counters include duplicate input occurrences where applicable.
#[derive(Debug, Clone, Copy)]
pub struct FetchLimits {
    /// Total received pkt-line bytes, framing included (default 512 MiB).
    pub max_wire_bytes: usize,
    /// Advertisement bytes including framing (default 4 MiB).
    pub max_advertisement_bytes: usize,
    /// Advertised entries, including peeled hints (default 100,000).
    pub max_refs: usize,
    /// Want occurrences accepted from the selection callback (default 100,000).
    pub max_wants: usize,
    /// Maximum have candidates retained (default 256). The single wire batch sends at most 32
    /// with multi-ACK, or one without it.
    /// Additional verified commits are retained for connectivity but not offered as haves.
    pub max_haves: usize,
    /// Shallow boundary IDs retained and sent in one negotiation (default 100,000).
    pub max_shallow_roots: usize,
    /// Objects retained while verifying local history (default one million).
    pub max_known_objects: usize,
    /// Aggregate local payload bytes retained (default 256 MiB).
    pub max_known_bytes: usize,
    /// Local history edge occurrences (default 4 million).
    pub max_known_edges: usize,
    /// Per-read decoding limits for local verification and installation rechecks.
    /// Total decoding work is bounded by this budget times `max_known_objects`.
    pub known_read: crate::ReadLimits,
    /// Received pack bytes, including header and trailer (default 256 MiB).
    pub max_pack_bytes: usize,
    /// Objects in the pack (default one million). Native selection also bounds distinct examined
    /// source IDs, including known boundaries; their descendants are skipped.
    pub max_objects: usize,
    /// Bytes in each decoded object (default 64 MiB).
    pub max_object_bytes: usize,
    /// Bytes in each inflated delta program (default 64 MiB).
    pub max_delta_bytes: usize,
    /// Aggregate inflated programs, ordinary payloads, delta results and appended external bases
    /// (default 512 MiB). Includes repeated decoding after cache eviction and connectivity reads.
    /// Native source selection and writing each apply a separate allowance of this size to the
    /// sum of selected logical payload lengths, including copied entries. Known boundaries are
    /// excluded; avoiding blob inflation does not exempt their declared result sizes.
    pub max_decode_bytes: usize,
    /// Delta edges from any object to an ordinary base (default 64).
    pub max_delta_depth: usize,
    /// Entry/edge visits and external-base probes during queued base resolution (default 10
    /// million).
    pub max_resolution_steps: usize,
    /// Reachable edge occurrences, including duplicates (default 4 million).
    pub max_connectivity_edges: usize,
}

impl Default for FetchLimits {
    fn default() -> Self {
        Self {
            max_wire_bytes: 512 * 1024 * 1024,
            max_advertisement_bytes: 4 * 1024 * 1024,
            max_refs: 100_000,
            max_wants: 100_000,
            max_haves: 256,
            max_shallow_roots: 100_000,
            max_known_objects: 1_000_000,
            max_known_bytes: 256 * 1024 * 1024,
            max_known_edges: 4_000_000,
            known_read: crate::ReadLimits::default(),
            max_pack_bytes: 256 * 1024 * 1024,
            max_objects: 1_000_000,
            max_object_bytes: 64 * 1024 * 1024,
            max_delta_bytes: 64 * 1024 * 1024,
            max_decode_bytes: 512 * 1024 * 1024,
            max_delta_depth: 64,
            max_resolution_steps: 10_000_000,
            max_connectivity_edges: 4_000_000,
        }
    }
}

impl FetchLimits {
    /// Returns limits for a transfer the caller trusts as Git trusts the user's own remotes.
    ///
    /// Git bounds none of these values. The defaults suit untrusted input; these are large enough
    /// that only available memory bounds the work, while keeping headroom for internal arithmetic.
    ///
    /// Wire pack payloads stay on disk, with a bounded delta-base cache. Metadata and individual
    /// object reconstruction still grow with the input. `max_haves` keeps its default: it bounds
    /// negotiation, not what can be fetched.
    pub const fn trusted() -> Self {
        const UNBOUNDED: usize = usize::MAX / 4;
        Self {
            max_wire_bytes: UNBOUNDED,
            max_advertisement_bytes: UNBOUNDED,
            max_refs: UNBOUNDED,
            max_wants: UNBOUNDED,
            max_haves: 256,
            max_shallow_roots: UNBOUNDED,
            max_known_objects: UNBOUNDED,
            max_known_bytes: UNBOUNDED,
            max_known_edges: UNBOUNDED,
            known_read: crate::ReadLimits::trusted(),
            max_pack_bytes: UNBOUNDED,
            max_objects: UNBOUNDED,
            max_object_bytes: UNBOUNDED,
            max_delta_bytes: UNBOUNDED,
            max_decode_bytes: UNBOUNDED,
            max_delta_depth: 10_000,
            max_resolution_steps: UNBOUNDED,
            max_connectivity_edges: UNBOUNDED,
        }
    }
}

/// Transfer, validation, or installation failed. No references have been changed.
#[derive(Debug, thiserror::Error)]
pub enum FetchError {
    /// A supplied identity uses a different object format from this operation.
    #[error(transparent)]
    ObjectFormat(#[from] crate::ObjectFormatError),
    /// Sanitized OpenSSH transport or service failure.
    #[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
    #[error(transparent)]
    Ssh(crate::transport::ssh::SshError),
    /// Sanitized smart HTTP exchange failure.
    #[cfg(feature = "http")]
    #[error(transparent)]
    Http(crate::transport::http::HttpError),
    /// Stream or filesystem failure; protocol I/O propagates interruption without retrying.
    #[error("fetch I/O")]
    Io(#[source] std::io::Error),
    /// Invalid or unexpected protocol framing or state.
    #[error("invalid upload-pack response: {0}")]
    Protocol(&'static str),
    /// A protocol feature needed by the peer is outside this slice.
    #[error("unsupported upload-pack feature: {0}")]
    Unsupported(&'static str),
    /// Peer ERR or fatal sideband, retained as bytes without assuming UTF-8.
    #[error("upload-pack reported an error: {0:?}")]
    Remote(Vec<u8>),
    /// An explicit want was not a non-peeled advertised reference tip.
    #[error("unadvertised want {0}")]
    Unadvertised(ObjectId),
    /// A resource budget was exhausted.
    #[error("fetch limit exceeded: {0}")]
    Limit(&'static str),
    /// The cancellation flag was set or the progress callback requested cancellation.
    #[error("fetch cancelled")]
    Cancelled,
    /// The owned transport reached its caller-supplied deadline.
    #[error("transport deadline expired")]
    Deadline,
    /// Pack framing, checksum, delta reconstruction, or storage validation failed.
    #[error("received pack")]
    Pack(#[from] crate::ObjectReadError),
    /// Native local pack construction failed before destination publication.
    #[error("local pack construction")]
    PackWrite(#[source] crate::PackWriteError),
    /// Local annotated-tag advertisement could not be verified.
    #[error("local tag peeling")]
    Peel(#[source] Box<crate::PeelError>),
    /// Reopening the destination failed before dependency checks or publication.
    #[error("destination object snapshot")]
    Destination(#[source] crate::ObjectReadError),
    /// The remote and local repositories use different object formats.
    #[error("the remote repository uses {remote} object IDs but this repository uses {local}")]
    FormatMismatch {
        /// Remote object format.
        remote: crate::ObjectFormat,
        /// Local object format.
        local: crate::ObjectFormat,
    },
    /// The local source repository could not be opened.
    #[error("cannot open source repository")]
    Source(#[source] Box<crate::OpenError>),
    /// A local dependency failed during history preparation or installation rechecking.
    #[error("local fetch dependency {id}")]
    LocalRead {
        /// Local object being read.
        id: ObjectId,
        /// Concrete storage failure.
        #[source]
        source: crate::ObjectReadError,
    },
    /// Received index encoding or thin-pack completion failed before installation.
    #[error("received pack/index construction")]
    Index(#[from] crate::PackWriteError),
    /// A selected tip or reachable object is absent from both received and verified local objects.
    #[error("missing reachable object {0}")]
    Missing(ObjectId),
    /// A reachable reference has the wrong logical object type.
    #[error("reachable object {0} has the wrong kind")]
    Kind(ObjectId),
    /// Reachable commit syntax is unsupported or invalid.
    #[error("reachable commit {id}")]
    Commit {
        /// Object whose payload failed validation.
        id: ObjectId,
        /// Concrete payload validation failure.
        #[source]
        source: crate::CommitError,
    },
    /// Reachable tree syntax or entries are invalid.
    #[error("reachable tree {id}")]
    Tree {
        /// Object whose payload failed validation.
        id: ObjectId,
        /// Concrete payload validation failure.
        #[source]
        source: crate::TreeError,
    },
    /// Reachable tag syntax is unsupported or invalid.
    #[error("reachable tag {id}")]
    Tag {
        /// Object whose payload failed validation.
        id: ObjectId,
        /// Concrete payload validation failure.
        #[source]
        source: crate::TagError,
    },
    /// A local upload-pack exited unsuccessfully after its protocol response.
    #[error("local upload-pack exited unsuccessfully: {0}")]
    Process(std::process::ExitStatus),
    /// A live object artifact with the same name contains different bytes.
    #[error("existing pack artifact differs: {0}")]
    Existing(std::path::PathBuf),
}

use crate::ObjectId;

pub(crate) fn check_cancelled(cancel: &AtomicBool) -> Result<(), FetchError> {
    if cancel.load(Ordering::Relaxed) {
        Err(FetchError::Cancelled)
    } else {
        Ok(())
    }
}

#[cfg(test)]
mod tests;

impl From<crate::packet::Error> for FetchError {
    fn from(error: crate::packet::Error) -> Self {
        match error {
            crate::packet::Error::Io(e) => Self::from(e),
            crate::packet::Error::Protocol(e) => Self::Protocol(e),
            crate::packet::Error::Limit(e) => Self::Limit(e),
            crate::packet::Error::Cancelled => Self::Cancelled,
            crate::packet::Error::Remote(e) => Self::Remote(e),
        }
    }
}

impl From<std::io::Error> for FetchError {
    fn from(error: std::io::Error) -> Self {
        match crate::transport::interruption(&error) {
            Some(crate::transport::Interruption::Cancelled) => Self::Cancelled,
            Some(crate::transport::Interruption::Deadline) => Self::Deadline,
            None => Self::Io(error),
        }
    }
}

#[cfg(feature = "http")]
impl From<crate::transport::http::HttpError> for FetchError {
    fn from(error: crate::transport::http::HttpError) -> Self {
        match error {
            crate::transport::http::HttpError::Cancelled => Self::Cancelled,
            crate::transport::http::HttpError::Deadline => Self::Deadline,
            error => Self::Http(error),
        }
    }
}

#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
impl From<crate::transport::ssh::SshError> for FetchError {
    fn from(error: crate::transport::ssh::SshError) -> Self {
        match error {
            crate::transport::ssh::SshError::Cancelled => Self::Cancelled,
            crate::transport::ssh::SshError::Deadline => Self::Deadline,
            error => Self::Ssh(error),
        }
    }
}

impl From<crate::edges::Error> for FetchError {
    fn from(error: crate::edges::Error) -> Self {
        match error {
            crate::edges::Error::Commit { id, source } => Self::Commit { id, source },
            crate::edges::Error::Tree { id, source } => Self::Tree { id, source },
            crate::edges::Error::Tag { id, source } => Self::Tag { id, source },
        }
    }
}