A fast terminal UI for Git, written in Zig.
1.9 MB binary · 3.6 ms startup · 8 MB memory when idle · no runtime, no toolchain, just git
Ziggity puts your whole Git workflow in one terminal window: stage single lines, commit, branch, rebase interactively, and read history without ever reaching for the mouse. Every list has a live diff preview and every action is a keystroke.
Nothing in it ever blocks. Fetch, pull, push and the slow multistep actions (merge, rebase, autosquash, bisect) run off the interface thread while navigation stays live, and you are never dropped back to the raw terminal with a "press enter to return" screen. Checkout by name tracks a remote branch in one step, a rejected push tries the safe force before the blunt one, branch diffs default to the merge base view a pull request shows, and dragging over the diff copies the exact text to your clipboard.
It follows the workflow that lazygit
made popular, and it owes that project a lot. It is not a port, though. Ziggity
is written from scratch in Zig, drives plain git subprocesses (no libgit2),
and in the places where the two tools differ, the difference is deliberate.
docs/comparison.md explains exactly where and why.
The Status panel about splash. Yes, the donut spins.
Status, Files, Branches, Commits and Stash panels with a live diff preview and a footer that follows context.
Documentation: Features · Keybindings · Configuration · Comparison with lazygit
Ziggity needs the git command on your PATH at runtime, since it drives
git as a subprocess. Install via Homebrew, grab a prebuilt binary, or build
from source.
brew install simoarpe/ziggity/ziggityThat taps simoarpe/homebrew-ziggity
and installs the binary, the short form of:
brew tap simoarpe/ziggity
brew install ziggityUpgrade with brew upgrade ziggity. Unlike a downloaded binary, a Homebrew
install is not quarantined, so it runs on macOS without a Gatekeeper prompt.
Every release ships static binaries for macOS, Linux, and Windows. No Zig toolchain required.
Grab the archive for your platform from the releases page: aarch64-macos or
x86_64-macos for macOS, x86_64-linux-musl, aarch64-linux-musl or
riscv64-linux-musl for Linux (the musl builds are fully static and run on
any distro), then:
# set VERSION to the latest release and pick your OS/arch (see the releases page)
VERSION=v0.15.0
curl -LO https://github.com/simoarpe/ziggity/releases/download/$VERSION/ziggity-$VERSION-aarch64-macos.tar.gz
tar -xzf ziggity-$VERSION-aarch64-macos.tar.gz
sudo mv ziggity /usr/local/bin/ # or any directory on your PATH
command -v ziggity # confirm it is on your PATHOn macOS the binary is not notarized, so Gatekeeper may block it on first run. Clear the quarantine flag once:
xattr -d com.apple.quarantine /usr/local/bin/ziggityDownload ziggity-v0.15.0-x86_64-windows-gnu.zip, unzip it, and put
ziggity.exe in a folder on your PATH. Requires
Git for Windows.
Note: Windows builds are cross compiled and not yet smoke tested on Windows. Treat them as experimental for now.
Each release includes a checksums.txt:
sha256sum --ignore-missing -c checksums.txt # macOS: shasum -a 256 --ignore-missing -c checksums.txtRequirements: Zig 0.16.0 and git on your PATH.
git clone https://github.com/simoarpe/ziggity
cd ziggity
zig build -Doptimize=ReleaseSafe # binary at ./zig-out/bin/ziggity
zig build test # (optional) run the test suiteThe build leaves the binary at ./zig-out/bin/ziggity. Add it to your
PATH so you can run ziggity from anywhere. Either copy it into a
directory already on your PATH:
sudo cp zig-out/bin/ziggity /usr/local/bin/or add zig-out/bin to your PATH (e.g. in ~/.zshrc or ~/.bashrc):
export PATH="$PWD/zig-out/bin:$PATH"Run ziggity inside any Git repository. Press ? for the keybindings
overlay (it opens scrolled to the panel you are on), and q to quit.
If lazygit already works for you, great: it is an excellent tool. Ziggity exists because a few everyday interactions could be quicker, more efficient, or more predictable, and because Zig makes it possible to deliver that in a tiny native binary.
Ziggity compiles to a single static binary with no runtime, no garbage
collector, and no library dependencies beyond the git you already have.
Measured on an Apple M1 Max running macOS 26.5.1, against lazygit 0.62.2 from
Homebrew, both opening the same repository (ziggity's own: 359 commits, 156
tracked files, 21 refs, a working checkout with build output on disk) at the
same terminal size, median of seven runs:
| ziggity 0.14.0-dev | lazygit 0.62.2 | |
|---|---|---|
| Binary size | 1.9 MB | 17.6 MB |
| Dynamic libraries linked | 1 | 4 |
| Process startup, median of 30 runs | 3.6 ms | 21.2 ms |
| Git subprocesses to load the repo | 16 | 25 |
| Peak git processes running in parallel | 11 | 9 |
| Network during load | none | git fetch --all |
| Resident memory once settled | 8 MB | 36 MB |
| Peak resident memory while loading | 8 MB | 36 MB |
| Git subprocesses over 10 s idle | 22 | 40 |
| Own CPU time over 10 s | 40 ms | 250 ms |
| Total CPU including git children | 0.48 s | 0.73 s |
Ziggity holds about a fifth of lazygit's memory and stays flat there from the
first paint, and both tools fetch: lazygit during load, ziggity about three
seconds later off the interface thread. The whole table
comes out of one command, docs/bench, so you can re-run it
rather than take it on trust. docs/comparison.md has the
methodology behind every row, the subprocess log, and the honest case point by
point, with demos:
- Small and Fast
- Nothing Blocks, Ever
- Copy Text Straight from the Screen
- The Mouse Works Everywhere
- At Home in Any Terminal
- Checkout by Name That Lands on a Real Branch
- A Commit Editor That Nudges the 50/72 Rule
- A Force Push That Never Dead Ends
- Diffs the Way Review Tools Show Them
- Word-Level Diff Highlighting
- Wrap Long Lines On Demand
- History Navigation with Intent
- Two Histories Side by Side
- Jump Between Repositories
- Stash Without Losing Your Working Tree
- Small Courtesies
- Staging down to the line: open a file and stage single lines or whole
hunks.
ddiscards at the same granularity, so one bad line goes away without touching the rest of the file, and a split view can show unstaged and staged side by side. - Interactive rebase you compose first: mark drop, squash, fixup, edit
and reword across a range of commits in a plan editor, then run the lot as
one rebase. Cherry picking, autosquash, custom patch building and
rebase --ontofrom a marked base are all there. - History and diffing: the real
git log --graphDAG in git's own colors, first parent jumps for walking a merge heavy history, and branch comparisons that default to the merge base, the same diff a pull request shows. - A real commit editor: a summary line and multiline body, a live count
nudging the 50/72 rule, and your repository's
prepare-commit-msghook run on open, so a branch ticket prefix finally lands in a terminal UI. - Range select anywhere:
vorshift+arrowsmark a range in any list, then one key acts on every file, commit, branch or stash in it;*selects a branch's own commits. - Stays current on its own: a quiet background fetch keeps the incoming commit count accurate, and commit hashes stay red until they reach the remote, so what is safe to rewrite is visible at a glance.
- The mouse works everywhere: click a panel or row to focus and select, click a line in the staging view to land the cursor there rather than walking down hunk by hunk, and scroll every list, dialog and the graph. Selected text copies over SSH too, through OSC 52.
- Undo and recovery:
ctrl+zwalks the last operation back through the reflog, and undoing a commit or amend returns the changes staged instead of dropping them. The stash menu can snapshot everything, untracked files included, and still leave your working tree untouched. - Explicit and configurable: fully remappable keys, themeable colors,
custom commands,
ctrl+rto switch between repositories you have opened, and an in-app prompt for HTTPS credentials so nothing takes over your terminal.
Stage or unstage single lines or whole hunks with space; d discards them instead.
c opens the commit dialog: a summary line plus an optional multiline body, and a live character count nudging the 50/72 rule.
ctrl+l opens the real git log --graph DAG in git's own colors, showing the current branch and its upstream.
docs/features.md has the full per panel breakdown and the rest of the screenshots.
Press ? in the app for the full, always current overlay. The
essentials:
| Key | Action |
|---|---|
1–5 |
Focus Status / Files / Branches / Commits / Stash (press again to cycle that panel's tabs) |
h l / arrows |
Move focus between side panels |
j k / arrows |
Move selection |
tab |
Focus the Diff panel (and back) |
z |
Maximize the Diff panel to full screen (z or esc to exit) |
[ ] |
Switch the focused panel's tabs (or staging side) |
enter / esc |
Inspect in the main panel / step back |
space |
Stage file · checkout branch · apply stash (by focus) |
c · a · d · D |
Commit · stage all · discard menu · discard all |
v · shift+arrows · * |
Start range · extend range · select branch commits |
i · ctrl+p · ctrl+l |
Rebase plan · custom patch · commit graph |
f · p · P |
Fetch · pull · push |
ctrl+z · @ · ? · q |
Undo · command log · help · quit |
Every key, including the panel key tab cycling, is in docs/keybindings.md.
Ziggity loads its defaults, then reads (each overriding the previous):
- the path in
ZIGGITY_CONFIG <repo>/.ziggity.ini
There is no auto loaded global file (no ~/.config/ziggity/, no XDG
path). To apply settings everywhere, point ZIGGITY_CONFIG at a file from
your shell profile, e.g.
export ZIGGITY_CONFIG="$HOME/.config/ziggity/config.ini", and a repo's
.ziggity.ini overrides those per repo. Without any file, settings fall back
to per repo auto detection (notably the editor).
docs/configuration.md is the annotated .ini with
every setting, key and color, plus the staging layout and editor rules.
The feature parity roadmap against lazygit is complete. Known smaller gaps:
- Redo: undo (
ctrl+z) is implemented; redoing an undo is not. - Move a custom patch to a different commit: only apply and remove from commit are implemented.
- Editing the live rebase todo mid rebase: ziggity composes the whole
plan up front in its
ieditor (with range select), so there is no paused rebase todo view to edit. - Reword in your external
$EDITOR: ziggity rewords in its own editor (r), which serves the same purpose. - Full lazygit config compatibility and score based fuzzy ranking (the current fuzzy filter matches but preserves order).
See docs/comparison.md for where ziggity deliberately
diverges, or
docs/ENHANCEMENTS_OVER_LAZYGIT.md for
the same list in short form.
zig build # debug build
zig build test --summary all # run all tests
zig build -Doptimize=ReleaseFast # optimized build| File | Responsibility |
|---|---|
src/main.zig |
Process entry point and startup error reporting |
src/app.zig |
App state, focus, selections, actions, refreshes |
src/git.zig |
Git subprocess wrapper and parsers |
src/tui.zig |
libvaxis event loop, layout, and rendering |
src/model.zig |
Owned domain models and status derivation |
src/config.zig |
Defaults and the keybinding and INI parser |
src/actions.zig |
Action names shared by the app and key layers |
Supporting modules split cohesive areas out of app.zig: staging.zig,
patch.zig, commitops.zig, rebaseplan.zig, drills.zig,
diffmode.zig, stash.zig, branches.zig, commits.zig, filetree.zig,
commitgraph.zig, editor.zig, diff.zig, credentials.zig. All Git data
is loaded through git commands rather than reimplementing Git internals.
The gif tapes under docs/assets/ regenerate with
vhs; run
bash docs/assets/demo-repos.sh first to set up the scratch repositories
they record against.
libvaxis stores each screen cell's grapheme as a slice into the source
text (Window.print does .grapheme = grapheme.bytes(segment.text); it
does not copy the bytes), and the frame is flushed after render()
returns. So any text drawn via vaxis printSegment or print must be backed
by memory that outlives render(): a string literal or an App owned buffer,
never a stack local buffer (which dangles and renders as garbage or
U+FFFD, intermittently).
In src/tui.zig:
- The local
print,printSpanandprintAnsihelpers are safe with any buffer lifetime (they map bytes through a static glyph table), so formatting into a stack buffer is fine for list rows, popups, the footer, and so on. - Only vaxis
printSegmentis by reference, and it is used only for panel and popup titles; dynamic titles must live in an App owned buffer (app.*_title_buf).
Ziggity is free and open source, and it will stay that way. There is a Ko-fi if you ever feel like buying me a coffee. Anything that comes in goes into maintenance: fixing bugs, keeping up with new Zig and Git releases, and answering issues.
There is no obligation at all. Starring the repo, filing a good bug report, or mentioning it to someone helps just as much. 💛
MIT © 2026 Simone Arpe.




