Using Git commit hooks to prevent stupid mistakes

I’m pretty prone to making stupid mistakes all the time. I think its a mixture of being busy, and being a natural scatter brain. In any case, mistakes happen, and thankfully over time developers have come up with a number of ways to combat mistakes. These usually take the shape in automated toolds. Unit tests are a great way to automated tool to help prevent stupid mistakes from happening, Git commit hooks are another. Git hooks are shell or other scripts that you install in your repo, and git will run them at certain points in time. One nice advantage of git commit hooks is you can install them locally and you can avoid publishing the stupid mistake you might make.

For me, I quite often commit debugger; statements in my javascript code. I want to not do that anymore so I created the following hook in .git/hooks/pre-commit

Show Plain Text
  1. #!/bin/sh
  3. if git-rev-parse --verify HEAD >/dev/null 2>&1; then
  4.     against=HEAD
  5. else
  6.     against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
  7. fi
  9. for FILE in `git diff-index --check --name-status $against -- | cut -c3-` ; do
  10.     # Check if the file contains 'debugger'
  11.     if [ "grep 'debugger' $FILE" ]
  12.     then
  13.         echo $FILE ' contains debugger!'
  14.         exit 1
  15.     fi
  16. done
  17. exit

After creating the file its important to make it executable with chmod +x .git/hooks/pre-commit or git won’t run your script. Git hooks are installed on a per-repo basis, which is kind of bad. Its also kind of good, in that you can easily install your own suite of git hooks and no-one will be the wiser. There are a number of hooks available in git, and you can learn all about them in Pro Git


What is the purpose of the conditional at the beginning with the fallback to the magic refname?

Justin on 4/20/11

At Disqus we’re running JSHint as part of our pre-commit hook. You can configure it to reject development-y function calls (like console.log), as well as enforce a number of good practices.

Speaking of which, static comments Mark? For shame …

Ben on 4/21/11

That’s a cool idea, Ben, using it to look for console.log – that’s hands-down one of my most annoying habits as far as commits are concerned.

I do a similar thing to this, but the stupid mistake I’m protecting against is committing configuration patches, which helps me keep apps platform/configuration agnostic (shameless self-plug: )

Jasper on 4/25/11

Justin: The magic refname is the initial tree object present in every git repo, its in the example pre-commit hook, and it would keep your hook working even on a brand new repo.

mark story on 4/30/11

Hey Mark, I think this line

if [ “grep ‘debugger’ $file” ]

should read

if [ “grep ‘debugger’ $FILE” ]

Kevin van Zonneveld on 8/1/11

Kevin: You’re totally right. Thanks :)

mark story on 8/7/11

Since it greps the file without regard for what has been staged, I believe it will reject debugs that aren’t actually about to be committed, if you git add a file, then add debug statements but don’t git add them, then try to commit. Probably not a big deal.

I made a similar hook in Ruby that only looks at what has been added:

Henrik N on 10/7/11

You CAN setup a pseudo-global set of hooks as you wish — but ONLY on newly created repositories of the local machine.

The git client’s shared files are typically in “/usr/share/git-core”.

You can use the “templates” folder therein to modify the default layout of a repository and its hooks.

Be careful, though, since this is mostly useful for repeatable project types*. (i.e. a repository of only javascript, or a repository of only php).

  • Its not nearly as bad if you know your way around bash scripting very well. You can add parameter handling to your commit hooks as well.

will on 6/9/12

Just stumbled over this little gem, too:

will on 6/9/12

Thanks for this. I get this error with your code though:

fatal: —name-only, —name-status, —check and -s are mutually exclusive

Jordan314 on 6/19/12

Great, thanks for your effort! Used this to keep those console.log from being checked in and embarrassing me publicly ;-)

Couple of things: * The “git-rev-parse” in line 4 has to read “git rev-parse” on my machine, else it always runs against the root node, never against head. * I had to remove “—check” on line 10, because git complains about the two options being mutually exclusive. Must have changed in the meantime.

Thorsten Krüger on 7/5/12

Was unable to run this hook, found that the problem is that
—check —name-status are mutually exclusive, at least in my version of git.

Just remove the —check and it works.

Dmitri Snytkine on 7/8/12

@Jordan314 using
`git diff-index —name-status —cached $against —`
instead of
`git diff-index —check —name-status $against —`

Koen on 7/25/12

@Jordan314 try using
`git diff-index —name-status —cached $against —`
instead of
`git diff-index —check —name-status $against —`

Koen on 7/25/12

I’m using this git hook to ensure my debugging code doesn’t get committed and I’m very happy with it!

Luis S on 8/5/12

I develop on OS (Mac). My installation of git did not include ‘git-rev-parse’, but ‘git rev-parse’ works just fine.

David V on 9/24/12

One other way to remove debugger; lines is to user google clouser compiler. In this way, besides other benefits you are able to remove debugger lines or other debugging code from productions scripts.

catalin on 8/6/13

After reading this I’ve been trying to make a global pre-commit hook that should work for ‘all’ languages here

Very interested in your feedback.

Kevin van Zonneveld on 12/18/13

Modern way of doing this: #!/bin/sh

if git rev-parse —verify HEAD >/dev/null 2>&1; then against=HEAD
else against=4b825dc642cb6eb9a060e54bf8d69288fbee4904

for FILE in `git diff-index —name-only $against ` ; do # Check if the file contains ‘debugger’ echo $FILE $against if [ “grep ‘debugger’ $FILE” ] then echo $FILE ‘ contains debugger!’ exit 1 fi

JJ on 5/3/14

Just a minor edit to a great and helpful post: Apparently, the Pro Git book added a chapter; Git Hooks are now in Chapter 8, Section 3. Your link at the very end of the post should point here: or

Daniel M on 12/17/14

Comments are not open at this time.