Important
go-git2 only supports read operations for now. It can't clone, fetch, check out or commit.
go-git2 is a Go library for interacting with git repositories. It is built on libgit2 and modeled on go-git's API.
- Fast. Often 10–100× faster than go-git, with speeds close to the git CLI itself. See Performance.
- Pure Go. libgit2 is compiled to WebAssembly and translated to Go
source with wasm2go, so go-git2
builds with the standard Go toolchain and
CGO_ENABLED=0. It needs no C compiler and no Go modules beyond its own. - Memory-safe. libgit2 runs inside a sandbox, so a memory bug can't corrupt your program's memory or take over its execution. See Safety.
- Familiar API. A near drop-in replacement for go-git, with results more faithful to the git CLI. See Compatibility.
go get github.com/msuozzo/go-git2go-git2 requires Go 1.25 or later on 64-bit Linux, macOS or Windows.
32-bit platforms aren't supported yet. Its only dependency is its own
libgit2 module.
r, err := git2.PlainOpen("path/to/repo")
if err != nil {
log.Fatal(err)
}
defer r.Close()
// Every commit, on any branch, that changed go.mod, newest first.
path := "go.mod"
commits, err := r.Log(&git2.LogOptions{
All: true,
Order: git2.LogOrderCommitterTime,
FileName: &path,
})
if err != nil {
log.Fatal(err)
}
err = commits.ForEach(func(c *git2.Commit) error {
fmt.Println(c.Hash, c.Committer.When)
return nil
})PlainOpen reads a repository from disk. Open reads one from another
filesystem, such as an io/fs.FS through git2.FromFS or a
go-billy filesystem through
billyfs.New, from the separate module github.com/msuozzo/go-git2/billyfs.
The package documentation
has more examples.
go-git2's biggest wins are history queries, which go-git answers by walking all of history and diffing every commit's tree. These are times for go-git v5.19.3, go-git2 and the git CLI on django (35k commits, 7k files):
| Workload | go-git | go-git2 | Speedup | git CLI¹ | |
|---|---|---|---|---|---|
| Commits that changed a file, over full history | 39.7s | 352ms | 113× | 255ms² | |
| Commits from the last 90 days | 382ms | 4ms | 96× | 6ms | |
List the files at HEAD |
263ms | 8ms | 32× | 10ms | |
Open the repository and read the HEAD commit |
39ms | 2ms | 16× | 5ms |
The benchmark results cover every workload on repositories of various sizes, with allocations and how to reproduce them.
¹ git 2.55.0, timed including process startup. The repository has no commit-graph file, as after a fresh clone. git would use one to speed up its walks.
² With --full-history, since go-git2 doesn't prune side branches as git
does by default.
Read-only code usually needs new imports and a call to Close. Importing
go-git2 as git keeps most call sites as they are:
import (
- "github.com/go-git/go-git/v5"
- "github.com/go-git/go-git/v5/plumbing/object"
+ git "github.com/msuozzo/go-git2"
)
r, err := git.PlainOpen(path)
if err != nil {
return err
}
+defer r.Close()
since := time.Now().AddDate(0, 0, -90)
commits, err := r.Log(&git.LogOptions{
All: true,
Order: git.LogOrderCommitterTime,
Since: &since,
PathFilter: func(p string) bool { return strings.HasPrefix(p, "django/db/") },
})
if err != nil {
return err
}
-err = commits.ForEach(func(c *object.Commit) error {
+err = commits.ForEach(func(c *git.Commit) error {
fmt.Println(c.Hash, c.Committer.When)
return nil
})Note
On django, this query drops from 43s with go-git to under 50ms with go-git2.
go-git2 is a single package, so types from go-git's object, plumbing
and filemode packages, such as object.Commit and plumbing.Hash,
become git.Commit and git.Hash. Repositories open from a path or a
filesystem rather than from a go-git storer.
- Path filters compare each commit with its parents, as
git log -- <path>does. go-git compares it with the next commit it visits, which is often not a parent. LogwithAllreaches every commit on every reference. go-git can miss commits that only some references reach.- Tree entry names are returned as stored, including ones git refuses
to check out, such as
.gitor... Check them before using them as paths.
The package documentation lists every difference.
Writing (clone, fetch, checkout, commit), iterating the whole object
database (CommitObjects, TreeObjects, BlobObjects), DiffTree and
Patch, the LogOrderDFSPost and LogOrderBSF orders, the seen argument
of NewTreeWalker, Commit.ExtraHeaders, submodule checkouts and linked
worktrees (whose .git is a file), reftable, SHA-256 repositories and 32-bit
platforms.
go-git2 is libgit2 compiled to WebAssembly and translated to Go source by wasm2go. History walks, tree walks and object parsing are written in Go on top of it, and a small read-only WASI host gives libgit2 the repository's files.
Each Repository runs its own instance of the module, so its methods run
one at a time. Open several to read in parallel.
go-git2 doesn't link libgit2's C code into your program. libgit2 is compiled to WebAssembly and translated to Go, which confines it:
- Its heap, stack and globals are one byte array, and every memory access it makes is bounds-checked against that array. A memory bug in libgit2 can only interfere with libgit2's own data.
- It can't run code in your program. Every call it makes goes to one of its own functions, fixed at build time.
- It sees only the files go-git2 gives it, read-only and within the repository, and it has no network access.
- If it crashes, go-git2 returns an error wrapping
ErrFaultand discards that repository's state.
The sandbox has limits:
- libgit2's bugs are contained, not removed. One can still make go-git2 return wrong data for that repository. Hash verification, on by default, catches corrupted or tampered objects as they're read.
- An operation can hang or use a lot of memory, up to the sandbox's 4GiB limit.
- The guarantee rests on wasm2go's translation and on the Go runtime.
go-git2's own code doesn't use
unsafe. go-git2 serializes access to the sandbox, but simultaneous access isn't supported yet.
go-git2 is three modules, released separately. The root module holds the
API, libgit2/ holds the build and the generated code, and billyfs/
holds the go-billy adapter. A libgit2 version records the libgit2
release it was built from. Release policy and mechanics are in
RELEASING.md.
The libgit2 module's generated code is built in Docker from the pins in
libgit2/build/Dockerfile:
cd libgit2 && go generate ./...CI regenerates it on each change and fails if any generated byte differs from what is committed. libgit2/build describes the build.
Code outside libgit2/ is licensed under the MIT license. The
libgit2 module contains libgit2 translated to Go and, like libgit2, is
licensed under the GNU GPL v2 with a linking exception
(libgit2/LICENSE). It also contains wasi-libc
(libgit2/LICENSE.wasi-libc). Binaries that use
go-git2 embed these license notices, and Licenses returns them.