@@ 0,0 1,176 @@
+---
+title: More on git scratch branches: using stgit
+published: 2021-04-16
+---
+
+I wrote [a short post](/posts/git-scratch-branch/) last year
+about a useful workflow for preserving temporary changes in
+git by using a scratch branch. Since then, I've come across
+[stgit](https://stacked-git.github.io/), which can be used in much the
+same way, but with a few little bells and whistles on top.
+
+Let's run through a quick example to show how it works. Let's say
+I want to play around with the cool new programming language
+[Zig](https://ziglang.org) and I want to build the compiler myself.
+The first step is to grab a source code checkout:
+```
+$ git clone https://github.com/ziglang/zig
+Cloning into 'zig'...
+remote: Enumerating objects: 123298, done.
+remote: Counting objects: 100% (938/938), done.
+remote: Compressing objects: 100% (445/445), done.
+remote: Total 123298 (delta 594), reused 768 (delta 492), pack-reused 122360
+Receiving objects: 100% (123298/123298), 111.79 MiB | 6.10 MiB/s, done.
+Resolving deltas: 100% (91169/91169), done.
+$ cd zig
+```
+
+Now, according to the
+[instructions](https://github.com/ziglang/zig/wiki/Building-Zig-From-Source#option-a-use-your-system-installed-build-tools)
+we'll need to have CMake, GCC or clang and the LLVM development libraries
+to build the Zig compiler. On [NixOS](https://nixos.org/) it's usual
+to avoid installing things like this system-wide but instead use a
+file called `shell.nix` to specify your project-specific dependencies.
+So here's the one ready for Zig (don't worry if you don't understand
+the Nix code, it's the stgit workflow I really want to show off):
+```
+$ cat > shell.nix << EOF
+{ pkgs ? import <nixpkgs> {} }:
+pkgs.mkShell {
+ buildInputs = [ pkgs.cmake ] ++ (with pkgs.llvmPackages_12; [ clang-unwrapped llvm lld ]);
+}
+EOF
+$ nix-shell
+```
+Now we're in a shell with all the build dependencies, and we can go
+ahead with the `mkdir build && cd build && cmake .. && make install`
+steps from the Zig build instructions^[At the time of writing, Zig
+depends on the newly-released LLVM 12 toolchain, but this hasn't made
+it into the nixos-unstable channel yet, so this probably won't work on
+your actual NixOS machine.].
+
+But now what do we do with that `shell.nix` file?
+```
+$ git status
+On branch master
+Your branch is up to date with 'origin/master'.
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+ shell.nix
+
+nothing added to commit but untracked files present (use "git add" to track)
+```
+We don't really want to add it to the permanent git history, since it's
+just a temporary file that is only useful to us. But the other options
+of just leaving it there untracked or adding it to `.git/info/exclude`
+are unsatisfactory as well: before I started using scratch branches
+and stgit, I often accidentally deleted my `shell.nix` files which were
+sometimes quite annoying to have to recreate when I needed to pin specific
+dependency versions and so on.
+
+But now we can use stgit to take care of it!
+```
+$ stg init # stgit needs to store some metadata about the branch
+$ stg new -m 'add nix config'
+Now at patch "add-nix-config"
+$ stg add shell.nix
+$ stg refresh
+Now at patch "add-nix-config"
+```
+This little dance creates a new commit adding our `shell.nix` managed by
+stgit. You can `stg pop` it to unapply, `stg push`^[an unfortunate naming
+overlap between pushing onto a stack and pushing a git repo] to reapply,
+and `stg pull` to do a `git pull` and reapply the patch back on top.
+The main [stgit documentation](https://stacked-git.github.io/guides/)
+is helpful to explain all the possible operations.
+
+This solves all our problems! We have basically recreated the scratch
+branch from before, but now we have pre-made tools to apply, un-apply
+and generally play around with it. The only problem is that it's really
+easy to accidentally push your changes back to the upstream branch.
+
+Let's have another example. Say I'm sold on the stgit workflow, I have
+a patch at the bottom of my stack adding some local build tweaks and,
+on top of that, a patch that I've just finished working on that I want to
+push upstream.
+```
+$ cd /some/other/project
+$ stg series # show all my patches
++ add-nix-config
+> fix-that-bug
+```
+Now I can use `stg commit` to turn my stgit patch into a real immutable
+git commit that stgit isn't going to mess around with any more:
+```
+$ stg commit fix-that-bug
+Popped fix-that-bug -- add-nix-config
+Pushing patch "fix-that-bug" ... done
+Committed 1 patch
+Pushing patch "add-nix-config ... done
+Now at patch "add-nix-config"
+```
+And now what we _should_ do before `git push`ing is `stg pop -a` to make
+sure that we don't push `add-nix-config` or any other local stgit patches
+upstream. Sadly it's all too easy to forget that, and since stgit updates
+the current branch to point at the current patch, just doing `git push`
+here will include the commit representing the `add-nix-config` patch.
+
+The way to prevent this is through git's hook system. Save this as
+`pre-push`[^githooks] (make sure it's executable):
+```
+#!/bin/bash
+# An example hook script to verify what is about to be pushed. Called by "git
+# push" after it has checked the remote status, but before anything has been
+# pushed. If this script exits with a non-zero status nothing will be pushed.
+#
+# This hook is called with the following parameters:
+#
+# $1 -- Name of the remote to which the push is being done
+# $2 -- URL to which the push is being done
+#
+# If pushing without using a named remote those arguments will be equal.
+#
+# Information about the commits which are being pushed is supplied as lines to
+# the standard input in the form:
+#
+# <local ref> <local sha1> <remote ref> <remote sha1>
+
+remote="$1"
+url="$2"
+
+z40=0000000000000000000000000000000000000000
+
+while read local_ref local_sha remote_ref remote_sha
+do
+ if [ "$local_sha" = $z40 ]
+ then
+ # Handle delete
+ :
+ else
+ # verify we are on a stgit-controlled branch
+ git show-ref --verify --quiet "${local_ref}.stgit" || continue
+ if [ $(stg series --count --applied) -gt 0 ]
+ then
+ echo >&2 "Unapplied stgit patch found, not pushing"
+ exit 1
+ fi
+ fi
+done
+
+exit 0
+```
+
+[^githooks]: A somewhat orthogonal but also useful tip here so that you
+don't have to manually add this to every repository is to configure git's
+`core.hooksDir` to something like `~/.githooks` and put it there.
+
+Now we can't accidentally^[You can always pass `--no-verify` if you want
+to bypass the hook.] shoot ourselves in the foot:
+```
+$ git push
+Unapplied stgit patch found, not pushing
+error: failed to push some refs to <remote>
+```
+
+Happy stacking!