Reputation: 31491
I would like to run a linter on git status
, however there seems to be no pre-status
nor post-status
hook. How could one add a hook to git? The fine docs are suspiciously silent on the matter!
I'm currently wrapping my linter and git status
in a Bash script, but I would prefer a solution which supports my muscle-memory-macro git status
. I'm running CentOS 7.3 with KDE 4 if it matters.
Upvotes: 15
Views: 2257
Reputation: 52887
git
hooks, such as pre-git status
and post-git status
Expanding on @Leon's answer, here's a more complete example of how to add custom git
hooks, such as pre-git status
and post-git status
into your ~/.bashrc
file.
First off, some notes:
.git/hooks
directory for a given repository. If you can use those, do. See here: https://git-scm.com/docs/githooks.~/.bashrc
file (or equivalent) instead under perhaps a few circumstances:
git status
hook). So, you manually create it here.git
wrapper function, as shown below, you can add as many custom hooks as you want to it.git
wrapper via the Bash function named git
below, you may want to go back to all of your other user-interactive scripts or functions which call git
, and change each git
call to command git
instead.
command git
is used in our custom git
function below. This is a Bash built-in function which causes the real git
command to be called, instead of our custom git
function.git
, you may not nee your custom hooks to run, so using command git
will ensure they don't.So, here's one way to write a git
wrapper to get it done:
First, create a ~/.git_custom_hooks
file. Then, source it in your ~/.bashrc
file by adding the following to the bottom of your ~/bashrc
file:
# Import this ".git_custom_hooks" file, if it exists.
if [ -f "~/.git_custom_hooks" ]; then
. "~/.git_custom_hooks"
fi
Now, place your custom hooks into the ~/.git_custom_hooks
file, like this.
This is from my home/.git_custom_hooks
file in my eRCaGuy_dotfiles repo. For the latest version of my custom git hooks, see that file.
# (all `git` cmds)
pre_git_hook()
{
# echo "=== Running pre_git_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
post_git_hook()
{
# echo "=== Running post_git_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
# status
pre_git_status_hook()
{
# echo "=== Running pre_git_status_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
post_git_status_hook()
{
# echo "=== Running post_git_status_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
# commit
pre_git_commit_hook()
{
# echo "=== Running pre_git_commit_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
post_git_commit_hook()
{
# echo "=== Running post_git_commit_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
# push
pre_git_push_hook()
{
# echo "=== Running pre_git_push_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
post_git_push_hook()
{
# echo "=== Running post_git_push_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
# pull
pre_git_pull_hook()
{
# echo "=== Running pre_git_pull_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
post_git_pull_hook()
{
# echo "=== Running post_git_pull_hook() function. ===" # debugging
# do stuff
true # do nothing (bash functions can't be empty); delete this line
# once you add your own code
}
# Outer-most git hook wrapper function
git()
{
echo "=== Running custom git() wrapper function. ===" # debugging
# Note: if no argument is passed in, this just sets cmd to an empty string
cmd="$1"
# echo "cmd = $cmd" # debugging
# Pre-hooks
pre_git_hook
# Note: in Bash if statements, `[[ ]]` is recommended over `[ ]` because
# `[[ ]]` is faster. See my answer here:
# https://stackoverflow.com/a/77291070/4561887
if [[ "$cmd" == "status" ]]; then
pre_git_status_hook
elif [[ "$cmd" == "commit" ]]; then
pre_git_commit_hook
elif [[ "$cmd" == "push" ]]; then
pre_git_push_hook
elif [[ "$cmd" == "pull" ]]; then
pre_git_pull_hook
fi
# Run the actual git command; the `command` built-in is used to force the
# real `git` command to get called here, to prevent infinite recursion.
command git "$@"
return_val="$?"
# Post-hooks
if [[ "$cmd" == "status" ]]; then
post_git_status_hook
elif [[ "$cmd" == "commit" ]]; then
post_git_commit_hook
elif [[ "$cmd" == "push" ]]; then
post_git_push_hook
elif [[ "$cmd" == "pull" ]]; then
post_git_pull_hook
fi
post_git_hook
# Be sure to return the return value of the actual git command, not the
# return value of this function nor our hooks.
return "$return_val"
}
Now, either close and re-open each terminal, or re-source your ~/.bashrc
file by running . ~/.bashrc
in each open terminal.
With the code exactly as-is above, running any git
command will add this to the first line of the output to remind you that it is in effect:
=== Running custom git() wrapper function. ===
Example run and output of git status
:
eRCaGuy_dotfiles$ git status
=== Running custom git() wrapper function. ===
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
If you uncomment all of the echo
statements in each pre and post hook function, you'll see the following output when you run git status
or git push
, for instance. Notice all of the lines which begin with ===
to indicate where the pre and post hooks run:
eRCaGuy_dotfiles$ git status
=== Running custom git() wrapper function. ===
=== Running pre_git_hook() function. ===
=== Running pre_git_status_hook() function. ===
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: home/.git_custom_hooks
no changes added to commit (use "git add" and/or "git commit -a")
=== Running post_git_status_hook() function. ===
=== Running post_git_hook() function. ===
And:
eRCaGuy_dotfiles$ git push
=== Running custom git() wrapper function. ===
=== Running pre_git_hook() function. ===
=== Running pre_git_push_hook() function. ===
Everything up-to-date
=== Running post_git_push_hook() function. ===
=== Running post_git_hook() function. ===
I'm going to add a custom pre-git status
hook to handle this new problem that cropped up in VSCode in the last couple weeks. Here's my bug report: Saving the settings.json file in VSCode mistakenly wipes and recreates the file with a new inode number, breaking symlinks and hard links to it.
[[ ]]
is faster than [ ]
in Bash if statements`: Are double square brackets [[ ]] preferable over single square brackets [ ] in Bash?settings.json
and keybindings.json
files whenever they changeUpvotes: 2
Reputation: 32494
Git hooks are for operations that (are going to) modify the repository or the working tree. Since git status
is a read-only operation there is no hook for it.
I'm currently wrapping my linter and
git status
in a Bash script, but I would prefer a solution which supports my muscle-memory-macrogit status
.
You can wrap your git
command into the following function that will not require to adjust your muscle memory:
git()
{
if [[ $# -ge 1 && "$1" == "status" ]]
then
echo Your git-status pre-hook should be here
fi
command git "$@"
}
Upvotes: 17