Hoff is a bot for GitHub that enforces a clean history, and ensures that master always builds.
Hoff intends to replace the merge button in the GitHub UI. Hoff integrates changes into master using a rebase. This keeps the history clean, free of random fork points and merge commits. (TODO: In the future Hoff will also enforce a commit message format.)
Furthermore, Hoff implements the Not Rocket Science Principle of software engineering:
Automatically maintain a repository of code that always passes all the tests.
The application watches a repository for new pull requests. Once a pull request has been approved (through an LGTM comment left by a reviewer), it integrates the changes into master, and pushes those to a testing branch. When CI reports a successful build for this branch, master is forwarded to it. If the build fails, the commits never make it into master, keeping the build green at all times.
Supposing Hoff is set up to listen for the comment prefix @hoffbot
with a
matching GitHub user, you use it by commenting on a PR with any of the
following commands:
@hoffbot merge
: rebase then merge;@hoffbot merge and tag
: rebase, merge then tag.@hoffbot merge and deploy
: rebase, merge, tag then deploy;
For all the commands, Hoff will wait for the builds to pass after rebasing and before merging. When the PR is merged, GitHub closes the PR as merged and, when configured to automatically do so, deletes the PR branch.
Hoff does not actually do the deploying. It just adds a special marker to the tag message indicating to the CI job that the tag should be deployed.
On Fridays, by default, Hoff refuses to do the above actions. To force merges
on Fridays, simply add on friday
at the end of your commands, like so:
@hoffbot merge on friday
;@hoffbot merge and tag on friday
.@hoffbot merge and deploy on friday
;
See the installation guide if you want to run a self-hosted version of Hoff.
TODO: Write a proper guide to build a package. TODO: Publish official deb packages?
Hoff is written in Haskell and builds with Stack:
$ stack setup
$ stack build
$ stack test
To run the application locally
you first need to create an appropriate config.json
file:
$ cp doc/example-dev-config.json config.json
Edit config.json
to match your required settings.
You can generate a personal access token in the
"personal access tokens" tab of GitHub settings.
Give it a 7 days expiration and access to just "repo"s.
$ mkdir -p run/state
$ stack exec hoff config.json
If using Nix, you may get a ssh: command not found
error
-- just pass --no-nix-pure
to stack exec
to avoid it:
$ nix run -c stack exec hoff config.json --no-nix-pure
You can then access http://localhost:1979 to see the open PRs and build queue.
The build queue is fetched through GitHub's web interface, so you will be able to see the full list right away.
Comments and build statuses are only sent in though a webhook.
While running without a public IP address,
GitHub will have no way of notifying your Hoff instance.
You can use some of the scripts in the tools/
folder
to simulate those:
$ ./tools/comment deckard 31337 @hoffbot merge
$ ./tools/build-status c033170123456789abcdef0123456789abcdef01
The tests of Hoff are extensive, you may be able to get by just by running them
when making changes to the code. To run a specific test, use --match
giving
part of the test title:
$ nix run -c stack test --ta '--match "part of the test title"'
The implementation uses free monads and some of the tests replace lower level functionality with mocks.
To run Hoff on a server, you can build a self-contained squashfs file system image with Nix:
$ nix build --out-link hoff.img
$ cp package/example-config.json config.json
$ vi config.json # edit the file appropriately
$ sudo systemd-nspawn \
--ephemeral \
--image hoff.img \
--bind-ro=$PWD:/etc \
-- /usr/bin/hoff /etc/config.json
The image includes Hoff and all of its dependencies (Git, SSH). You can run it with systemd. TODO: Make it work with portablectl.
You can also build Hoff as a deb package by running:
./package/build-and-ship.sh
Hoff reports all build links for builds that could trigger success/failure. So sometimes Hoff may comment:
CI job started.
(30 seconds later)
CI job started.
If you look closely, you will see that the subsequent links are different.
This only happens when there are two branches pointing to the same commit hash that are building at the same time. Multiple CI jobs are created and we are notified of changes in all.
In a regular Hoff workflow, this may happen if the following conditions are
met: a PR contains a single commit; it can be fastforwarded on top of master;
and the @hoffbot merge
comment happens early enough so that builds are
parallel. This should happen infrequently enough for it not to be a nuisance.
When the PR has multiple commits, this should not happen as Hoff will create a unique merge commit. Unless of course one manually duplicates the Hoff testing branch.
Unfortunately the < 6EF8 a href="https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#status">GitHub webhook for the build status provides no way to check the canonical branch for the build link (#147, #148, faf04c9). We are left with a choice of reporting only the first link or all links that arrive. We chose the latter as success ✅ or failure ❌ depends the result of the first of the builds to complete.
More information is available in the doc directory:
- Background: My original intention was more ambitious than building a GitHub bot. This document gives some background about what I want to build.
- Installing: The installation guide.
Hoff is free software. It is licensed under the Apache 2.0 license. It may be used both for commercial and non-commercial use under the conditions given in the license.