# Contributing to grebe Issues and pull requests go to the [Tangled mirror](https://tangled.org/jcollie.dev/grebe). ## Setting up Everything needed is in the Nix development shell: Zig 0.17.0, git, the Vulkan loader and validation layer, `reuse`, and `zon2nix`. ```console $ 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`): ```console $ 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: ```console $ 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](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](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.