MIT 6.S194 | Open Source Software Project Lab  

Hooking Custom Functionality into the Git Pipeline

Git (like many other version control systems) support custom script triggers through hooks. These hooks give you a change to inject functionality at particular points in the standard pipeline:

  1. Before committing ("pre-commit")
  2. Before writing a commit message ("prepare-commit-msg")
  3. After writing a commit message ("commit-msg")
  4. After committing ("post-commit")
  5. Before a rebase ("pre-rebase")
  6. After a checkout ("post-checkout")
  7. After a merge ("post-merge")
  8. Before receiving a push ("pre-receive")
  9. After receiving a push ("post-receive")
  10. Before receiving a push, run once per branch ("update")

To add a hook, place a file with the hook name in .git/hooks/. Every git repository comes with several sample files already there to provide example usage. Depending on the hook, git may pass in command line arguments with information, such as a file pointer to the commit message or a list of the files about to be committed.

Hooks that start with pre- can generally abort whatever operation is in place by returning a non-zero exit code. Why might you do this?

A Simple Hook: Displaying a Banner

Go to one of the repositories you've created from an earlier exercise and create a post-commit hook that displays a congratulatory banner, such as the following:
#!/usr/bin/python

print "3.. 2.. 1.. LIFTOFF!!"
Play around to see that it works.

Preventing a Commit

Now let's try preventing ALL commits. Add a pre-commit hook that simply returns a non-zero exit code:

#!/usr/bin/python

import sys
print "No soup for you!"

# In *nix systems, an "exit code" of 0 means
# that execution happened sucessfully, whereas a nonzero
# exit code indicates an error.
#
# In python, you can return an exit code with sys.exit(CODE)
sys.exit(-1)

Try changing a file and committing that change to the repository. What happens? Check the staging area with git status to verify that the commit didn't go through.

Let's change that hook to something a little more reasonable: a math puzzle. You're going to have to do a bit of a hack re-routing stdin to accept user input. Here's one math puzzle; try your own.

#!/usr/bin/python

import random
import sys

x = random.randint(1,10)
y = random.randint(1,10)

# This is required because git hooks are run in non-interactive
# mode. You aren't technically supposed to have access to stdin.
# This hack works on MaxOS and Linux. Mileage may vary on Windows.
sys.stdin = open('/dev/tty')

result = input("What is %d * %s:" % (x, y))

if int(result) == x * y:
  sys.exit(0)
else:
  print "INCORRECT!"
  sys.exit(-1)

Now try committing again. Verify that the commit is allowed when you get the puzzle right, and rejected when you are wrong.

More Reasonable Interrogation

Talk about some circumstances you can imagine wanting to prevent a commit. Anything here seem familiar to anyone with internships at a big company like Google?

Server-side Hooks

You can add hooks on the server-side, too. Go back to the repository you created on Athena and try adding the following post-update hook:

#!/bin/bash
git show --name-status | mail -s "Received Push" eob@csail.mit.edu

See if you can figure out what it is going to do before pushing changes up to your Athena repository to find out. When might you want to be running hooks on a shared server, instead of a developer machine?

Simulating Github Pages

You are only about 10 minutes of work away from creating a simple version of Github Pages. How would you go about this?

We won't do it in class, because it involves writing a script that deletes files, and that is, ahem, dangerous, but this can be a convenient way to let a group of people update a simple website.