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?
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.
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 "$@" || exit $?
fi
if [ -x tracked_hooks/$(basename $0) ]; then
tracked_hooks/$(basename $0) "$@" || 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"
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
chmod +x .git/hooks/*
to your bin/create-hook-symlinks
to work it. –
Wes HOOK_DIR=$(git rev-parse --show-toplevel)/.git/hooks
. –
Schooner $HOOK_DIR
will be wrong. Using HOOK_DIR=$(git rev-parse --git-common-dir)/hooks
instead should work. –
Spahi 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.
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.
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.
For PHP Composer-based PHP projects, you can automatically distribute to engineers. Here is an example for pre-commit and commit-msg hooks.
Create a hooks
folder, and then in your composer.json file:
},
"scripts": {
"post-install-cmd": [
"cp -r 'hooks/' '.git/hooks/'",
"php -r \"copy('hooks/pre-commit', '.git/hooks/pre-commit');\"",
"php -r \"copy('hooks/commit-msg', '.git/hooks/commit-msg');\"",
"php -r \"chmod('.git/hooks/pre-commit', 0777);\"",
"php -r \"chmod('.git/hooks/commit-msg', 0777);\"",
],
Then you can even update them as the project continues as everyone is running composer install
on a regular basis.
The pre-commit npm package handles this elegantly, allowing you to specify pre-commit hooks in your package.json file.
Here's a script, add-git-hook.sh, which you can ship as a regular file in the repository and can be executed to append the Git hook to the script file. Adjust which hook to use (pre-commit, post-commit, pre-push, etc.) and the definition of the hook in the cat heredoc.
#!/usr/bin/bash
# Adds the git-hook described below. Appends to the hook file
# if it already exists or creates the file if it does not.
# Note: CWD must be inside target repository
HOOK_DIR=$(git rev-parse --show-toplevel)/.git/hooks
HOOK_FILE="$HOOK_DIR"/post-commit
# Create script file if doesn't exist
if [ ! -e "$HOOK_FILE" ] ; then
echo '#!/usr/bin/bash' >> "$HOOK_FILE"
chmod 700 "$HOOK_FILE"
fi
# Append hook code into script
cat >> "$HOOK_FILE" <<EOF
########################################
# ... post-commit hook script here ... #
########################################
EOF
This script might make sense to have executable permissions or the user can run it directly. I used this to automatically git-pull on other machines after I committed.
I answered the easier question which wasn't what was asked and wasn't what the OP was looking for. I opined on the use cases and arguments for shipping hook scripts in the repository versus managing them externally in the comments below.
Looks like a lot of the posts are out of date, at least if you're using pre-commit in the python ecosystem (+ I found that changing the git hook path fails with slightly older versions of git, e.g. 2.3). With a .pre-commit-config.yaml in a hooks dir in the root of your repo, the easiest solution is to run:
pre-commit install -f --config hooks/.pre-commit-config.yaml
You could use a managed solution for pre-commit hook management like pre-commit. Or a centralized solution for server-side git-hooks like Datree.io.
It has built-in policies like:
- Detect and prevent merging of secrets.
- Enforce proper Git user configuration.
- Enforce Jira ticket integration - mention ticket number in pull request name / commit message.
It won't replace all of your hooks, but it might help your developers with the most obvious ones without the configuration hell of installing the hooks on every developer's computer/repository.
Disclaimer: I am one of Datrees founders
© 2022 - 2024 — McMap. All rights reserved.
.git-suggested
directory, prompt the user if they would like to copy its contents into.git/{hooks,config,info,(whatever is safe)}
. Smoothly handling updates to suggested hooks would be tricky, though. – Flyover