
Show HN: Pre-commit – A framework for managing multi-language pre-commit hooks - struys
http://pre-commit.com
======
struys
Hey everyone, creators of pre-commit here. Thought it was a good idea to
clarify what makes our project different from most other systems.

Even thought pre-commit is written in python we really don't care about the
language that was used to write the hook/linter. If there's a good linter out
there written in ruby, you should be able to use it and not have to install
ruby/gems/etc. With pre-commit you just say you want scss-lint
([https://github.com/causes/scss-lint](https://github.com/causes/scss-lint))
in your pre-commit config file and run pre-commit install. When you commit,
pre-commit downloads, install scss-lint (without root) and runs the hook
against the files you've changed.

Currently we support ruby, node and python and we definitely want to expand
the system to support linters in other languages like Go, C/C++ and PHP. We'd
really like to support all of these linters -
[https://github.com/showcases/clean-code-
linters](https://github.com/showcases/clean-code-linters).

Ken & Anthony

~~~
contingencies
Good job guys - I do think there's space for a broadly applicable solution in
this space. The main selling points for a solution in this area, for us, are:

(1) a strong remote registry implementation (available hooks, preferably auto-
matched to auto-detected languages present, perhaps using _file_ / _libmagic_
and language syntax versioning); and

(2) the ability to spec default behavior remotely in a company-wide registry,
but then to override at two levels: that of checked in files within the repo,
and _.gitignore_ 'd local developer-specific prefs.

I'm definitely going to look at this further. To speed adoption, I'd encourage
you to avoid assumptions around people's de-facto desire for all but the most
basic levels of linting (iffy warnings, etc.), ie. make sure the defaults are
very light touch ... but useful. Convenience is key.

However, _please_ don't encourage _curl_ -based installs!

~~~
struys
(1) We definitely thought about implementing a search/auto-suggest for pre-
commit. We'd love help from the community to build a solution for that. You
can follow/add ideas on the v2 label - [https://github.com/pre-commit/pre-
commit/issues/10](https://github.com/pre-commit/pre-commit/issues/10)

For the time being our registry of available hooks is here:
[https://github.com/pre-commit/pre-
commit.github.io/blob/real...](https://github.com/pre-commit/pre-
commit.github.io/blob/real_master/all-repos.yaml). We use that file to
generate this page - [http://pre-commit.com/hooks.html](http://pre-
commit.com/hooks.html). It's also available as json - [http://pre-
commit.com/all-hooks.json](http://pre-commit.com/all-hooks.json). If you
create new hooks for pre-commit we'd love to get a pull request to add to all-
repos.yaml.

It would be awesome to have `pre-commit search <keyword>` and `pre-commit add
<hook-id>`.

(2) I think we could integrate with yeoman generators to provide the company
wide requirements. It's really hard to make assumptions about the structure of
a repo, often just knowing extensions is not enough to identify file types
(e.g. tests, auto generated code). Generally we leave it up to the developer,
we don't want to enforce too much.

We're definitely encouraging fixers over linters at Yelp. It's great to tell a
developer "Hey you missed a semicolon" via jshint but it's much more useful to
say "Hey you missed a semicolon, we fixed it and our change is unstaged, if
you agree with our change add/commit" via fixmyjs. We'll be supporting fixmyjs
really soon. We were blocked by
[https://github.com/jshint/fixmyjs/pull/99](https://github.com/jshint/fixmyjs/pull/99)
and now we're just waiting for the next release.

If you're a python developer using our autopep8-wrapper (found in this repo -
[https://github.com/pre-commit/pre-commit-hooks](https://github.com/pre-
commit/pre-commit-hooks)) is really awesome. It fixes a lot of the simple pep8
issues.

------
icefox
Another shameless plug, for my git-hooks project I wrote a few years ago
[https://github.com/icefox/git-hooks](https://github.com/icefox/git-hooks) It
is a single small bash script with no external depenandancies so it just needs
to be in your path.

    
    
      git-hooks provide a way to manage and share your hooks using three+ locations:
    
      User hooks, installed in ~/.git_hooks/
      Project hooks, installed in .git/git_hooks/ in a project.
      Global hooks, specified with the hooks.global configuration option.
    

Another very real problem with any hooks solution is that it must be enabled
in each repository. Users frequently either don't do it or forget to do it in
all of the repositories. Running git hooks --installglobal will force any new
git repository or any git repository you clone to have a reminder to install
git hooks. (It can't be on by default for security reasons of course.)

~~~
michaelmior
Thanks for this! It's been a while, but I did use it a year or so ago for a
project at work and it was quite handy. It's unfortunate that git doesn't have
something like this built-in.

------
EGreg
This is cool but why do we need it if Mercurial already supports pre-commit
hooks?

For example we have this for refreshing redmine:

    
    
      #!/bin/sh
      
      PROJ=""
      
      while [ $# -gt 0 ] ; do
          case $1 in
            --project)
              shift
      	PROJ="&id=$1"
              shift
      	;;
          esac
      done
    
      echo $"Refreshing Redmine"
    
      curl -s -o /dev/null "http://issues.qbixstaging.com/sys/fetch_changesets?key=jI$
    

and here is our PHP syntax check:

    
    
      #!/bin/sh
    
      ### Configuration
      HGBIN="/usr/bin/hg"
      phpPath="/usr/bin/php"
      tmpstyle=`/bin/mktemp -t`
    
      ### hg log style file
      cat > $tmpstyle << EOF
      changeset = "{files}\n"
      file = "{file}\n"
      EOF
    
      ### PHP syntax check
      for f in `$HGBIN log -r $HG_NODE:tip --style $tmpstyle | grep -E "\.php[3-5]?$" | sort | uniq` ; do
            res=`$HGBIN cat -r tip $f | $phpPath -l -d display_errors=1 -d error_reporting=4`
            if [ $? -ne 0 ] ; then
                    echo $f 1>&2
                    echo $res 1>&2
                    exit 1
            fi
      done
      rm $tmpstyle
    

finally here is one for JS:

    
    
      #!/bin/sh
      
      ### Configuration
      HGBIN="/usr/bin/hg"
      jshPath="/usr/local/bin/jshint"
      tmpstyle=`/bin/mktemp -t`
    
      ### hg log style file
      cat > $tmpstyle << EOF
      changeset = "{files}\n"
      file = "{file}\n"
      EOF
    
      ### JS syntax check
      for f in `$HGBIN log -r $HG_NODE:tip --style $tmpstyle | grep -E "\.js$" | sort | uniq` ; do
            res=`$HGBIN cat -r tip $f | $jshPath --config /projects/.jshintrc --verbose - | grep "(E"`
            st=$?
    	if [ $st -eq 0 ] ; then
                    echo $f 1>&2
                    echo $res 1>&2
                    exit 1
            fi
      	if [ $st -gt 1 ] ; then
                    exit 1
            fi
      done
      rm $tmpstyle

~~~
struys
We had something similar before building pre-commit and it served us well for
many years. The issue is it grew to be a really long bash file with a lot of
copy and pasted code (for lots of different hooks). We built pre-commit to
reduce the code duplication and allow all developers to install new hooks that
are not installed on the system. We also handle interesting cases like dealing
with merge conflicts (see "pre-commit during merges" [http://pre-
commit.com/#advanced](http://pre-commit.com/#advanced)).

aside: It would be cool to support mercurial in the future :D

------
progx
I (think i) understand it, but i did not understand the workflow.

Before i commit, i run local tests to be sure that my code is ok (with jshint,
...). Normally i work with my local branch before i push it to other
developers.

Why should i share unfinished untested code to others?

Or is pre-commit a kind of local history (like Eclipse holds it in background
for every file)?

~~~
ethomson
No, there is no change in how you share, nor is this any sort of local
history.

Git has the concept of "pre-commit hooks" which is a script that is run (by
git itself) whenever you run "git commit", before your changes actually land
in the repository. This can be used to (for example) run your local tests for
you, as a sanity check.

However, your hooks are configured on a per-repository basis, and not
transferred when you clone. So you would need to configure these manually each
time you clone. It's easy to forget to set up the hook with all the things you
need to run.

This tool is to help alleviate some of the pain in this, by having a
configuration file to set up all the necessary hooks, as well as having a
library of default hooks (whitespace checks, etc).

------
Newky
Shameful plug, but I wrote
[https://github.com/Newky/hooked/](https://github.com/Newky/hooked/) which
allows you to deal with git hooks (in general) in a nicer fashion using
Python.

Might be of use to someone.

------
_mikz
there is [https://github.com/jish/pre-commit](https://github.com/jish/pre-
commit) already

