Putting Git hooks into a repository

114,101

Solution 1

I generally agree with Scy, with a couple of additional suggestions, enough that it's worth a separate answer.

First, you should write a script which creates the appropriate symlinks, especially if these hooks are about enforcing policy or creating useful notifications. People will be much more likely to use the hooks if they can just type bin/create-hook-symlinks than if they have to do it themselves.

Second, directly symlinking hooks prevents users from adding in their own personal hooks. For example, I rather like the sample pre-commit hook which makes sure I don't have any white space errors. A great way around this is to drop in a hook wrapper script in your repository, and symlink all of the hooks to it.

The wrapper can then examine $0 (assuming it's a Bash script; an equivalent like argv[0] otherwise) to figure out which hook it was invoked as, then invoke the appropriate hook within your repository, as well as the appropriate user's hook, which will have to be renamed, passing all the arguments to each. Quick example:

#!/bin/bash
if [ -x $0.local ]; then
    $0.local "[email protected]" || exit $?
fi
if [ -x tracked_hooks/$(basename $0) ]; then
    tracked_hooks/$(basename $0) "[email protected]" || exit $?
fi

The installation script would move all pre-existing hooks to the side (append .local to their names), and symlink all known hook names to the above script:

#!/bin/bash
HOOK_NAMES="applypatch-msg pre-applypatch post-applypatch pre-commit prepare-commit-msg commit-msg post-commit pre-rebase post-checkout post-merge pre-receive update post-receive post-update pre-auto-gc"
# assuming the script is in a bin directory, one level into the repo
HOOK_DIR=$(git rev-parse --show-toplevel)/.git/hooks
for hook in $HOOK_NAMES; do
    # If the hook already exists, is executable, and is not a symlink
    if [ ! -h $HOOK_DIR/$hook -a -x $HOOK_DIR/$hook ]; then
        mv $HOOK_DIR/$hook $HOOK_DIR/$hook.local
    fi
    # create the symlink, overwriting the file if it exists
    # probably the only way this would happen is if you're using an old version of git
    # -- back when the sample hooks were not executable, instead of being named ____.sample
    ln -s -f ../../bin/hooks-wrapper $HOOK_DIR/$hook
done

Solution 2

No, putting them into the repository is fine. I’d even suggest doing so (if they are useful for others as well). The user has to explicitly enable them (as you said, for example, by symlinking), which is on one hand a bit of a pain, but it protects users on the other hand from running arbitrary code without their consent.

Solution 3

Nowadays you can do the following to set a directory that is under version control to be your Git hooks directory, e.g., MY_REPO_DIR/.githooks would be

git config --local core.hooksPath .githooks/

It is still not directly enforceable but, if you add a note in your README (or whatever), this requires a minimum of effort on each developer's part.

Solution 4

Store in the project and install in the build

As others state in their answer, if your hooks are specific for your particular projects then include them in the project itself, managed by Git. I would take this even further and say that, given that it is good practice to have your project build using a single script or command, your hooks should be installed during the build.

I wrote an article about managing Git hooks, if you are interested in reading about this in a little more depth.

Java & Maven

Full disclaimer; I wrote the Maven plugin described below.

If you are handling build management with Maven for your Java projects, the following Maven plugin handles installing hooks from a location in your project.

https://github.com/rudikershaw/git-build-hook

Put all your Git hooks in a directory in your project, and then configure your pom.xml to include the following plugin declaration, goal, and configuration.

<build>
  <plugins>
    <plugin>
      <groupId>com.rudikershaw.gitbuildhook</groupId>
      <artifactId>git-build-hook-maven-plugin</artifactId>
      <configuration>
        <gitConfig>
          <!-- The location of the directory you are using to store the Git hooks in your project. -->
          <core.hooksPath>hooks-directory/</core.hooksPath>
        </gitConfig>
      </configuration>
      <executions>
        <execution>
          <goals>
            <!-- Sets git config specified under configuration > gitConfig. -->
            <goal>configure</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
      <!-- ... etc ... -->
  </plugins>
</build>

When you run your project build, the plugin will configure Git to run hooks out of the directory specified. This will effectively set up the hooks in that directory for everyone working on your project.

JavaScript & NPM

For NPM there is a dependency called Husky which allows you to install hooks including ones written in JavaScript.

// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "npm test",
      "pre-push": "npm test",
      "...": "..."
    }
  }
}

Others

Additionally, there are a number of different hook management applications/plugins including pre-commit for Python projects, Overcommit for Ruby projects, and Lefthook for Ruby or Node.js projects.

Solution 5

From TEMPLATE DIRECTORY, you could use one of these mechanisms to update the .git/hooks directory of each newly created Git repository:

The template directory contains files and directories that will be copied to the $GIT_DIR after it is created.

The template directory will be one of the following (in order):

  • the argument given with the --template option;

  • the contents of the $GIT_TEMPLATE_DIR environment variable;

  • the init.templateDir configuration variable; or

  • the default template directory: /usr/share/git-core/templates.

Share:
114,101

Related videos on Youtube

shabunc
Author by

shabunc

Just another javascripter, coding since 1998, wanna sleep.

Updated on December 30, 2021

