Sunday, November 24, 2013

No more pdb.set_trace() committed: git pre-commit hooks

After 1, 2, ... 5 times it happens, you must find a way to solve the problem.
I'm talking of committing to your git repository a pdb.set_trace() you forgot to remove.

What is really nice of git (something missing in SVN for what I know) is the support to two type of hooks: client side and server side.
While server side hooks are complex and triggered when you push to the repository, client side hooks are simpler and under your control.

So, I can use client side git hook to solve the pdb problem?

Git hooks are executable file (of any kind) inside the .git/hooks directory of your repository. Those files must have a precise name, that match the action they capture (you can find a complete list in the Git Hooks reference).
In our case: we need to use an executable named pre-commit.

As you probably noticed, hooks are inside the repository. But can I have a centralized database of hooks that will be replied in every repository?
Yes, you must use Git templates.
To quick set you global templates, see this nice article: "Create a global git commit hook".
For impatiens, in two lines of code:

$ git config --global init.templatedir '~/.git-templates'
$ mkdir -p ~/.git-templates/hooks

Now put the executable file named pre-commit inside this new directory. After that, when you create a new repository, hooks inside the template directory will be replicated inside the repository.

The pdb commit hook is a well know problem, already solved. The best reference I found is in the article "Tips for using a git pre-commit hook", that use a simple bash script.

I simple changed the author idea a little, because I don't want to block commit when a commented pdb is found in the code (bugged! See next section!):

FILES_PATTERN='\.py(\..+)?$'
FORBIDDEN_PATTERN='^[^#]*pdb.set_trace()'
git diff --cached --name-only | \
    grep -E $FILES_PATTERN | \
    GREP_COLOR='4;5;37;41' xargs grep --color --with-filename -n \
    -e $FORBIDDEN_PATTERN && echo 'COMMIT REJECTED Found "pdb.set_trace()" references. Please remove them before commiting' && exit 1

There are some other info you'd like to know about client side hooks:
  • You can ignore the hook for a single commit (git commit --no-verify)
  • Only a single hook can exits in a repository. This is a limit, however you can find workarounds.

EDIT (21 December)

Normally I don't modify old articles, but I found a bug in the solution above. The problem: grep command return non-zero status code when it found no match.

Here another working solution (I'm not a Linux bash ninja... any other cleaner suggestion will be accepted!):

FILES_PATTERN='\.py(\..+)?$'
FORBIDDEN_PATTERN='^[^#]*pdb.set_trace()'
git diff --cached --name-only | \
    grep -E $FILES_PATTERN | \
    GREP_COLOR='4;5;37;41' xargs grep --color --with-filename -n \
    -e $FORBIDDEN_PATTERN && echo 'COMMIT REJECTED Found "pdb.set_trace()" references. Please remove them before commiting'
RETVAL=$?
[ $RETVAL -eq 1 ] && exit 0