A history browser for git, a replacement for gitk, drawn with Vulkan on Wayland.
grebe CONTRIBUTING.md
7.1 kB · 154 lines
Markdown
at main

Contributing to grebe #

Issues and pull requests go to the Tangled mirror.

Setting up #

Everything needed is in the Nix development shell: Zig 0.17.0, git, the Vulkan loader and validation layer, reuse, and zon2nix.

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

zig build check compiles everything without running anything.

GREBE_SNAPSHOT=FILE grebe reads the history, draws one frame on the CPU into FILE as a PPM, and quits: a way to look at what grebe draws without a screenshot, and what tests that compare pictures are built on.

Before sending a change #

Each of these is checked by the workflow on every push:

  • reuse lint passes.
  • zig fmt --check --exclude zig-pkg . passes.
  • zig build test passes. The tests that read history build small repositories with git, under .zig-cache/tmp, with nobody's configuration; they skip where there is no git. It runs every test twice, the second time with GREBE_TEST_OBJECT_FORMAT=sha256, so that the repositories have SHA-256 object IDs rather than SHA-1's.
  • nix build succeeds, which runs the tests again in the sandbox.
  • nix build .#checks.x86_64-linux.vm passes: grebe driven in a virtual machine (see tests/nixos/grebe.nix below).
  • zig build -Dtarget=x86_64-windows-gnu compiles, and nix build .#msi makes the installer, twice, bit for bit the same.

Windows #

grebe.exe is cross-compiled; nothing here runs on Windows. Code for one platform goes behind builtin.os.tag, and the Win32 API comes from zigwin32, imported as win32 only for a Windows build. What differs is small and kept where it differs:

  • src/history/git/Child.zig reads git's pipes. On Linux each is a file descriptor set not to block; on Windows std.process.spawn opens them for overlapped reads, and each keeps one read outstanding, whose event is what the loop waits on.
  • src/Clock.zig takes Windows' own time zone, from the IANA database built into the binary (-Dembed-tzdata, which build.zig asks of zig-datetime for Windows, and gives fluent too, so that the two are one module).
  • zig-aerie's desktop.Watcher is the registry's on Windows rather than the portal's.

The tests can run under Wine, though the ones that make repositories skip unless a Windows git is on Wine's PATH (WINEPATH):

$ zig build test -Dtarget=x86_64-windows-gnu -fwine

A release is a tag v and the version in build.zig.zon. Pushing one runs .forgejo/workflows/release.yaml, which builds the MSI, publishes it with its SHA-256 on the release, and submits the version to the JCOllie winget source as a pull request against jeff/winget-pkgs (JCOllie.Grebe), to be merged there by hand. If that submission did not happen, run .forgejo/workflows/winget.yaml by hand with the version and the MSI's ProductCode, which msiinfo export grebe-X-x64.msi Property reads out of the published installer.

A change to build.zig.zon regenerates build.zig.zon.nix in the same commit:

$ nix develop -c zon2nix --17 --nix=build.zig.zon.nix build.zig.zon

How it is laid out #

  • src/history/ — what is browsed. History.zig holds the commits; Loader.zig is the seam a backend sits behind (ADR 0001); git/ is the one backend there is, which runs git log and parses what it writes; git/Refs.zig is the only thing in it that writes.
  • src/find/ — what the find bar looks for, and what grebe matches itself; history/git/Find.zig is what git answers.
  • src/graph/ — where each commit's node and edges go in the graph, worked out a row at a time, in lanes rather than pixels.
  • src/ui/ — what is drawn: the panes and their dividers, the commit list and its graph, the details and diff, the file list, the menus and dialogs, the fonts, the colors.
  • src/config/ — the configuration: Config.zig, what a file decodes to; load.zig, finding it and evaluating it, and again when the desktop's settings change.
  • src/input/ — key bindings: gitk's, and the configuration's over them.
  • src/Clock.zig — the user's time zone, and dates on it.
  • src/App.zig — the window and the loop that runs it.
  • src/testing/ — repositories for the tests to read.
  • tests/pointer.py — a pointer for a headless sway, which has none: moves, clicks and waits, from one virtual pointer kept for the whole script.
  • dist/ — what is installed beside the binary: the icon at each size, and the desktop entry, filled in with where the binary is by -Dbindir; dist/windows/grebe.wxs, the MSI's source; and dist/notices/, license texts the Windows build's notices need.
  • tools/ — programs the build runs: ico.zig packs the icon's PNGs into the one grebe.exe carries, notices.zig writes the notices of everything grebe.exe is built with, and product_code.zig reads the MSI's ProductCode out for the winget manifest. All three are harrier's.
  • msi.nix — the MSI, as a Nix package: nix build .#msi.
  • tests/nixos/grebe.nix — grebe in a NixOS virtual machine, on a headless sway drawing with lavapipe, reading a repository made for it: keys, a configured one among them, find, a click, the primary selection, the desktop going from dark to light, and a configuration with a mistake in it; and a repository with SHA-256 object IDs. nix build .#checks.x86_64-linux.vm runs it; it needs KVM, and takes about a minute once everything is built.
  • tests/portal.py — a stand-in for the desktop portal's settings, for a test with no desktop: it serves a color scheme, and changes it on a schedule. Run it on the session bus of a dbus-run-session, with Python's dbus-next.

Text #

Nothing a user reads is written in the code. Every message is in locales/en-US.ftl, named by an id, and reached through src/l10n.zig: l10n.text(id) for one with no arguments, l10n.format or l10n.write with arguments. A number goes in as a number (l10n.num), so that the language decides how it is written and which plural it takes. Add the English message with the code that shows it, and a comment saying what its arguments are. Logs stay in English.

The history's own side of the seam says nothing in words: what went wrong is a status or an error, and the interface picks the message.

Conventions #

  • Use the words in GLOSSARY.md, and add to it when a new thing gets a name. A decision that is hard to reverse, would surprise a reader, and was a real choice gets an ADR in docs/adr.
  • Nothing on the UI side of the seam knows git's output format, or that a backend is a process.
  • grebe writes to the repository only to make, rename, delete and reset branches and to make and delete tags, through history/git/Refs.zig, when the user has confirmed it in a dialog (ADR 0003). Anything more is a new decision.
  • Doc comments carry the explanation: why a thing is the way it is.