Itty Bitty Labs

Code and technical stuff from Itty Bitty Apps.

git pre-push

Breaking the build never feels good. Thanks to git’s new pre-push hook we can force ourselves to run our tests before we push to master, and prevent any odd punishments our current team has setup.
As of git version 1.8.2 which was released in April 2013, you now have the ability to execute a script on a push, and prevent the push from occuring if your script exits with a failure.

If you’re running OS X 10.9 Mavericks, and you have Xcode 5’s command line tools installed, you will already have a version of git that is pre-push hook ready.

Otherwise if your version is less than 1.8.2, you will need to download, and ensure /usr/local/git/bin is in your path, by adding export PATH=/usr/local/git/bin:$PATH to your ~/.bash_profile
You can test if you have installed correctly with which git and git --version



Enabling the pre-push hook

For each git repository you can find the hooks in the .git/hooks directory. All it takes for a git hook to run, is for there to be an executable file with the appropriate name within this directory. The list of available hooks are:

  • applypatch-msg
  • pre-applypatch
  • post-applypatch
  • pre-commit
  • prepare-commit-msg
  • commit-msg
  • post-commit
  • pre-rebase
  • post-checkout
  • post-merge
  • pre-push
  • pre-receive
  • update
  • post-receive
  • post-update
  • pre-auto-gc
  • post-rewrite

To enable the pre-push hook, just create $YOUR_REPO/.git/hooks/pre-push



Editing your pre-push hook

If the pre-push script exits with a failure (a return status of non-zero) then the push will do nothing. A very basic script to remind yourself that you should run your tests before pushing would look like this:

basic.pre-push.shlink
1
2
3
4
5
6
7
8
9
#!/bin/bash

read -p "You're about to push, are you sure you won't break the build? " -n 1 -r < /dev/tty
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
    exit 0 # push will execute
fi
exit 1 # push will not execute

This is a pretty silly use-case for pre-push, below are some more practical uses.



Using pre-push as a safety net

Sometimes I want the ability to write a comment on a piece of code/spike that I am doing, and make sure it never goes beyond my machine. Below is a pre-push example that will search the current source for the string ‘DONT PUSH ME’ and if found, will prevent the push and display in which commit the ‘DONT PUSH ME’ flag was introduced.

flag.pre-push.shlink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

# If the following text is found anywhere in the source for HEAD, we will prevent pushing
dont_push_flag="DONT PUSH ME"

flag_found=`git grep --color "$dont_push_flag" HEAD`
if [ -n "$flag_found" ]
then
    # Display which commit the first occurence of the flag was found and exit failure
    commit=`git log --pretty=format:'%Cred%h%Creset' -S "$dont_push_flag" | tail -n1`
    echo "Found $flag_found, first occurence was in $commit, not pushing"
    exit 1
fi
exit 0


Using pre-push to only push on successful execution of tests

Below is an example pre-push script that let’s us specify a branch to ‘protect’ so that our tests will only run if there are commits to push and we are on ‘master’. Also because pre-push will execute regardless of if there are commits to push or not, the script ensures we don’t fire off a lengthy test command, only to find out we actually didn’t need to.

tests.pre-push.shlink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash 

CMD="ls -l" # Command that runs your tests
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
    $CMD
    RESULT=$?
    if [ $RESULT -ne 0 ]; then 
        echo "failed $CMD"
        exit 1
    fi
fi
exit 0


Bypassing the pre-push hook

git push --no-verify

If you want to skip your pre-push hook from executing, you can run this and feel dirty about it ;)



Git’s included example

If you have created a repo with a version of git later than 1.8.2, then git has already set you up with a sample pre-push script .git/hooks/pre-push.sample. This script prevents pushing if any of the ‘to be pushed’ commits begin with ‘WIP’, and also details the parameters that are passed into the script.



That’s a wrap

You can find all of the scripts here https://github.com/raven/git-prepush-recipes. Please submit pull requests if you spot any issues, or if you have any pre-push scripts of your own.
If you have any questions or comments, ping me on twitter


About Pete

Peter Goldsmith is an iOS Developer for Itty Bitty Apps and has worked on their amazing iOS introspection tool Reveal. He thinks you should follow him on twitter @peterngoldsmith