I wrote a git post-commit hook that looks at certain files in my
repository whenever I change them, edits them a bit if it wants to, and
commits any changes it made. Such a hook could be used to maintain "Last
modified: ..." lines in static HTML files as shown below.
Let's say we want to update all foo/*.html
files that
contain something like the following line:
<div class=lastmod>Last modified: ...</div>
The idea is simple: use git diff --name-only HEAD^ HEAD
to
get a list of the files that were changed by the last commit, pick the
ones we're interested in, edit them using sed, and commit any changes
we make.
#!/bin/sh
git log -1 --format=%s HEAD|grep -q "^Auto: " && exit
START="<div class=lastmod>Last modified: "
END="</div>"
FILES=$(git diff --name-only HEAD^ HEAD|grep -x 'foo/.*\.html')
grep -xl "$START.*$END" $FILES < /dev/null |\
while read file;
do
DATE=$(date '+%Y-%m-%d')
sed -i "s,^\($START\).*\($END\),\1$DATE\2," $file &&
git add $file
done
git diff-index --quiet --cached HEAD || git commit -m "Auto: lastmod"
There are a few things worth noting here. The hook is re-executed after
its own commit, so we must protect against recursion, which I do by not
acting on a commit with an "Auto: ..." message. We must be prepared for
the possibility that the commit does not touch any of the files we care
about (i.e. that $FILES
is empty), which I do by feeding
< /dev/null
to grep. We add edited files to the index
without knowing if we actually changed the value, and use git
diff-index
to see if anything changed at the end.
With the hook in place, you see something like this when you commit a
change to some HTML file in foo:
[master e14fbb9] Auto: lastmod
1 files changed, 1 insertions(+), 1 deletions(-)
[master dc5dc4d] Towards a transformative hermeneutics of hook scripts
1 files changed, 123 insertions(+), 14 deletions(-)
(Adding -q
to the git commit
in the hook will
suppress the "Auto: ..." message.)
Unfortunately, as the notation implies, git diff --name-only HEAD^
HEAD
does not work for the root commit in the repository, because
HEAD^
makes no sense without a parent. But I can't find a
good way to list the files affected by a given commit that handles this
special case.
There are some minor complications. The extra commits won't make
everyone happy, especially since git commit --amend
may or
may not operate on your last commit any more (git reset
HEAD^^
may be useful here). The automatically updated lines may
also cause extra conflicts if you ever try to merge unrelated changes
to the affected files.
For a very different mechanism that can be used to do (only) CVS-style
$Keyword$ expansion outside the repository when exporting a commit with
git-archive
, see
gitattributes(5).
(Questions and other feedback are welcome. Send me email.)