A history browser for git, a replacement for gitk, drawn with Vulkan on Wayland.
  • Zig 89.7%
  • Nix 7.5%
  • Fluent 1.5%
  • Python 1.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jeffrey C. Ollie 85b6eda5cf
All checks were successful
release / release (push) Successful in 19m48s
release / winget (push) Successful in 1m55s
test / windows (push) Successful in 47m8s
test / test (push) Successful in 15m33s
test / vm (push) Successful in 5m31s
Bump version to 0.3.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BivPw4GP4cNYFxvVdC256f
2026-10-27 00:44:58 -05:00
.forgejo/workflows Release without pushing to the Nix cache 2026-10-26 21:51:31 -05:00
dist Build for Windows, and package it as an MSI for winget 2026-10-26 11:32:55 -05:00
docs/adr Delete tags from a tag's label 2026-10-26 20:31:39 -05:00
LICENSES Build for Windows, and package it as an MSI for winget 2026-10-26 11:32:55 -05:00
locales Delete tags from a tag's label 2026-10-26 20:31:39 -05:00
src Delete tags from a tag's label 2026-10-26 20:31:39 -05:00
tests Test with repositories whose object IDs are SHA-256 2026-10-26 00:11:53 -05:00
tools Build for Windows, and package it as an MSI for winget 2026-10-26 11:32:55 -05:00
.gitignore Show a repository's history, streamed from git log, in a window 2026-10-25 15:03:11 -05:00
build.zig Build for Windows, and package it as an MSI for winget 2026-10-26 11:32:55 -05:00
build.zig.zon Bump version to 0.3.1 2026-10-27 00:44:58 -05:00
build.zig.zon.nix Follow every Windows theme switch, not just the first 2026-10-27 00:44:06 -05:00
CONTRIBUTING.md Release without pushing to the Nix cache 2026-10-26 21:51:31 -05:00
flake.lock Show a repository's history, streamed from git log, in a window 2026-10-25 15:03:11 -05:00
flake.nix Build for Windows, and package it as an MSI for winget 2026-10-26 11:32:55 -05:00
GLOSSARY.md Make, delete and reset branches from the menus 2026-10-26 20:12:23 -05:00
msi.nix Build for Windows, and package it as an MSI for winget 2026-10-26 11:32:55 -05:00
package.nix Build for Windows, and package it as an MSI for winget 2026-10-26 11:32:55 -05:00
README.md Delete tags from a tag's label 2026-10-26 20:31:39 -05:00
REUSE.toml Build for Windows, and package it as an MSI for winget 2026-10-26 11:32:55 -05:00

grebe

A history browser for git, meant to replace gitk, in Zig 0.17. It draws its own interface on the GPU with zig-aerie, the window, renderer and text stack of the terminal emulator harrier.

$ grebe                       # the current branch
$ grebe --all                 # every branch and tag
$ grebe main..topic -- src/   # what topic adds to main, under src/

It is new. What it does today is the start of what gitk does; see What it does. It runs on Linux under Wayland, and on Windows; see Windows.

