Reputation: 3071
I currently have a simple pre-commit
hook for my project (in R, though that's incidental to this question) which runs unit tests:
#!/bin/sh
a=$(Rscript tests/testthat.R)
exit $a
Where tests/testthat.R
is a wrapper file that handles all the tests.
This hook has a problem, though: if I make partial commits (such that there are still changes which remain uncommitted), the tests will run on the current state of the working tree, including the uncommitted changes.
So, let's say I finish "Step 1" of something I'm doing but forget to commit it. I then start on "Step 2", but then realize I forgot to commit "Step 1". If "Step 2" is currently in a broken state due to my unfinished changes, I won't be able to do a partial commit for "Step 1" because the tests will detect that "Step 2" is defective.
So, is there a way for the hook to run on the version which is actually being committed? My thought would be a hook which effectively temporarily checks out a commit, runs the tests on that checkout, deletes the checkout, and then defines whether to allow the commit to go through. But given that this hook triggers before the commit is done, I'm assuming it's impossible to check it out.
I'd also be open to a pre-push
hook. This seems more plausible since the hook receives the SHA's for the commits being pushed, so my idea above seems more reasonable: get the latest SHA, create a temporary directory, checkout that SHA, run the tests, delete the directory. But then my question becomes whether that's actually the suggested method or if I'm overcomplicating things due to my ignorance.
Upvotes: 2
Views: 4259
Reputation: 3071
Ends up that the git stash
manual page describes this exact use-case:
You can use
git stash push --keep-index
when you want to make two or more commits out of the changes in the work tree, and you want to test each change before committing:# ... hack hack hack ... $ git add --patch foo # add just first part to the index $ git stash push --keep-index # save all other changes to the stash $ edit/build/test first part $ git commit -m 'First part' # commit fully tested change $ git stash pop # prepare to work on all other changes # ... repeat above five steps until one commit remains ... $ edit/build/test remaining parts $ git commit foo -m 'Remaining parts'
So simply do
git stash push --keep-index
#
# testing...
#
git stash pop
Using this in a hook has an edge-case risk, though: you might have an old, unrelated stash you've forgotten about, and might want to make a clean commit (leaving no uncommitted changes).
In this case, the call to git stash push --keep-index
won't actually create a stash (returning "No local changes to save"). But when the tests are complete, git stash pop
will find the old stash, leading to at the very least a headache.
So my actual pre-commit
hook looks like:
#/bin/sh
hasChanges=$(git diff)
if [ -n "$hasChanges" ]; then
git stash push --keep-index
fi
#
# testing...
#
if [ -n "$hasChanges" ]; then
git stash pop
fi
exit $testSuccess
Basically, use git diff
to see if there are any changes to tracked files. If there are, stash and later pop them. Otherwise, don't bother with the stash operations.
Upvotes: 5
Reputation: 1908
This article suggests to stash the changes that are not being committed before running the tests, and then un-stash them after tests are run. Relevant code snippet:
# pre-commit.sh
STASH_NAME="pre-commit-$(date +%s)"
git stash save -q --keep-index $STASH_NAME
# Test prospective commit
...
STASHES=$(git stash list)
if [[ $STASHES == "$STASH_NAME" ]]; then
git stash pop -q
fi
Upvotes: 2