- Zig 100%
plumbuz
A Git plumbing library for Zig. Pure Zig, no subprocesses, no C dependencies.
Designed for embedding in applications — editors, TUIs, dev tools — where you need direct access to Git internals without shelling out to git. Comes with a small CLI for testing and exploration.
Features
Object store — read and write loose objects (blob, tree, commit, tag) with SHA-1 and SHA-256 support. Pack file reading with index caching.
Refs — resolve symbolic refs, read HEAD, list branches
Index — parse, mutate, and write .git/index (v2–v4)
Staging & commits — full git add + git commit workflow with automatic timezone detection, git reset (unstage) support
Diff — tree-to-tree comparison and line-level Myers diff
Log — commit walker with priority queue (reverse chronological, dedup)
Status — working tree status: staged changes, unstaged modifications, untracked files
Blame — line-level attribution via first-parent walk and diff mapping
.gitignore — hierarchical pattern matching (.git/info/exclude, root, per-directory)
Library usage
Add plumbuz as a Zig dependency. plumbuz follows Zig 0.16's explicit-I/O pattern: pass a std.Io (typically from std.process.Init.io) into Repository.open, after which the handle threads it through internally.
conststd=@import("std");constplumbuz=@import("plumbuz");pubfnmain(init:std.process.Init)!void{constio=init.io;constallocator=init.gpa;// Open a repositoryvarrepo=tryplumbuz.Repository.open(allocator,io,".");deferrepo.close();// Stage and unstage filestryrepo.addFile("src/main.zig");tryrepo.unstageFile("src/main.zig");// Get statusvarstatus=tryrepo.getStatus();deferstatus.deinit();for(status.files)|f|{std.debug.print("{s}: index={s} worktree={s}\n",.{f.path,@tagName(f.index_status),@tagName(f.worktree_status),});}// Walk commit historyvarwalker=tryrepo.logFrom("HEAD");deferwalker.deinit();while(trywalker.next())|*entry|{deferentry.deinit();varhex_buf:[64]u8=undefined;consthex=entry.oid.toHex(&hex_buf);std.debug.print("{s} {s}\n",.{hex[0..7],entry.commit.message});}// Blame a filevarresult=tryrepo.blameFile("src/main.zig");deferresult.deinit();for(result.lines)|line|{varhex_buf:[64]u8=undefined;consthex=line.oid.toHex(&hex_buf);std.debug.print("{s} ({s} {d}) {s}\n",.{hex[0..7],line.author.name,line.line_no,line.line,});}// Read objects directlyconstoid=tryrepo.resolveRef("HEAD");varobj=tryrepo.readObject(oid);deferobj.deinit();}CLI
$ zig build
$ ./zig-out/bin/plumbuz help
Usage: plumbuz <command> [args]
Commands:
add Stage a file
reset Unstage a file
commit Record changes to the repository
status Show working tree status
log Show commit history
branch List branches
diff Show changes between HEAD~1 and HEAD
blame Show line-by-line attribution
cat-file Display object contents
help Show this help
Examples
$ plumbuz status
On branch main
Changes not staged for commit:
modified: src/index.zig
Untracked files:
notes.txt
$ plumbuz add src/index.zig
$ plumbuz log -n3
commit a1b2c3d4...
Author: Alice <alice@example.com>
Date: Mon Mar 3 10:30:00 2026 +0100
Add staging workflow
$ plumbuz branch
* main
feature/parser
$ plumbuz blame src/main.zig
a1b2c3d (Alice 1) const std = @import("std");
f8e9d0c (Bob 2) const plumbuz = @import("plumbuz");
a1b2c3d (Alice 3) ...
$ plumbuz reset src/index.zig
Unstaged 'src/index.zig'
$ plumbuz cat-file a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
commit (size 231)
tree 4b825dc642cb6eb9a060e54bf899d15644e68cb5
parent ...
Building
Requires Zig 0.16.
zig build # build CLI
zig build test # run test suite
Status
Early development. Read path is solid, write path covers staging and commits. Currently supports:
- SHA-1 (default) and SHA-256 object formats (auto-detected from
.git/config) - Loose object read/write and pack file reading (OFS_DELTA/REF_DELTA)
- Hierarchical
.gitignore(.git/info/exclude, root, per-directory) - Single-file
addandreset(unstage) (no directory/glob staging yet) - Commit creation with system timezone detection
- Line-level blame (first-parent walk)
License
MIT