What it does

  • Takes gitk's arguments. Whatever follows grebe is handed to git log as it is: ranges, --all, --author=, --date-order, -- paths. git chooses and orders the commits, and with paths names each commit's nearest ancestor that is shown as its parent, so they mean exactly what they mean to git, .mailmap and replace refs included. The order is topological, as in gitk, unless you ask for another.
  • Streams the history. Commits are shown as git writes them, so the first screenful of a large repository is up while the rest is still coming. The window stays live while it loads; a repository of 88,000 commits is read in under a second.
  • A graph beside the commits: each line of descent in a lane of its own, merges opening lanes and forks closing them, as gitk draws it. A line keeps its color from where it starts, so more history arriving never repaints what is already there.
  • A commit list with the subject, the author and the date, in your own time zone (TZ, or /etc/localtime). Only the rows in view are ever laid out or drawn.
  • Branches and tags are labels ahead of the subject of the commit they point at, as in gitk: green for a local branch, with a heavier border for the one checked out; orange for a remote's; yellow for a tag. They are the refs git log decorates with, so log.excludeDecoration hides any you would rather not see.
  • Selecting a commit shows, below the list, its details — author, committer, parents, the local branches it is on, the nearest tags before it and after it (gitk's Follows and Precedes), the whole message — and its diff, with the files it changes listed beside them. Clicking a file scrolls the diff to it, and the file the diff is showing is marked. A merge's diff is the combined one gitk shows: only what the merge did that neither side did. The bar above the diff sets how many lines of context it has, and whether changes only in whitespace count.
  • Changes not committed yet are rows of their own at the top, leading down to HEAD, as in gitk: one for the working tree, with a red node, and one for the index, with a green one. Selecting one shows its diff. They are there when the history starts from HEAD, and with paths, only for changes to those paths.
  • Find, in the bar under the commit list, as in gitk: commits containing some text — in the headline, the comments, the author, the committer, or all of them, exactly, ignoring case, or as a regular expression — or touching paths, adding or removing a string (git log -S), or changing lines that match (git log -G). Matches are lit as they are found; Enter or Ctrl-G goes to the next below the selected commit, and Shift-Enter or Ctrl-Shift-G to the one above. / or Ctrl-F goes to the find bar, and Escape back to the list. The headline and the author are searched by grebe as you type; everything else by git, with the same revision arguments.
  • The panes are gitk's, and the dividers between them can be dragged. Each has a scrollbar, and the diff one along the bottom as well, for lines wider than the pane: drag the thumb, or click either side of it for a page. Shift and the wheel, or a touchpad, scroll the diff sideways.
  • Selecting text in the details and the diff: drag across it, or double-click a word, or triple-click a line. What is selected goes in the primary selection, for a middle click, and Ctrl-C copies it to the clipboard. Dragging past the top or the bottom scrolls.
  • Selecting a commit shows its whole ID, and which row it is, at the right of the status bar, and puts the ID in the primary selection, ready for a middle click in a terminal, as gitk does.
  • A menu on each commit, with a right click: copy its ID, or a summary of it as git writes one (--pretty=reference); diff it against the selected commit, either way round; mark it, and diff the marked commit against another; create a new branch or a tag there; or reset the branch checked out to it, as gitk does. A tag with a message is annotated, and signed if git is configured to sign tags; one without is lightweight. A reset can be soft, mixed or hard, and a hard one discards changes that have not been committed.
  • A menu on each local branch, with a right click on its label: copy its name, rename it, or delete it. git deletes a branch only if it is merged. If it is not, grebe shows why and offers to delete it anyway.
  • A menu on each tag, with a right click on its label: copy its name, or delete it from this repository (not from any remote).
  • Keys: ↑ and ↓ or k and j move the selection, Page Up and Page Down a page, Home and End to either end; Space and b page through the diff; / or Ctrl-F finds, and Ctrl-G finds again; F5 reads the history again, keeping the selected commit selected where it was in the list, and the diff where it was (or, if that commit is gone, the row it was on); Ctrl-C copies; Ctrl-Q quits. Any of them can be bound to other keys; see Configuration.
  • Light or dark, as the desktop prefers, in the desktop's own fonts. grebe asks the XDG desktop portal, and follows it when the preference changes while grebe is open. With no portal, or no preference, it is light, in fontconfig's sans-serif and monospace.
  • A title bar of its own on a desktop whose compositor draws none, such as GNOME.

grebe changes the repository only from the menus above: making, renaming, deleting and resetting branches, and making and deleting tags. Each change is confirmed in a dialog first, and is made by running the git command a user would type, so git checks it as it would on the command line. The window waits while git runs, including while gpg asks for a passphrase to sign a tag.

Building and running

With Nix:

$ nix run git+https://git.jcollie.dev/jeff/grebe.git -- --all

or, to work on it, everything is in the development shell:

$ nix develop
$ zig build test
$ zig build run -- --all

Without Nix you need Zig 0.17.0 to build it, and at run time git, a Vulkan loader and driver, and a Wayland compositor. git is the one on your PATH, so your configuration applies; the Nix package brings its own only for when there is none.

grebe shows the repository the current directory is in, or the one --repository=DIR names. Installed — the Nix package does it, or zig build -Dbindir=/usr/local/bin --prefix /usr/local — it comes with its icon and a desktop entry. The entry is not in the application menus, since grebe started from one would have no repository to show; it is offered instead for opening a folder from a file manager, and it is what gives grebe's windows their icon.

On Windows

grebe.exe is cross-compiled, from Linux or anywhere else Zig runs, for the GNU ABI on Zig's own MinGW headers, so no Visual Studio is needed:

$ zig build -Dtarget=x86_64-windows-gnu

The installer is an MSI, built by zixl from dist/windows/grebe.wxs as one more step of the build:

$ zig build msi -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseSafe
$ nix build .#msi

Either puts grebe-VERSION-x64.msi in zig-out or result, with its ProductCode beside it in grebe-VERSION-x64.msi.ProductCode. Built with Nix, one commit makes one MSI, bit for bit. It installs grebe for all users, in Program Files\grebe, with the directory on the PATH and Open in grebe on the menu Explorer shows for a folder. It adds nothing to the Start menu, for the same reason the desktop entry is not in the application menus. An MSI replaces any grebe of its version or an older one; it will not install over a newer one.

Installing with winget

Every release is also published to the JCOllie winget source, as JCOllie.Grebe. Add the source once, from an elevated prompt:

> winget source add -n JCOllie -a https://winget.jcollie.dev/ -t Microsoft.PreIndexed.Package --trust-level trusted

then install grebe, and later upgrade it, as any other package:

> winget install JCOllie.Grebe -s JCOllie
> winget upgrade JCOllie.Grebe -s JCOllie

The MSI is not signed. winget marks an installer from an untrusted source as downloaded from the internet, and Microsoft Defender SmartScreen then stops an unsigned one with "Windows protected your PC". A source added with --trust-level trusted skips that; one added without it can be removed with winget source remove JCOllie and added again. An MSI downloaded from the release page gets the same warning, and Run anyway installs it.

Windows

grebe on Windows is the same program in a Win32 window, drawing with Vulkan through the GPU driver's own loader. What differs:

  • git is the one on the PATH, which Git for Windows puts there. grebe brings none.
  • Light or dark follows Settings > Personalization > Colors, and changes when it does; the fonts are Windows' own.
  • Times are in the time zone Windows is set to, or the one TZ names, from a copy of the IANA database built into grebe.
  • There is no primary selection. Selecting a commit keeps its ID for a middle click inside grebe only; Ctrl-C, or Copy ID on the commit's menu, puts it on the clipboard.
  • What goes wrong at the start — no repository there, no git, a mistake in the configuration — is said in a message box, since grebe started from Explorer has no terminal to say it on.
  • The configuration is %APPDATA%\grebe\config.struthio.

Configuration

grebe needs none. If there is a file at ~/.config/grebe/config.struthio ($XDG_CONFIG_HOME/grebe/config.struthio; on Windows, %APPDATA%\grebe\config.struthio), it is read, in struthio: Zig's ZON with let, if, @include of other files and a context to decide by. GREBE_CONFIG names another file instead, and set but empty, none.

.{
    // The interface's font and the diff's; unset is the desktop's.
    .font = .{ .mono = .{ .family = "Iosevka", .size = 11 } },
    // Any of the palette's colors, by name, over the light or dark one.
    .colors = if (ctx.appearance.color_scheme == .dark)
        .{ .list_background = "#101418", .graph = .{ "#58a6ff", "#ff7b72" } }
    else
        .{ .selection_background = ctx.appearance.accent_color },
    // How a diff is shown at first.
    .diff = .{ .context = if (ctx.repository == "/src/linux") 5 else 3 },
    // Over gitk's keys; `.unbind` takes one away.
    .keybind = .{
        .{ .keys = "ctrl+r", .action = .reload },
        .{ .keys = "ctrl+q", .action = .unbind },
    },
}

What the file can decide by is ctx: ctx.appearance — the desktop's color_scheme, accent_color, contrast and reduced_motion — ctx.repository, the working tree being shown, ctx.hostname and ctx.os. @env("NAME") is an environment variable, and @setting("namespace", "key") any other setting the desktop publishes through the portal. When the desktop's settings change, the file is evaluated again, and grebe draws as it then says. .language chooses the language grebe speaks, in place of the environment's. The fields, the palette's color names and the actions are in src/config/Config.zig.

A mistake in the file is said on the terminal, with where it is, and grebe does not start.

Translations

Everything grebe says is in Project Fluent files in locales/, one per language, built into the program. It speaks the language the environment asks for — LANGUAGE, then LC_ALL, LC_MESSAGES and LANG — or the nearest it has, and English for anything a translation leaves out. English is the only one there is so far; a new one is a copy of locales/en-US.ftl, translated, and a line in build.zig.

What git says, when it says something went wrong, is shown as git said it, in whatever language git speaks.

Design

The words grebe uses for what it shows are in GLOSSARY.md, and the decisions behind it in docs/adr:

  • ADR 0001: grebe runs git rather than linking libgit2, behind a seam that carries commits and diffs as data, so that another backend can be put behind it.
  • ADR 0002: harrier's window, renderer and text stack became a library, zig-aerie, rather than being copied.
  • ADR 0003: grebe makes, renames, deletes and resets branches, and makes and deletes tags, from its menus, through git, and writes nothing else.

Where this lives

The code is at https://git.jcollie.dev/jeff/grebe, and mirrored on Tangled at https://tangled.org/jcollie.dev/grebe:

git clone https://git.jcollie.dev/jeff/grebe.git

Report issues and send pull requests on the Tangled mirror. See CONTRIBUTING.md for what to know before changing it.

License

MIT; see LICENSES/MIT.txt. The project follows REUSE: every file says what it is licensed under. The icon, in dist/icons/, is a photograph of a western grebe by Jeffrey C. Ollie, under CC BY 4.0; see LICENSES/CC-BY-4.0.txt.

References cited