DEV Community

Cover image for Git Hooked on Git Hooks
Ryan Palo
Ryan Palo

Posted on • Updated on • Originally published at assertnotmagic.com

Git Hooked on Git Hooks

I found something cool that either should be more popular than it is, or it is popular and it should be taught to new programmers sooner than it is. They are called Git Hooks. I'm going to write this post assuming you know what Git is, and assuming you're comfortable with the general process of code, stage, commit, lather, rinse, repeat, push. If you're not, Here are a few good resources. (Note, those are three separate links, not one long one).

The Background

Git hooks are scripts that, if enabled and set up properly, get fired at certain times in the git process. Here is a list of the available hooks:

  1. pre-commit: Runs first, before a you even enter a commit message (assuming you don't use -m for messages).
  2. prepare-commit-msg: Runs before displaying the commit message to you for editing. This is good for templating commit messages.
  3. commit-msg: Runs after you hit save on the commit message, but before the commit goes through. Useful for enforcing commit message standards.
  4. post-commit: Runs after the commit is saved and completed. Used for notifying or status updating usually.
  5. pre-rebase: Runs before a rebase occurs. Git's default example script for this makes sure you haven't pushed before you rebase (since that could cause issues for other people).
  6. post-rewrite: Runs after a commit is replaced (e.g. with git commit --amend, git rebase). Good for generating documentation or copying in untracked files. Think npm install or similar, maybe.
  7. post-checkout: Runs after git checkout. Similar to #6.
  8. post-merge: Runs after git merge. See #6.
  9. pre-push: Happens before a push completes. You can use it to abort the push if need be, similar to pre-commit, but for pushes.

In addition to these, there are a few hooks available for an email-based workflow (i.e. when people are emailing you patches to merge). There are also a few available on the git server side that are useful for deployment. I'm going to skip all of these for now, and just focus on a really basic case (mostly because that's about where my skill level is at right now).

Hook It UP

So let's do it! For my example, I'm using Python because of course I am, shut up. I'll walk through this with you step by step. First, create a new project folder.

$ cd ~/Desktop
$ mkdir hooky && cd $_
Enter fullscreen mode Exit fullscreen mode

Create a new Python file called hooky.py. Note that I'm going to purposefully use poor style for our example. You'll see why in a minute.

# hooky.py

def hook( quantity ):
    return "---u<><"*quantity

if __name__ == "__main__":
    print(hook(3))
Enter fullscreen mode Exit fullscreen mode

Now let's initialize our git repository.

$ git init
$ git add hooky.py
Enter fullscreen mode Exit fullscreen mode

Before we commit, take a look at the .git/hooks/ directory. Also, if you haven't, install flake8, which is one of many linters for Python. If you're following along in another language, install an equivalent linter. Normally, we'd use a virtual environment, but a linter is a handy thing to have around globally.

$ ls -l .git/hooks/
$ pip3 install flake8
Enter fullscreen mode Exit fullscreen mode

You'll see a bunch of samples that you should look through later. They've got some neat ideas in them. For now, we'll create our own pre-commit hook. Create .git/hooks/pre-commit. Make sure there is no file extension.

#!/usr/bin/env bash

# This is the pre-commit file
echo "Linting code before commit..."
flake8
Enter fullscreen mode Exit fullscreen mode

Now, make sure that your script is executeable and we can attempt to commit.

$ chmod +x .git/hooks/pre-commit
$ git commit  # omit the -m for example's sake.
              # if you include a message, it will still work
              # you'll just waste your time typing a message
Enter fullscreen mode Exit fullscreen mode

Boom! Flake8 complains about our 💩 style. No commit. Prove it with git status. Note the files are still staged, but not commited. But why no commit? For pre-commit hooks, if the script exits with any other status but zero, it cancels the commit.

Cancelled!

prepare-commit-msg, commit-msg, pre-rebase, and pre-push all do similar things. That's pretty much it! Great right? Go forth and hook away! Just to drive this home, I want to illustrate one more similar use case. This is extra, so feel free to skip it if you're already too excited you can't wait to try it yourself.

Bonus Example

Create two new files: __init__.py and test_hooky.py. __init__.py will remain blank, but here are the contents of the test file.


from hooky import hooky   # From this dir, import the module

# This test should pass
def test_hook():
    assert hooky.hook(3) == "---u<><---u<><---u<><"

# This one will fail because there is no such method (yet).
def test_release():
    assert hooky.release("---u<><") == 1
Enter fullscreen mode Exit fullscreen mode

Now, update your .git/hooks/pre-commit file. Again, if you haven't yet, intall pytest (an awesome testing framework) via pip.

#!/usr/bin/env bash

# This is the pre-commit file
echo "Linting code before commit..."
flake8

echo "Running tests..."
pytest
Enter fullscreen mode Exit fullscreen mode

Now, running flake8, go back into your hooky.py file and fix all of the warnings until flake8 is appeased. Then try committing again. You should see it blow up with the broken test! Feel cool? Good, because you are cool. And it's not just the git hooks. Although they add to your overall coolness, you were already cool. So how about that?


Originally posted on my blog

Latest comments (5)

Collapse
 
jrock2004 profile image
John Costanzo

This is great for yourself, but the next question, is how do you share this between team members? How do I back these up so I do not lose them if I reformat?

Collapse
 
rpalo profile image
Ryan Palo • Edited

There are a couple of ways that I've found to do this.

  1. Create a separate directory such as ./hooks to hold your hooks. Then use a setup script to copy them into your .git/hooks directory after cloning. (i.e. git clone ... && ./setup.sh)

  2. If you're on Git 2.9 or later, there is a config setting to hook that up. git config core.hooksPath hooks. You can similarly add this line to any makefile/init/setup script.

  3. If you're doing JavaScript, there's shared-git-hooks as well.

Let me know if you find a better way!

Collapse
 
jrock2004 profile image
John Costanzo

Yeah, where my concern is, making sure developers do this when they first clone the repo. I will keep looking

Thread Thread
 
gyandeeps profile image
Gyandeep Singh

If your work on javascript projects, then using github.com/typicode/husky would solve all those problems. npm install hooks up everything for every developer. In JS, you have to run npm install to get started on a project.

Thread Thread
 
jrock2004 profile image
John Costanzo

Thank you very much