Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

crates/gunnar-client/src/lib.rs

//! # gunnar-client: the git CLIENT half
//!
//! **This crate assumes a normal operating system, always, and uses it.** Real
//! working trees, `~/.gitconfig`, `~/.ssh/known_hosts`, `$SSH_AUTH_SOCK`,
//! checkouts onto disk, `gix::Repository`. None of that is abstracted, because
//! abstracting it buys nothing: the no-OS rule is
//! the **server's** constraint and only the server's. Conflating the two makes
//! the client needlessly hard and the server accidentally easy.
//!
//! `gunnar-client` must never become a dependency of a server crate. That edge
//! is what would drag gix's porcelain, and with it the whole filesystem
//! assumption, into an appliance image.
//!
//! ## What is reused, and what is new
//!
//! | | where it comes from |
//! |---|---|
//! | ssh url parsing, `ls-remote`, the SSH channel (fetch **and** push), clone/fetch | **`nornir-git`**, unchanged — reused, not written |
//! | pkt-line framing | **`gunnar-pktline`** |
//! | the pack writer | **`gunnar-pack`**, the same one the server serves with |
//! | status (the porcelain walk) | **`nornir-git`**, `gitio::porcelain_status_in` |
//! | log, diff, commit, branch, checkout, tag | **`gix`** porcelain |
//! | **send-pack (push)** | **new here.** gix has none; see [`send_pack`] |
//! | **`gunnar mirror`** | **new here**, in [`mirror`] |
//!
//! ## The one gap in gix, and the shape it forces
//!
//! Verified in gix 0.86: `gix-protocol` has `fetch/`, `ls_refs.rs` and
//! `handshake/`, and nothing for push. So [`send_pack`] puts `receive-pack`'s
//! conversation on the wire itself, over [`gunnar_pktline`] and [`gunnar_pack`],
//! both of which the server needs anyway, which is why that cost is close to
//! zero (one implementation).
//!
//! ## Everything runs over a `(Read, Write)` pair
//!
//! [`transport::Transport`] is the only seam, and it exists for one reason
//! beyond tidiness: it makes the wire code provable against **the real `git`
//! binary**. `git receive-pack` and `git upload-pack` are ordinary programs that
//! speak this protocol on stdin/stdout, so the test suite drives them over pipes
//! and asserts that stock git, not a second gunnar, accepts what this crate
//! sends (`tests/real_git_push.rs`, `tests/real_git_fetch.rs`). A test where
//! gunnar's client agrees with gunnar's server would prove nothing; both halves
//! can be wrong the same way — only stock `git` is an honest judge.
//!
//! ## Example
//!
//! ```no_run
//! use gunnar_client::{push, PushOptions, RefSpec};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let repo = gix::open(".")?;
//! let specs = RefSpec::parse_all(&["HEAD:refs/heads/main"])?;
//! let outcome = push(&repo, "git@host:ns/repo.git", &specs, &PushOptions::new())?;
//! if let Some(why) = outcome.report.as_ref().and_then(|r| r.failure_summary()) {
//!     eprintln!("the remote refused: {why}");
//! }
//! # Ok(()) }
//! ```

#![deny(missing_docs)]
#![forbid(unsafe_code)]
#![doc(
    html_logo_url = "https://gunnar.rs/download/gunnar.webp",
    html_favicon_url = "https://gunnar.rs/download/favicon.svg"
)]

// ── the two halves, and the `porcelain` feature between them ────────────────
//
// Everything gated below opens a `gix::Repository` on a real filesystem.
// Everything ungated is the WIRE: pkt-line in, pkt-line out, object ids as
// `gix-hash` values, and a russh channel underneath. `Cargo.toml`'s feature
// block carries the accounting; the short version is that `gunnar-server`'s
// replication needs only the wire, and paying +128 packages to say so was
// what kept the appliance from replicating at all.

/// Worktree filter policy. Porcelain: it reads `.gitattributes` off a checkout.
#[cfg(feature = "porcelain")]
pub mod attributes;
/// Clone into a worktree. Porcelain.
#[cfg(feature = "porcelain")]
pub mod clone;
pub mod error;
/// Fetch and `ls-remote` against an open repository. Porcelain.
#[cfg(feature = "porcelain")]
pub mod fetch;
pub mod filter;
pub mod mirror;
/// Object planning over an open repository. Porcelain.
#[cfg(feature = "porcelain")]
pub mod objects;
/// The `gix::Repository` → `gunnar-pack` object-source bridge. Porcelain.
#[cfg(feature = "porcelain")]
pub mod pack_source;
/// `status` and friends. Porcelain, by name.
#[cfg(feature = "porcelain")]
pub mod porcelain;
/// `push`, which builds a pack out of a worktree's loose objects. Porcelain.
#[cfg(feature = "porcelain")]
pub mod push;
pub mod send_pack;
/// Shallow/deepen arguments for a fetch. Porcelain.
#[cfg(feature = "porcelain")]
pub mod shallow;
pub mod ssh;
pub mod transport;
/// The two wire arguments gix's fetch does not expose. Porcelain: it exists for
/// `fetch`.
#[cfg(feature = "porcelain")]
pub(crate) mod wire;

#[cfg(feature = "porcelain")]
pub use attributes::{guard_checkout, unfiltered_paths, FilterReport, FilteredPath};
#[cfg(feature = "porcelain")]
pub use clone::{clone_ssh_with, clone_with_io, CloneReport};
pub use error::{chain, Error, Result};
#[cfg(feature = "porcelain")]
pub use fetch::{
    check_fetch_refspec, check_fetch_refspecs, clone_or_fetch, clone_or_fetch_with, fetch_ssh,
    fetch_ssh_with, fetch_ssh_with_opts, fetch_with_io, fetch_with_io_opts, ls_remote,
    ls_remote_output, ls_remote_with, ls_remote_with_io, AdvertisedRef, FetchOptions, FetchOutcome,
};
pub use filter::FilterSpec;
pub use mirror::{plan_ref_sync, RefChange};
#[cfg(feature = "porcelain")]
pub use mirror::{Mirror, MirrorRegistry, MirrorStatus, SyncReport};
#[cfg(feature = "porcelain")]
pub use objects::{plan_objects, ObjectPlan};
#[cfg(feature = "porcelain")]
pub use push::{
    mirrored_refs, push, push_mirror, push_mirror_over, push_over, PushOptions, PushOutcome,
    RefSpec,
};
pub use send_pack::{
    read_advertisement, send_pack, Advertisement, Capabilities, PushCommand, PushReport, RefStatus,
    RemoteRef, SendPackOptions,
};
#[cfg(feature = "porcelain")]
pub use shallow::{DepthArgs, Shallow};
pub use ssh::{connect_receive_pack, connect_upload_pack, HostKeyPolicy, SshChannel, SshOptions};
pub use transport::{IoPair, Transport};

/// The `agent=` string this client identifies itself with.
pub fn agent() -> String {
    format!("gunnar/{}", env!("CARGO_PKG_VERSION"))
}