Run Commands At Exact Git Refs
A small script to run commands in a worktree based on a particular commit or the index of a git checkout.
It is often very handy to be working on multiple things in a single repository, and while magit
makes unpicking local changes into coherent commits easy, testing those commits is kind of
annoying. My primary use case for this was for testing changes in an Ansible tree - often I have a lot of
things in flight, but it's pretty important to ensure that a commit is coherent. Specifically: the change
refers to a new file and the new file itself needs to end up in the same commit. Just running tests or
ansible-playbook --check --diff
doesn't help in that case, because all the files are in the tree, but what you care about is whether they'll
be committed together.
So I wrote a little script, the gist of which is this:
#!/usr/bin/env bash
set -euo pipefail
main_repo=$(git rev-parse --show-toplevel)
rel_dir=$(git rev-parse --show-prefix)
cache="$HOME/.cache/run-at-worktrees"
mode_index=0
commit=HEAD
while [[ $# -gt 0 ]]; do
case "$1" in
--index) mode_index=1; shift ;;
--) shift; break ;;
-*) echo "run-at: unknown flag: $1" >&2; exit 2 ;;
*) commit="$1"; shift ;;
esac
done
if [[ $# -eq 0 ]]; then
echo "run-at: missing command" >&2
exit 2
fi
if (( mode_index )); then
tree=$(git -C "$main_repo" write-tree)
sha=$(
GIT_AUTHOR_DATE='@0 +0000' GIT_COMMITTER_DATE='@0 +0000' \
GIT_AUTHOR_NAME=run-at GIT_AUTHOR_EMAIL=at@localhost \
GIT_COMMITTER_NAME=run-at GIT_COMMITTER_EMAIL=at@localhost \
git -C "$main_repo" commit-tree "$tree" -p HEAD \
-m 'run-at index snapshot'
)
label="index snapshot on top of $(git -C "$main_repo" rev-parse --short HEAD)"
else
sha=$(git -C "$main_repo" rev-parse --verify "$commit^{commit}")
label=$(git -C "$main_repo" log -1 --format='%h %s' "$sha")
fi
wt="$cache/${sha:0:12}"
mkdir -p "$cache"
if [[ ! -d "$wt" ]]; then
git -C "$main_repo" worktree add --detach "$wt" "$sha" >/dev/null
fi
echo "→ run-at: $wt" >&2
echo " rev: $sha" >&2
echo " desc: $label" >&2
target_dir="$wt"
if [[ -n "$rel_dir" ]]; then
target_dir="$wt/${rel_dir%/}"
fi
if [[ ! -d "$target_dir" ]]; then
echo "run-at: target directory does not exist in selected revision: ${rel_dir:-.}" >&2
exit 1
fi
cd "$target_dir"
exec "$@"
(the real version does some Ansible-specific stuff like symlinking the venv).
It's a lot of lines to:
-
Get a git commit id (or, if you pass
--index, create a temporary commit from the staging area). Either must go before the--. -
Make a worktree in
~/.cache/run-at-worktrees, with the name based on the commit. -
cdto the same relative directory (e.g. if I'm in~/src/linux/drivers, ensure I end up in./driversin the worktree) - Exec the provided command.
Setting the commit metadata - GIT_AUTHOR_* and GIT_COMMITTER_* - to static values
means the temporary commit's hash depends only on the current commit + contents of the index, so we will reuse
any previous worktree that shared the same content (and HEAD). commit-tree
ignores any configured gpg/ssh signing requirement, so it doesn't require any creds
and the commit id doesn't depend on keys or timestamps.
This is one of those things that in the past I might have considered too slow to do, but on a 5-year-old Mac, running it on the Linux kernel:
❯ time run-at -- pwd
Preparing worktree (detached HEAD 9ecfb2f7287a)
Updating files: 100% (94643/94643), done.
→ run-at: /Users/rob/.cache/run-at-worktrees/9ecfb2f7287a
rev: 9ecfb2f7287a967b418ba69f10d45ead0d360593
desc: 9ecfb2f7287a Merge tag 'trace-ring-buffer-v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace
/Users/rob/.cache/run-at-worktrees/9ecfb2f7287a
user=3.21s system=6.72s cpu=94% total=10.523
Since the worktree is named after the commit it's pointing at, subsequent runs are ~instant:
❯ time run-at -- pwd
→ run-at: /Users/rob/.cache/run-at-worktrees/9ecfb2f7287a
rev: 9ecfb2f7287a967b418ba69f10d45ead0d360593
desc: 9ecfb2f7287a Merge tag 'trace-ring-buffer-v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace
/Users/rob/.cache/run-at-worktrees/9ecfb2f7287a
user=0.02s system=0.03s cpu=76% total=0.066
My code bases are much, much smaller than linux.git, so that's plenty fast enough for me for
running tests or uv run ansible-playbook --check --diff .... It is not fully hermetic, and any
changes or junk left in the worktree persist between runs, but it's turned out to be very handy.