102

I wrote my own custom post-merge hook, now I added a "hooks" directory to my main project folder (since git doesn't track changes in .git/hooks), somewhere I read that I can make a symbolic link from hooks to .git/hooks so I don't have to copy the file from one folder to the other every time someone changes it so I tried:

ln -s -f hooks/post-merge .git/hooks/post-merge

But it doesn't seem to work, any ideas why? "ln hooks/post-merge .git/hooks/post-merge" works fine but making a hard link is the same as copyin I guess....

Tyler DeWitt
  • 23,366
  • 38
  • 119
  • 196
Mateusz Dymczyk
  • 14,969
  • 10
  • 59
  • 94
  • 26
    Because the symlink is resolved relative to its location. A symlink in `.git/hooks/` that points to `hooks/post-merge` gets resolved to `.git/hooks/hooks/post-merge`, which does not exist. You want `ln -s -f ../../hooks/post-merge .git/hooks/post-merge`. Or make your life easier: `ln -s -f ../hooks .git/hooks`. Your problem has nothing to do with git. – Aristotle Pagaltzis Jan 04 '11 at 13:02
  • 1
    http://stackoverflow.com/questions/3462955/putting-git-hooks-into-repository and http://stackoverflow.com/questions/427207/can-git-hook-scripts-be-managed-along-with-the-repository (and http://stackoverflow.com/questions/3703159/git-remote-shared-pre-commit-hook) point out the fact that symlink can work. – VonC Jan 04 '11 at 13:04
  • 1
    Correct me if I'm wrong, but a Symlink still has to be setup per workstation. The only thing that this saves, is copying it around manually or writing another command that copies the tracked hook file into `.git/hooks`. – adi518 May 18 '19 at 18:43
  • @adi518 Any solution for this as of today? Was reading from [Atlassian](https://www.atlassian.com/git/tutorials/git-hooks): > "As an alternative, Git also provides a Template Directory mechanism that makes it easier to install hooks automatically. All of the files and directories contained in this template directory are copied into the .git directory every time you use git init or git clone." More details on Template Directories [here](git-scm.com/docs/git-init#_template_directory) But I still don't get how to make this work... – Carlos J. Jimenez Jan 26 '22 at 18:25

6 Answers6

180

you just used wrong path, it should be:

ln -s -f ../../hooks/post-merge .git/hooks/post-merge
Michal Čihař
  • 9,799
  • 6
  • 49
  • 87
  • 11
    I don't understand why I need to go up two directories to link a resource that lives in the folder I've `cd`'d into. Shouldn't it just be `ln -s ./hooks/` ? – yurisich Aug 04 '13 at 19:13
  • 50
    This. When git is evaluating the symlink, it apparently does so using `.git/hooks` as its working directory, so relative paths should be relative to that directory. This is more self-explanatory if you first `cd` into `.git/hooks` before making the symlink, and figure out the relative path from there. – Eliot Feb 06 '14 at 19:57
  • 12
    @Eliot neither creation nor resolution of symlinks is affected by the working directory. Whatever you give `ln` will be stored as the target and resolved relative to the location of the link. – Joó Ádám Mar 17 '16 at 01:18
  • 2
    @JoóÁdám You're right. So the issue here is that the original command specifies an incorrect relative path. Still, `cd`ing into `.git/hooks` before you make the link will help you write the command, as you can then autocomplete to the correct path. – Eliot Mar 30 '16 at 20:01
  • 1
    All this worked for me in the end. The only difference is that I am linking to my own `prepare-commit-msg`. The problem is if I am editing the commit message using nano, then Ctl+X out to abort, git still completes a commit anyway instead of aborting like it used to before I made this change. Is there a way to have nano exit without causing this commit to complete? – Frak Jun 15 '17 at 20:57
  • 1
    ../../ although correct, is very confusing. `ln -s -f $(readlink -f hooks/post-merge) $(readlink -f .git/hooks/post-merge) ` and `ln -s -f $(pwd)/hooks/post-merge $(pwd)/.git/hooks/post-merge) ` are much more readable. – noooooooob Mar 20 '20 at 03:52
35

While you can use symbolic links, you can also change the hooks folder for your project in your git settings with :

git config core.hooksPath hooks/

Which is local by default so it won't ruin git hooks for your other projects. It works for all hook in this repository, so it's especially useful if you have more than one hook.

If you already have custom hooks in .git/hooks/ that you do not want to share with your team you can add them in hooks/ and add a .gitignore so they're not shared.

Pierre.Sassoulas
  • 3,733
  • 3
  • 33
  • 48
  • 4
    Very nice! A handy trick :) Seems far more future-proof than sym-linking them one by one. – jkp Jun 06 '19 at 14:03
  • 2
    this functionality is supported for git > 2.9 https://github.com/git/git/blob/master/Documentation/RelNotes/2.9.0.txt#L127-L128 – Romain TAILLANDIER Mar 04 '21 at 11:09
  • 1
    How does this not have way more upvotes? This seems objectively better for future-proofing. – Farzad A Oct 27 '21 at 14:51
  • I think this is the best solution, by far. The only caveat I see is that this precludes any private hooks one dev on a team has...but OP addresses that concern, and it is far outweighed by the benefits, IMO. – David Hempy Oct 26 '22 at 20:20
3

Changing directory before linking

cd /path/to/project-repo/.git/hooks
ln -s -f ../../hooks/post-merge ./post-merge
Jekis
  • 4,274
  • 2
  • 36
  • 42
1

The path calculation is done relative to the symlink. Let's understand using an example,

ln -s path/to/file symlink/file

Here, the path to the file should actually be the relative path from the symlink path.
The system actually calculates the file path as symlink/path/path/to/file
The above command should be re-written as

ln -s ../path/to/file symlink/path

The folder structure being,

/code
------ symlink/file
------ path/to/file

swayamraina
  • 2,958
  • 26
  • 28
0

Utilizing Michael Cihar's comment, here is an example of a bash script I wrote to simply create these symlinks. This script is located in git_hooks/ dir which is at the project root. My .git/ folder is also in the same directory level.

#!/usr/bin/env bash

pwd=$(pwd);

# Script is designed to be ran from git_hooks/ dir
if [[ "$pwd" == *"git_hooks"* ]]; then

  files=$(ls | grep -v -e '.*\.');

   while read -r file; do

     ln -s ../../git_hooks/$file ../.git/hooks/
     echo "Linked $file -> ../.git/hooks/$file"

   done <<< "$files";

else

  echo "";
  echo "ERROR: ";
  echo "You must be within the git_hooks/ dir to run this command";
  exit 1;

fi

My script must be ran from within the actual git_hooks/ directory. You can modify it to behave differently, if you'd like.

This script will symlink any file that is not suffixed with a file extension within the git_hooks/ directory. I have a README.txt in this directory + this script (named symlink.sh). All the actual git hooks are named 'pre-commit', 'pre-push', etc. so they will be symlinked.

cchoe1
  • 325
  • 2
  • 15
-3

why not just cp ./hooks/* .git/hooks/

this worked for me in Mac OS

Croer01
  • 15
  • 1
  • 3
Frazko
  • 27
  • 6