Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

365 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ziggity icon

ziggity

A fast terminal UI for Git, written in Zig.

Zig 0.16 License: MIT Platform

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.

Ziggity, the about splash with a spinning donut

The Status panel about splash. Yes, the donut spins.

Ziggity, the main view

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

Installation

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.

Homebrew (macOS / Linux)

brew install simoarpe/ziggity/ziggity

That taps simoarpe/homebrew-ziggity and installs the binary, the short form of:

brew tap simoarpe/ziggity
brew install ziggity

Upgrade with brew upgrade ziggity. Unlike a downloaded binary, a Homebrew install is not quarantined, so it runs on macOS without a Gatekeeper prompt.

Prebuilt Binaries

Every release ships static binaries for macOS, Linux, and Windows. No Zig toolchain required.

macOS / Linux

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 PATH

On 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/ziggity

Windows

Download 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.

Verify the Download (Optional)

Each release includes a checksums.txt:

sha256sum --ignore-missing -c checksums.txt   # macOS: shasum -a 256 --ignore-missing -c checksums.txt

Compile from Source

Requirements: 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 suite

The 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"

Quick Start

Run ziggity inside any Git repository. Press ? for the keybindings overlay (it opens scrolled to the panel you are on), and q to quit.

Why Ziggity

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:

Features

  • Staging down to the line: open a file and stage single lines or whole hunks. d discards 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 --onto from a marked base are all there.
  • History and diffing: the real git log --graph DAG 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-msg hook run on open, so a branch ticket prefix finally lands in a terminal UI.
  • Range select anywhere: v or shift+arrows mark 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+z walks 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+r to switch between repositories you have opened, and an in-app prompt for HTTPS credentials so nothing takes over your terminal.

Line level staging view

Stage or unstage single lines or whole hunks with space; d discards them instead.

Commit message dialog

c opens the commit dialog: a summary line plus an optional multiline body, and a live character count nudging the 50/72 rule.

Commit graph viewer

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.

Keybindings

Press ? in the app for the full, always current overlay. The essentials:

Key Action
15 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.

Configuration

Ziggity loads its defaults, then reads (each overriding the previous):

  1. the path in ZIGGITY_CONFIG
  2. <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.

Status & Roadmap

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 i editor (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.

Development

zig build                                  # debug build
zig build test --summary all               # run all tests
zig build -Doptimize=ReleaseFast           # optimized build

Source Layout

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 Gotcha: Cells Store Graphemes by Reference

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, printSpan and printAnsi helpers 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 printSegment is by reference, and it is used only for panel and popup titles; dynamic titles must live in an App owned buffer (app.*_title_buf).

Sponsor

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.

Support Ziggity on Ko-fi

ko-fi.com/simoarpe

There is no obligation at all. Starring the repo, filing a good bug report, or mentioning it to someone helps just as much. 💛

License

MIT © 2026 Simone Arpe.

About

⚡️ Ziggity an ultra fast, keyboard driven terminal UI for Git, written in Zig.

Topics

Resources

Stars

Watchers

Forks

Releases

Sponsor this project

Packages

Contributors

Languages