I wrote this code today to verify setup branch pushes on Branchable. When I was writing it I was just down in the trenches coding until it worked, but it's rather surprising that what it does to git does work.

The following code runs in git's update hook. The fast path of the hook (written in C) notices that the user is committing a change to the setup branch, and hands the incoming git ref off to the setup verifier.

                # doing a shared clone makes the setupref, which has
                # not landed on any branch, be available for checkout
                shell("git", "clone", "--quiet", "--shared",
                        "--no-checkout", repository($hostname), $tmpcheckout);
                chdir($tmpcheckout) || error "chdir $tmpcheckout: $!";
                shell("git", "checkout", "--quiet", $setupref, "-b", "setup");

I got lucky here, since I initially passed --shared only to avoid the overhead of a clone of the site's entire git repository (which can be quite large, since Branchable doesn't have any real limits on site size). Without the --shared, the clone wouldn't see the incoming ref at all.

In the setup branch is an ikiwiki.setup file, and we only want to allow safe changes to be committed to it. Ikiwiki has metadata about which configurations are safe. Checking that and various other amusing scenarios (what if someone makes ikiwiki.setup a symlink etc) takes a hundred lines of fairly hairy code, but that doesn't matter here. Eventually it decides the setup file is ok as-is, or it's already died with an error message.

                # Check out setup file in toplevel. This is slightly tricky
                # as the commit has not landed in the bare git repo yet --
                # but it is available in the tmpcheckout.
                shell("git", "pull", "-q", $tmpcheckout, "setup");

                # Refresh or rebuild site to reflect setup changes.
                print STDERR "Updating site to reflect setup changes...\n";
                shell("ikiwiki", "-setup", "ikiwiki.setup", "-v",
                        ($rebuild_needed ? ("-rebuild") : ("-refresh", "-wrappers"))
                );

When this code runs there are three repositories, each with a different view of the setup branch. The main bare repository is waiting for the hook to succeed before it updates the ref to point to what was pushed. The temporary clone has what was pushed already checked out. And the site's home directory still has the old version of the setup branch checked out. Possibly even a version that has diverged from what's in the bare repository.

It's rather odd that the update hook goes and causes that latter repository to be updated, before the change has finished landing in the bare repository. But it does work; it ensures that if there is some kind of bizzare merge problem the user doing the push sees it, and I probably won't regret it.

The result certianly is nice -- edit ikiwiki.setup file locally, commit and push it, and ikiwiki automatically reconfigures itself and even rebuilds your whole site if you've changed something significant.