Comments

  • shabunc
    shabunc 9 months

    Is it considered to be a bad practice - to put .git/hooks into the projects repository (using symlinks, for example). If yes, what is the best way to deliver same hooks to different Git users?

    • Peter Mortensen
      Peter Mortensen 10 months
      What do you mean by "the projects repository" (or perhaps "the project's repository" (possessive))? Does it refer to a particular IDE? Or something else?
    • shabunc
      shabunc 10 months
      @PeterMortensen what is meant by project in this question is something existing as a VCS root one can clone and start to work with.
    • Peter Mortensen
      Peter Mortensen 10 months
      The term used in this context may have arisen from GitLab.
    • shabunc
      shabunc 9 months
      @PeterMortensen GitLab launched in 2014 while the question was asked in Aug'11, so the term precedes GitLab existence )
  • guneysus
    guneysus over 8 years
    I added chmod +x .git/hooks/* to your bin/create-hook-symlinks to work it.
  • Cascabel
    Cascabel over 8 years
    @guneysus You shouldn't need that, because the hooks should already be executable (they should be checked in that way) and the links don't need any special permissions, just the files they link to.
  • Arnold Daniels
    Arnold Daniels over 8 years
    A better way to get the hook dir is HOOK_DIR=$(git rev-parse --show-toplevel)/.git/hooks.
  • ELLIOTTCABLE
    ELLIOTTCABLE over 8 years
    I've put together a simple system based on this to manage the hooks in my project: ell.io/tt$Paws.js/blob/Master/Scripts/install-git-hooks.sh
  • Tobias Hagenbeek
    Tobias Hagenbeek almost 8 years
    what if it is a company policy thing, then the code is not "arbitrary" this is required code, so this would be considered a limitation in GIT, for not having another (pre-defined) directory, which is tracked, which also gets executed along with the regular hooks
  • Superole
    Superole over 7 years
    the risk of overwriting old git sample files (or possibly the users own disabled hooks!) is easily avoidable by testing for file (-f) instead of executable (-x) when renaming. The wrapper tests for executable upon invocation anyway, which is the most sensible time to test this.
  • Mark K Cowan
    Mark K Cowan over 7 years
    Automatically delivering hooks is a security issue, I'm glad that Git doesn't do it directly - to enforce team/company policy, use hooks on the server side or let users manually decide to enable them as @scy describes :)
  • Scott Jungwirth
    Scott Jungwirth about 7 years
    I took just the essentials and put it in a repo github.com/sjungwirth/githooks
  • MiniGod
    MiniGod about 7 years
    "protects users [...] from running arbitrary code without their consent". If a developer would do like you suggest (symlinking) then the hook could be changed by someone else, and run "arbitrary code without their consent"
  • scy
    scy about 7 years
    MiniGod: Of course. If you’re sufficiently paranoid, you could copy the hooks instead of symlinking them, then audit them, and only then enable them. However, most (citation needed) Git repositories will contain source code which is to be run on the user’s machine, so you’re likely to run constantly changing, unaudited code anyway. But yes, you’ve got a point. ;)
  • Sz.
    Sz. over 6 years
    @Jasny-ArnoldDaniels, any trick for '--separate-git-dir', where .git is just a "pseudo symlink" to the repo located outside of the working tree?
  • Arnold Daniels
    Arnold Daniels over 6 years
    @Sz, No I've never used a setup like that. Sorry.
  • shabunc
    shabunc almost 4 years
    I appreciate your effort and do believe there's a valuable information here however - it does not answer the question stated.
  • mathewguest
    mathewguest almost 4 years
    In my opinion, if the hooks are specific to a particular repository or are integral components of the workflows used then they belong in the repository as files. It's hard to put them anywhere else without creating more problems than it solves. You could store general hooks in a repository of it's own or on a shared drive which could keep the project repo squeaky clean but at the cost of being much less practical. I agree with the other users in saying that the hooks must be easy to add. Symbolic links may create unnecessary dependence on a particular system or file structure.
  • mathewguest
    mathewguest almost 4 years
    Additionally, symbolic links break the users ability to add their own hooks. The .git/hooks directory isn't tracked so the source should start in the repository and make it's way into the hooks script, not the other way around. I think the counter-argument would be that the git hooks are more related to the workflow or team rather than the project and thus don't belong in the repository. Depending on your specific use-case, are you more okay with potentially polluting the git repository with less-related hooks or would you rather forego a bunch of complexity in putting them somewhere else?
  • shabunc
    shabunc over 3 years
    I think you are making an interesting product but I also think this does not answers the question and basically is a self-promotion and nothing more.
  • Julius Bullinger
    Julius Bullinger over 3 years
    One trick I found on viget.com/articles/two-ways-to-share-git-hooks-with-your-tea‌​m is to set the option from your Makefile/CMake config/whatever.
  • Michiel Bugher
    Michiel Bugher over 2 years
    Thanks for creating this plugin, it made integrating my pre-commit file super easy.
  • NationWidePants
    NationWidePants over 1 year
    As a note, this only works for versions of git above 2.9.0. This wasn't a "thing" at the time of the post.
  • Garrett W.
    Garrett W. 12 months
    Husky is really great. I've even worked on PHP projects that used Husky just to manage pre-commit hooks that ran tools installed by Composer, such as phpstan and phpcs, and I was pretty happy with that setup. Composer doesn't have anything quite the same, to my knowledge.