Okay, finally got around to implementing this. There are two changes, a post-commit hook that writes the text, and a gitmessage function that reads it. I’m going to describe everything in-depth for the good of anyone who trips over this thread later.
The hook is very straightforward:
#! /bin/bash
format='{%n "commit": "%H",%n "tree": "%T",%n "author": "%an",%n "email": "%ae",%n "date": "%ad",%n "message": "%s"%n}'
GITPATH=$(git rev-parse --show-toplevel)
COMMITINFO="$GITPATH/nixos/.lastcommit.json"
echo "Requesting sudo to write to file ${COMMITINFO}"
git log -1 --pretty=format:"${format}" | sudo tee "${COMMITINFO}"
git add --intent-to-add "${COMMITINFO}"
git update-index --assume-unchanged "${COMMITINFO}"
echo -e "\nPost-hook wrote to ${COMMITINFO}"
Besides running git log, the only other important part is that you need to use the git add --intent-to-add
and git update-index --assume-unchanged
pair to make sure it is excluded from your commits, but is also copied to the Nix Store, as flakes only copy things to the store when they’re in the git index.
Output looks like this:
Requesting sudo to write to file /nix-sync/nixos/.lastcommit.json
[sudo] password for volkswagen:
{
"commit": "ed6b88248d17255c28977e712465bcc150eb2db1",
"tree": "977620f8201f085a3309631d8cab9155843ab1d9",
"author": "volkswagenfeature",
"email": "13547477+volkswagenfeature@users.noreply.github.com",
"date": "Wed Feb 5 17:17:18 2025 -0500",
"message": "checking clean repo naming feature"
}
Post-hook wrote to /nix-sync/nixos/.lastcommit.json
Minimal:
A minimal implementation of loading the commit message from a file like this would look like the following, but the one I’m using has bells and whistles.
gitmessage = pkgs:
with pkgs.lib;
let
lastcommit = ../.lastcommit.json;
commitjson = trivial.importJSON lastcommit ;
in
commitjson.message
My version:
TLDR for the impatient:
My full function for copy/pasting:
gitmessage = pkgs: checkrev :
with pkgs.lib;
let
lastcommit = ../.lastcommit.json;
commitjson = (assert builtins.pathExists (lastcommit); trivial.importJSON lastcommit) ;
truncate = str: len: (
strings.concatImapStrings
(i: a: if i <= len then a else "")
( strings.stringToCharacters str )
);
fetch = if (strings.hasSuffix "-dirty" checkrev) then "dirty" else "clean";
in
(
(
if (checkrev) != "unknown"
then (
assert commitjson.commit == (strings.removeSuffix "-dirty" checkrev);
"${fetch}-${truncate checkrev 6}"
)
else "unknown-${truncate commitjson.commit 6}"
) + "-" +
strings.sanitizeDerivationName (
truncate (
builtins.elemAt (
strings.splitString "\n" ( commitjson.message )
) 0
) 30
)
);
Detailed explination:
The file is loaded with a function that takes a instance of pkgs for lib, and a value checkrev, which is set to checkrev = self.rev or self.dirtyRev or "unknown"
. Nix puts the repo revision in rev
if the repo is clean. If it’s dirty, as of 2.17, you can get it from dirtyRev
. I also can pull it from the json as a last resort, and that’s what the “unknown” case is for.
Function header:
gitmessage = pkgs: checkrev :
with pkgs.lib;
let
Path to the json, load the json into commitjson
, do some string handling for the format I’ve picked out. I also verify that the file exists at this time.
lastcommit = ../.lastcommit.json;
commitjson = (assert builtins.pathExists (lastcommit); trivial.importJSON lastcommit);
fetch = if (strings.hasSuffix "-dirty" checkrev) then "dirty" else "clean";
I couldn’t find a built-in way to truncate a overly long commit message, so I wrote this utility. usage is just truncate "123456789" 7 -> "1234567"
truncate = str: len: (
strings.concatImapStrings
(i: a: if i <= len then a else "")
( strings.stringToCharacters str )
);
I use an assert to validate the commit hash stored in the temp file. this part generates a string with a format like dirty-fd09ea
or clean-43922a
or a unkown-993992
in the worst case.
in
(
(
if (checkrev) != "unknown"
then (
assert commitjson.commit == (strings.removeSuffix "-dirty" checkrev);
"${fetch}-${truncate checkrev 6}"
)
else "unknown-${truncate commitjson.commit 6}"
)
the commit message is used like this [raw commit] → [first line only] → [first 30 chars of first line] → [sanitized output (no spaces, backslashes, etc…)] → output
+ "-" +
strings.sanitizeDerivationName (
truncate (
builtins.elemAt (
strings.splitString "\n" ( commitjson.message )
) 0
) 30
)
);
}
}
Final version numbers look like this after nixos adds its other stuff:
24-clean-b34e3b-testing-better-commit-detectio-24.11.20250204.030ba19
24-dirty-ed6b98-checking-clean-repo-naming-fea-24.11.20250204.030ba19