Skip to content

Instantly share code, notes, and snippets.

@pixelhandler
Last active July 2, 2024 11:27
Show Gist options
  • Save pixelhandler/5718585 to your computer and use it in GitHub Desktop.
Save pixelhandler/5718585 to your computer and use it in GitHub Desktop.
Git pre-push hook to prevent force pushing master branch
#!/bin/sh
# Called by "git push" after it has checked the remote status,
# but before anything has been pushed.
#
# If this script exits with a non-zero status nothing will be pushed.
#
# Steps to install, from the root directory of your repo...
# 1. Copy the file into your repo at `.git/hooks/pre-push`
# 2. Set executable permissions, run `chmod +x .git/hooks/pre-push`
# 3. Or, use `rake hooks:pre_push` to install
#
# Try a force push to master, you should get a message `*** [Policy] never force push...`
#
# The commands below will not be allowed...
# `git push --force origin master`
# `git push --delete origin master`
# `git push origin :master`
#
# Nor will a force push while on the master branch be allowed...
# `git co master`
# `git push --force origin`
#
# Requires git 1.8.2 or newer
#
# Git 1.8.2 release notes cover the new pre-push hook:
# <https://github.com/git/git/blob/master/Documentation/RelNotes/1.8.2.txt>
#
# See Sample pre-push script:
# <https://github.com/git/git/blob/87c86dd14abe8db7d00b0df5661ef8cf147a72a3/templates/hooks--pre-push.sample>
protected_branch='master'
policy='[Policy] Never force push or delete the '$protected_branch' branch! (Prevented with pre-push hook.)'
current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')
push_command=$(ps -ocommand= -p $PPID)
is_destructive='force|delete|\-f'
will_remove_protected_branch=':'$protected_branch
do_exit(){
echo $policy
exit 1
}
if [[ $push_command =~ $is_destructive ]] && [ $current_branch = $protected_branch ]; then
do_exit
fi
if [[ $push_command =~ $is_destructive ]] && [[ $push_command =~ $protected_branch ]]; then
do_exit
fi
if [[ $push_command =~ $will_remove_protected_branch ]]; then
do_exit
fi
unset do_exit
exit 0
@nhoag
Copy link

nhoag commented Sep 15, 2016

Over 3 years later and this is still a great script!

Note that this doesn't protect against git push --force with a Git post config as default = matching. You're covered with any other post config, and Git 2.0+ uses simple by default.

@gajendramani
Copy link

any idea in groovy as pre receieve hook.

@wojciech-kopras
Copy link

To prevent "git push --force" on a remote server it is impossible using a hook, because you cannot know the command line options given. In that case you need to set in the repository's configuration (or any level above) these options:

[receive]
        denyNonFastForwards = true
        denyDeletes = true

@dimon777
Copy link

I tried this but it doesn't show the message, and nothing is pushed to master (which is expected)

$ git push
Everything up-to-date
$ git push --force
Everything up-to-date

Why is this?

@AaronLiuIsCool
Copy link

any idea in groovy as pre receieve hook.

You can use maven or any binary build process to check-exist or install local hook.

@devinrhode2
Copy link

I don't know when this may have changed, but with git 2.32.1 and 2.33, I had to detect branch deletion like so:

STDIN=$(cat -)
[[ $STDIN =~ "(delete)" ]] && echo "deleting branch";

@devinrhode2
Copy link

$1 still maps to origin (or upstream, etc)

@milanpoudel1
Copy link

Here $1 means origin? Is it possible to get the branch name we are trying to push into ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment