# Git
**`jm:git` puts git inside your program, so it works on machines that have no
git installed.**
- **No git required.** libgit2 links statically, so a shipped binary carries
its own git.
- **The everyday subset.** Open, init, clone, status, add, commit, log,
remotes, fetch, push, pull and diff, for an application that keeps its data
in a repository.
- **The platform's own security.** HTTPS uses the system's TLS, and `git@`
remotes go through the system's ssh and agent.
- **Checked against a model.** A fuzz suite runs random sequences of writes,
pushes and pulls across clones and holds every step against a model.
| At a glance | |
|---|---|
| Version | libgit2 **v1.9.7** |
| Licence | GPLv2 with a linking exception |
| Links | Statically from `git/lib`, else the system libgit2 |
| Builds with | `just libgit2` |
## Quick start
```odin
repo := must(git.open("notes"))
defer git.close(&repo)
must(git.add(repo, {"."}))
id := must(git.commit(repo, "notes: today's bullets"))
must(git.push(repo, "origin"))
```
## How it connects
| Remote | Goes through |
|---|---|
| HTTPS on Windows | WinHTTP |
| HTTPS on macOS | SecureTransport |
| HTTPS on Linux | OpenSSL, loaded at run time |
| `git@` | The platform's ssh and agent (`USE_SSH=exec`) |
> [!WARNING]
> libgit2 runs no hooks. A program that wants pre-commit checks runs them
> itself before `commit`.
Under the hood: build and ABI checks
`just libgit2` fetches libgit2 v1.9.7 into `build/src` and builds it into
`git/lib`. The package links that archive when it exists and the system
libgit2 otherwise, so a machine with the distribution's package tests without
the CMake step.
The archive is built with the platform's own HTTPS (WinHTTP, SecureTransport,
OpenSSL loaded at run time on Linux), `USE_SSH=exec` so `git@` remotes go
through the platform's ssh and agent, and the bundled zlib, regex engine and
HTTP parser.
`git/ffi_test.odin` pins every option struct's size and offsets against what
the C headers lay out. A libgit2 bump that moves a field fails a test instead
of corrupting a stack.
## The network is tested by hand
`tools/git-probe` makes the runs no test in the repository can make, against a
real remote:
- an anonymous HTTPS clone and fetch;
- a token pulling from and pushing to a private remote, from an unborn branch,
so the remote is asked for its default branch;
- a push refused as non-fast-forward;
- a wrong token, refused with a plain message;
- the same repository over `git@` through the platform's ssh.
It passed on 2026-09-28 against GitHub with the static Linux build, OpenSSL
loaded at run time. Run it again after a libgit2 bump or a change to the
transport options:
```
just libgit2
odin build tools/git-probe -collection:jm=. -out:build/debug/git-probe
build/debug/git-probe https://github.com/octocat/Hello-World.git \
https://github.com//.git "$(gh auth token)" git@github.com:/.git
```
## Tested by fuzzing
`git/fuzz` is the suite: three properties, each case a fresh bare hub with two
clones on local paths.
| Property | What it checks |
|---|---|
| `sequence` | Any order of writes, adds, commits, pushes, fetches and pulls across the clones, each step held against a model of the three trees and the commit history |
| `strings` | A generated message, path and remote name, read back byte for byte, or refused |
| `damaged` | One corrupted file under `.git`, then every question; a Fault is a fine answer, a crash is not |
Under the hood: what the model holds the suite to
Under `sequence`, a push succeeds exactly when the hub is behind. A pull
fast-forwards exactly when the clone is, and refuses when work is uncommitted.
Status is what the trees say.
`strings` is why the wrapper refuses a string with a NUL in it, rather than
letting the C boundary cut it short.
## See also
- [Fuzzing](fuzzing.md): every property suite and what they found
- [Packages](packages.md): every package in jm
- [Building and testing](building.md): the `just` recipes, `libgit2` among them