girt 0.1.1

An incremental, idiomatic Rust implementation of Git
Documentation
//! Native local and upload-pack object discovery, plus v0 object transfer.
//!
//! [`FetchRequest`] combines supported refspecs with a destination snapshot and explicit update
//! authorization. Its local/HTTP/SSH adapters plan from their actual advertisement, then use shared
//! validation, installation and conditional publication through [`FetchReady::finish`]. 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`] and [`receive_local`] return a validated [`ReceivedFetch`] without
//! touching a repository. Install explicitly and choose reference policy yourself, or use
//! [`FetchRequest`] for the supported workflow. These lower-level APIs do not write refs or
//! reflogs.
//!
//! 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};

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

#[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;
pub use update::{FetchUpdateError, FetchUpdateLimits};
mod workflow;
mod worktree;
pub use workflow::{
    FetchFinishError, FetchFinishFailure, FetchPlanError, FetchReady, FetchReport, FetchRequest,
    FetchUpdate, FetchUpdateKind, FetchWorkflowError,
};

mod connectivity;
mod discovery;
#[cfg(feature = "http")]
pub use discovery::discover_http;
#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
pub use discovery::discover_ssh;
pub(crate) use discovery::interpret_head;
pub use discovery::{
    ProtocolVersion, RemoteDiscovery, RemoteHead, discover, discover_local, discover_session,
};
mod import;
mod install;
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 install::NativeContents;
pub use install::{FetchInstalled, ReceivedFetch};
pub use known::KnownHistory;
pub use local::{receive_local, receive_local_with_control, receive_local_with_known};
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.
///
/// Retained buffers are O(`max_wire_bytes` + `max_decode_bytes` + `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. Thin-pack completion can retain a second pack bounded by
/// `max_pack_bytes`. 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).
    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, and delta results (default 512 MiB).
    pub max_decode_bytes: usize,
    /// Delta edges from any object to an ordinary base (default 64).
    pub max_delta_depth: usize,
    /// Entry visits during iterative 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,
        }
    }
}

/// 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("{0}")]
    Ssh(#[source] crate::transport::ssh::SshError),
    /// Sanitized smart HTTP exchange failure.
    #[cfg(feature = "http")]
    #[error("{0}")]
    Http(#[source] crate::transport::http::HttpError),
    /// Stream or filesystem failure; protocol I/O propagates interruption without retrying.
    #[error("fetch I/O: {0}")]
    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: {0}")]
    Pack(#[from] crate::ObjectReadError),
    /// Native local pack construction failed before destination publication.
    #[error("local pack construction: {0}")]
    PackWrite(#[source] crate::PackWriteError),
    /// Local annotated-tag advertisement could not be verified.
    #[error("local tag peeling: {0}")]
    Peel(#[source] Box<crate::PeelError>),
    /// Reopening the destination failed before dependency checks or publication.
    #[error("destination object snapshot: {0}")]
    Destination(#[source] crate::ObjectReadError),
    /// A local dependency failed during history preparation or installation rechecking.
    #[error("local fetch dependency {id}: {source}")]
    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: {0}")]
    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}: {source}")]
    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}: {source}")]
    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}: {source}")]
    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 },
        }
    }
}