[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi] Writing a git pre-commit hook
From: |
Greg Chicares |
Subject: |
[lmi] Writing a git pre-commit hook |
Date: |
Tue, 8 Nov 2016 22:13:57 +0000 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Icedove/45.3.0 |
[I've already solved the problems that I'm aware of, but it has been
difficult enough that I wanted to post some notes here.]
A proprietary repository has this layout:
/opt/lmi/proprietary[0]$ls --classify
data/ hooks/ src/ test/
and I want a pre-commit hook to test each those subdirectories except
hooks/ itself. It is sufficient for now to automate this cut-and-paste
code from lmi's 'gwc/develop2.txt':
# Make some changes, then test concinnity (before every commit)
cd /opt/lmi/proprietary/data/
cd ../data; make src_dir=/opt/lmi/src/lmi -f /opt/lmi/src/lmi/GNUmakefile
check_concinnity 2>&1 |less -S
cd ../src ; make src_dir=/opt/lmi/src/lmi -f /opt/lmi/src/lmi/GNUmakefile
check_concinnity 2>&1 |less -S
cd ../test; make src_dir=/opt/lmi/src/lmi -f /opt/lmi/src/lmi/GNUmakefile
check_concinnity 2>&1 |less -S
The first thing I did was to symlink the repo's hooks/ :
cd /opt/lmi/proprietary
ln --symbolic --force --no-dereference ../hooks .git
and then I tried the following script, which is defective:
----8<--------8<--------8<--------8<--------8<--------8<--------8<----
#!/bin/sh
# git pre-commit hook
check_concinnity()
{
output=$( \
make src_dir=/opt/lmi/src/lmi -f /opt/lmi/src/lmi/GNUmakefile
check_concinnity \
2>&1 | sed \
-e'/^make[[]/d' \
-e'/^ Problems detected by xmllint:$/d' \
-e'/^ Miscellaneous problems:$/d' \
-e'/^ *[0-9][0-9]* \(source files\|source lines\|marked defects\)$/d'
)
if [ -n "$output" ]; then
printf '\n%s\n' "$output"
printf "COMMIT ABORTED\n"
exit 1
fi
}
printf "checking "
printf "src..."
cd $(git rev-parse --show-toplevel)/src && check_concinnity
printf "data..."
cd $(git rev-parse --show-toplevel)/data && check_concinnity
printf "test..."
cd $(git rev-parse --show-toplevel)/test && check_concinnity
printf "okay\n"
---->8-------->8-------->8-------->8-------->8-------->8-------->8----
When I ran it at the command line, it worked:
/opt/lmi/proprietary[0]$./hooks/pre-commit
checking src...data...test...okay
However, when I let 'git commit' run it, it failed to navigate the
subdirectories:
/opt/lmi/proprietary[0]$git commit --all -m'Update test results
See lmi commit 6faf7bb7640bb2fe9cf5416ca716fc48242f36db.'
checking src...data...fatal: Not a git repository: '.git'
.git/hooks/pre-commit: 26: cd: can't cd to /data
test...fatal: Not a git repository: '.git'
.git/hooks/pre-commit: 28: cd: can't cd to /test
okay
[master 61cc82a] Update test results
1 file changed, 25 insertions(+), 25 deletions(-)
/opt/lmi/proprietary[0]$
It still "succeeds", and the change is committed, but apparently it's
not checking the data/ and test/ subdirectories.
(1) How can we test a pre-commit hook in the context in which git runs
it, which is evidently not the same thing as running it at the command
prompt?
This article:
http://stackoverflow.com/questions/11512155/how-to-test-git-hooks
suggests adding 'set -x' inside the script (good idea), then running
it in a throwaway clone of the repository (yick--I'd rather commit
a nonsensical change and then 'reset --hard'). But I stumbled upon a
method I like better: just run 'git commit' in a clean repository;
that runs the hook and then commits nothing.
(2) How does running the script at the command line differ from having
git run it?
Searching the web for "fatal: Not a git repository: '.git'" suggests that
http://stackoverflow.com/questions/2314500/git-post-receive-hook-not-working
| GIT_DIR and GIT_WORK_TREE are set when the hook runs
which leads to advice like
http://stackoverflow.com/questions/3542854/calling-git-pull-from-a-git-post-update-hook
| git --git-dir=.git pull
| git -C [directory] [subcommand]
(but I don't want to hard-code an absolute path)
| env -i git [subcommand]
(but that's an awfully large hammer to hit this problem with) or even
unset $(git rev-parse --local-env-vars)
which seems unlikely to help because 'rev-parse' seems to be the
subcommand that's failing in my case.
I was about to add 'env -i' before 'git rev-parse' just to see whether
that would magically solve the (unknown) problem, when I figured that
if I was going to go to all that trouble, I should first refactor:
+ toplevel=$(git rev-parse --show-toplevel)
- cd $(git rev-parse --show-toplevel)/src && check_concinnity
+ cd $toplevel/src && check_concinnity
- cd $(git rev-parse --show-toplevel)/data && check_concinnity
- cd $(git rev-parse --show-toplevel)/test && check_concinnity
+ [...similarly]
...and then it simply worked (so I never had reason to experiment with
'env -i'). This leads to a better question:
(3) What was the error message above:
checking src...data...fatal: Not a git repository: '.git'
.git/hooks/pre-commit: 26: cd: can't cd to /data
test...fatal: Not a git repository: '.git'
.git/hooks/pre-commit: 28: cd: can't cd to /test
really saying...and not saying?
When I ran the script interactively, it did this (ignoring the
check_concinnity() function call):
/opt/lmi/proprietary[0]$cd $(git rev-parse --show-toplevel)/src
/opt/lmi/proprietary/src[0]$cd $(git rev-parse --show-toplevel)/data
/opt/lmi/proprietary/data[0]$cd $(git rev-parse --show-toplevel)/test
/opt/lmi/proprietary/test[0]$
Of those three 'cd' commands, the error message does not suggest that
the first failed. Instead, it says that the others failed, apparently
because $(git rev-parse --show-toplevel) evaluated to an empty string.
I don't know why that happened--maybe it's a bad idea, inside a git
hook, to cd to a subdirectory that might conflict with GIT_WORK_TREE,
and then invoke any git facility. (Perhaps this article:
http://serverfault.com/questions/107608/git-post-receive-hook-with-git-pull-failed-to-find-a-valid-git-directory
sheds some light on that.) If I experimentally add this:
toplevel=$(git rev-parse --show-toplevel)
printf "toplevel is $toplevel\n"
printf "PWD is $PWD\n"
cd gwc
toplevel=$(git rev-parse --show-toplevel)
printf "toplevel is $toplevel\n"
printf "PWD is $PWD\n"
to the pre-commit hook for lmi above, I get:
/opt/lmi/src/lmi[0]$git commit
toplevel is /opt/lmi/src/lmi
PWD is /opt/lmi/src/lmi
fatal: Not a git repository: '.git'
so, whatever the ultimate cause, that's a reproducible problem. I'll
go ahead and commit pre-commit scripts that avoid that problem and seem
to work properly.
- [lmi] Writing a git pre-commit hook,
Greg Chicares <=