124

I would like to run a unit-tests before every git push and if tests fails, cancel the push, but I can't even find pre-push hook, there is pre-commit and pre-rebase only.

sheepwalker
  • 1,399
  • 2
  • 11
  • 17

7 Answers7

218

Git got the pre-push hook in the 1.8.2 release.

Sample pre-push script: https://github.com/git/git/blob/87c86dd14abe8db7d00b0df5661ef8cf147a72a3/templates/hooks--pre-push.sample

1.8.2 release notes talking about the new pre-push hook: https://github.com/git/git/blob/master/Documentation/RelNotes/1.8.2.txt

manojlds
  • 259,347
  • 56
  • 440
  • 401
  • 1
    @manojlds do you know what is this hook designed to be used for? I would like to use it to push my binary to my customers when pushing to a specific branch(i.e. build the nightly version and upload it with curl, before pushing). The problem is that it takes a while to build and upload, and remote closes connection. So i end up with my binary built and uploaded to customers but not pushed to a repo, because remote repo closes connection. Any idea how to work around this? Or maybe it is a bad idea in it's root. – igrek Jun 19 '14 at 13:51
  • @igrek did you find a solution to the connection closing issue? – Mario Estrada Apr 27 '15 at 03:54
  • 1
    @MarioEstrada, yes, i don't remember exactly how, but i made it push twice: first git command runs unit tests and then if it doesn't disconnect it pushes and starts another push in another thread, if the first one push times out, the second one from another thread works for me. If either first and second succeeds, then the first pushes changes, and the second pushes nothing. The trick is that i had some argument added, which bypasses unit tests(which was used for the second git push, so it didn't start unit tests again) – igrek Apr 28 '15 at 07:12
30

Git got the pre-push hook in the 1.8.2 release.

Pre-push hooks are what I needed along with pre-commit hooks. Apart from protecting a branch, they can also provide extra security combined with pre-commit hooks.

And for an example on how to use (taken and adopted and enhanced from this nice entry)

Simple example to login to vagrant, run tests and then push

#!/bin/bash
# Run the following command in the root of your project to install this pre-push hook:
# cp git-hooks/pre-push .git/hooks/pre-push; chmod 700 .git/hooks/pre-push

CMD="ssh vagrant@192.168.33.10 -i ~/.vagrant.d/insecure_private_key 'cd /vagrant/tests; /vagrant/vendor/bin/phpunit'"
protected_branch='master'

# Check if we actually have commits to push
commits=`git log @{u}..`
if [ -z "$commits" ]; then
    exit 0
fi

current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')

if [[ $current_branch = $protected_branch ]]; then
    eval $CMD
    RESULT=$?
    if [ $RESULT -ne 0 ]; then
        echo "failed $CMD"
        exit 1
    fi
fi
exit 0

As you can see the example uses a protected branch, subject of the pre-push hook.

Jimmy Kane
  • 14,040
  • 9
  • 73
  • 109
14

If you are using the command line, the easiest way to do this is to write a push script that runs your unit tests and, if they succeed, completes the push.

Edit

As of git 1.8.2 this answer is outdated. See manojlds's answer above.

kubi
  • 44,308
  • 19
  • 90
  • 118
  • do you mean not using hooks at all? just replace "git pull" with, for example, "git uinttestspull"? that 's not exactly what I need – sheepwalker Nov 16 '10 at 16:22
  • 1
    @sheepwalker: s/pull/push/, and use an alias to make it nice and short. – Cascabel Nov 16 '10 at 18:02
  • @sheepwalker Yeah, that's not exactly what you asked for, but like @calmh said, there's no pre-push hooks. – kubi Nov 16 '10 at 20:08
6

There isn't a hook for it, because a push isn't an operation that modifies your repository.

You can do the checks on the receiving side though, in the post-receive hook. That is where you would usually reject an incoming push. Running unit tests might be a little intensive to do in a hook, but that's up to you.

Jakob Borg
  • 21,033
  • 6
  • 45
  • 47
6

For the record, there is a patch to Git 1.6 that adds a pre-push hook. I don't know whether it works against 1.7.

Rather than mess with that, you could run push script like @kubi recommended. You could also make it a Rake task instead so it's in your repo. ruby-git could help with this. If you check the target repo, you could run tests only when pushing to the production repo.

Finally, you could run your tests in your pre-commit hook but check for what branch is being committed to. Then you could have a, say, a production branch that requires all tests pass before accepting a commit but your master doesn't care. limerick_rake may be useful in that scenario.

jchevali
  • 163
  • 10
Turadg
  • 7,048
  • 2
  • 45
  • 48
  • thanks, actually I've already chosen the last variant (Finally, you could run your tests in your pre-commit hook..) – sheepwalker Mar 01 '11 at 15:56
5

I would rather run the test in a pre-commit-hook. Because the change is already recorded when committing. Push and pull only exchange information about already recorded changed. If a test fails you would already have a "broken" revision in your repository. Whether you're pushing it or not.

ordnungswidrig
  • 3,068
  • 1
  • 15
  • 29
  • 224
    I generally agree, though if you're in the habit of making a lot of incremental commits to squash later, and the test suite is large, this could be impractical. – Cascabel Nov 16 '10 at 18:03
  • I see. So I would suggest the tests run before merging with the main branch but there is no pre-merge hook either. However there is an "update" hook wich can be used to prevent updating a ref in the remote repository: "Just before updating the ref on the remote repository, the update hook is invoked. Its exit status determines the success or failure of the ref update. The hook executes once for each ref to be updated, and takes three parameters: the name of the ref being updated, the old object name stored in the ref, and the new objectname to be stored in the ref." – ordnungswidrig Nov 17 '10 at 08:27
  • I decided to make @ordnungswidrig variant - just use pre-commit hook, cause of "broken" revisions. Here is my variant (php + phpUntit tests):
    #!/bin/sh
    
    /usr/bin/php /srv/www/wf/tests/run.php /srv/www/wf/tests > /tmp/result
    if grep 'OK' /tmp/result
    then
      exit 0
    else
      echo 'Your tests is failed, you cannot commit'
      exit 1
    fi
    
    – sheepwalker Dec 08 '10 at 11:51
  • Suppose we want to run integration and end to end tests before pushing (in addition to unit tests which perhaps already ran pre-commit). I could see value in having a post-commit/rebase hook to run those tests and save the success/failed state somewhere. Then a pre-push hook could check that state and only push if the state was success. This way the developer can commit and continue working. When they are ready to push, the tests have hopefully already finished running since their last commit, so they can push right away without any delay. – tboyce12 May 29 '15 at 21:07
  • 31
    Down voted because - while informative - it completely ignores the OP's question. – The Dembinski Jan 16 '17 at 18:24
  • 1
    @TheDembinski I wouldn't say that it ignores the OP question. In fact it takes it into consideration and says that there is a better way to do it than the way the OP had in mind. That is in general the kind of answer i would like to get. – calder.ty May 01 '17 at 14:37
  • 12
    @calder.ty - Nah. manojlds better addresses what matters. In fact, pre-commit hooks that run tests are generally a bad idea imo. It assumes that all things that get committed must pass tests. Which is bad for common work flows that focus on collaboration. So yea...I disagree; its not a better way to do "it" nor does it address the question. – The Dembinski May 08 '17 at 16:16
  • 6
    This is not an answer to the question. It is just a personal opinion and as such does not belong as an answer – gman Mar 14 '18 at 02:03
  • 2
    If the responder wished to simply share their opinion, the appropriate response would be to either 1) respond with a comment, or 2) answer the question as expected and then provide their personal opinion below it for better visibility. – Jake Bellacera Apr 29 '19 at 18:59
  • 2
    this doesn't really answer the original question. it looks like it should be a comment instead. – typoerrpr May 24 '19 at 01:48
1

The script linked by the highly-voted answer shows the parameters etc to the pre-push hook ($1 is remote name, $2 URL) and how to access the commits (lines read from stdin have structure <local ref> <local sha1> <remote ref> <remote sha1>)

#!/bin/sh

# An example hook script to verify what is about to be pushed.  Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed.  If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
#   <local ref> <local sha1> <remote ref> <remote sha1>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).

remote="$1"
url="$2"

z40=0000000000000000000000000000000000000000

while read local_ref local_sha remote_ref remote_sha
do
    if [ "$local_sha" = $z40 ]
    then
        # Handle delete
        :
    else
        if [ "$remote_sha" = $z40 ]
        then
            # New branch, examine all commits
            range="$local_sha"
        else
            # Update to existing branch, examine new commits
            range="$remote_sha..$local_sha"
        fi

        # Check for WIP commit
        commit=`git rev-list -n 1 --grep '^WIP' "$range"`
        if [ -n "$commit" ]
        then
            echo >&2 "Found WIP commit in $local_ref, not pushing"
            exit 1
        fi
    fi
done

exit 0
serv-inc
  • 29,557
  • 9
  • 128
  • 146