Gitoxide

What it is, and isn't

https://github.com/Byron/gitoxide

Gitoxide

(and wants to be)

https://github.com/Byron/gitoxide

Terms

  • git2 - A Rust crate âĪïļ binding to libgit2
  • libgit2
    • needs no introduction 🙇‍♂ïļ
  • Git - the mothership ðŸšĒ
    • libgit-rs - a Rust crate binding to libgit.a

Gitoxide

is

the high-performance Rust crate that acts like Git would

Gitoxide

is

the binaries gix and ein

Gitoxide

isn't

a Git replica

Gitoxide

isn't

always quite as easy-to-use as git2

Gitoxide

isn't

usable from any other language but Rust

Gitoxide

isn't

stable (yet)

Aspirations of Gitoxide ⭐

  • be the go-to Git library for anyone, usable from any language
  • be the best choice for large monorepos
  • power an entire Git forge
    • most-efficient on server
  • be the first with new (experimental) Git features

Who uses Gitoxide ðŸĪŠ

  • Cargo
  • rustsec & cargo-deny
  • starship
  • OneFetch
  • jj
  • Sapling
  • GitButler

Why would you use Gitoxide ❓

  • new project with Git integration, gitoxide features suffice
    • or you want to contribute what's missing
    • and you can figure out how to use it (gix::Repository)
  • need Git-level repository compatbility
  • need to read from untrusted repositories
  • need next-level performance and fearless concurrency
  • you deal with massive monorepos

Gitoxide includes

  • read & write thread-safe & lock-free object database
    • decode and encode all object types
      • also step-wise
  • read + write ref-database
  • lossless git-config reading and writing
  • read and write index

Gitoxide includes

  • .gitignore (with precious files) & .gitattributes
  • pathspecs: /**hello.*
  • revspecs: @~1
  • refspecs: refs/heads/*:refs/remotes/origin/*

Gitoxide includes

  • read commit-graphs and use them for traversals
    • describe commit
    • find merge-base
    • perform fetch negotiations
  • rev-parse

Gitoxide includes

  • diff
    • tree-tree
    • index-worktree
    • (missing: tree-index)
  • find untracked files/classify worktree

Gitoxide includes

  • (shallow) fetch
    • ssh
    • http/https
    • git-native
  • first checkout after fetch
    • with built-in filters
    • with custom filters (all protocols)

Gitoxide lacks

  • full status (tree-index diff missing)
  • commit (index-add, index-to-tree)
  • merge (blob-diff works🎉, but WIP)
  • push (but can build packs by object-copy)
  • rebase
  • cherry-pick
  • reset/checkout
  • blame (but WIP)

Principles during Development

Correctness

  • baseline tests with Git where possible
  • fence-post tests (at least)
  • leave notes and TODOs if corners were cut
  • fuzzing of parsers

Principles during Development

Performance

  • be mindful about allocations
  • stepwise computation when feasible
  • optional multi-threading where possible

Principles during Development

Everything Else

  • progress-reporting and are interrupt-handling
  • easy-to-use API without hiding any knob
  • async-support for networked IO
  • exhaustive docs
  • Security
    • by Eliah Kagan (and sponsored by the Radicle Foundation)

Bonus Round: API still needs improvements

Real world code written by a real API user, courtesy of GitButler.

Bonus Round: API still needs improvements

git2

#[tauri::command(async)]
pub fn git_clone_repository(repository_url: &str, target_dir: &Path) -> Result<(), Error> {
  git2::Repository::clone(repository_url, target_dir).context("Cloning failed")?;
  Ok(())
}

Bonus Round: API still needs improvements

Gitoxide

#[tauri::command(async)]
pub fn git_clone_repository(repository_url: &str, target_dir: &Path) -> Result<(), Error> {
    let url =
        gix::url::parse(repository_url.into()).context("Failed to parse repository URL")?;
    let should_interrupt = AtomicBool::new(false);
    let mut prepared_clone =
        gix::prepare_clone(url, target_dir).context("Failed to prepare clone")?;
    let (mut prepared_checkout, _) = prepared_clone
        .fetch_then_checkout(Discard, &should_interrupt)
        .context("Failed to fetch")?;
    let should_interrupt = AtomicBool::new(false);
    prepared_checkout
        .main_worktree(Discard, &should_interrupt)
        .context("Failed to checkout main worktree")?;
    Ok(())
}

Bonus Round: API still needs improvements

Gitoxide - less noisy

#[tauri::command(async)]
pub fn git_clone_repository(repository_url: &str, target_dir: &Path) -> Result<(), Error> {
    let url = gix::url::parse(repository_url.into()).map_err(anyhow::Error::from)?;
    let should_interrupt = AtomicBool::new(false);

    let (mut checkout, _outcome) = gix::prepare_clone(url, target_dir)
        .map_err(anyhow::Error::from)?
        .fetch_then_checkout(Discard, &should_interrupt)
        .map_err(anyhow::Error::from)?;
    checkout
        .main_worktree(Discard, &should_interrupt)
        .map_err(anyhow::Error::from)?;
    Ok(())
}

Bonus Round: API still needs improvements

Gitoxide - even less noisy

#[tauri::command(async)]
pub fn git_clone_repository(repository_url: &str, target_dir: &Path) -> Result<(), UnmarkedError> {
    let should_interrupt = AtomicBool::new(false);

    gix::prepare_clone(repository_url, target_dir)?
        .fetch_then_checkout(gix::progress::Discard, &should_interrupt)
        .map(|(checkout, _outcome)| checkout)?
        .main_worktree(gix::progress::Discard, &should_interrupt)?;
    Ok(())
}

Bonus Round: API still needs improvements

Comparison

#[tauri::command(async)]
pub fn git_clone_repository(repository_url: &str, target_dir: &Path) -> Result<(), Error> {
  git2::Repository::clone(repository_url, target_dir).context("Cloning failed")?;
  Ok(())
}
#[tauri::command(async)]
pub fn git_clone_repository(repository_url: &str, target_dir: &Path) -> Result<(), UnmarkedError> {
    let should_interrupt = AtomicBool::new(false);

    gix::prepare_clone(repository_url, target_dir)?
        .fetch_then_checkout(gix::progress::Discard, &should_interrupt)
        .map(|(checkout, _outcome)| checkout)?
        .main_worktree(gix::progress::Discard, &should_interrupt)?;
    Ok(())
}

benefits clients, no need to re-implement stuff users expect from you

respect configuration, but allow overrides

`gix` dev tool, `ein` client + tooling

`gix` will never be `git`, never stable, won't mimmick it

But `gitoxide` should be versatile enough to implement the majority of Git

in great contrast to `git2`

disclaimer: not entirely just yet, still `git2` in there