简体   繁体   中英

Git pre-commit/pre-push hook to run unit tests on commits, with uncommited changes on the working tree

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM