Skip to content

Instantly share code, notes, and snippets.

@stefansundin
Last active October 12, 2023 10:33
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save stefansundin/d465f1e331fc5c632088 to your computer and use it in GitHub Desktop.
Save stefansundin/d465f1e331fc5c632088 to your computer and use it in GitHub Desktop.
Git pre-push hook to prevent force pushing the master/main branch.
#!/bin/bash
# This script will install a Git pre-push hook that prevents force pushing the master/main branch.
# There are three variants that I have built:
# - pre-push: prevents force-pushing to master/main.
# - pre-push-2: prevents force-pushing to master/main depending on the remote (you need to edit the file!).
# - pre-push-3: prevents any type of pushing to master/main.
# Set the desired version like this before proceeding:
# FILE=pre-push
# Single repo installation:
# curl -fL https://gist.githubusercontent.com/stefansundin/d465f1e331fc5c632088/raw/install-pre-push.sh | sh -s ${FILE:-pre-push}
# Uninstall:
# rm .git/hooks/pre-push
# Global installation instructions:
# mkdir $HOME/.githooks
# git config --global core.hooksPath $HOME/.githooks
# curl -fL -o $HOME/.githooks/pre-push https://gist.githubusercontent.com/stefansundin/d465f1e331fc5c632088/raw/${FILE:-pre-push}
# chmod +x $HOME/.githooks/pre-push
# Uninstall:
# rm $HOME/.githooks/pre-push
GIT_DIR=`git rev-parse --git-common-dir 2> /dev/null`
echo
echo
if [ "$GIT_DIR" == "" ]; then
echo This does not appear to be a git repo.
exit 1
fi
if [ -f "$GIT_DIR/hooks/pre-push" ]; then
echo There is already a pre-push hook installed. Delete it first.
echo
echo " rm '$GIT_DIR/hooks/pre-push'"
echo
exit 2
fi
FILE=${1:-pre-push}
echo "Downloading $FILE hook from https://gist.github.com/stefansundin/d465f1e331fc5c632088"
echo
curl -fL -o "$GIT_DIR/hooks/pre-push" "https://gist.githubusercontent.com/stefansundin/d465f1e331fc5c632088/raw/$FILE"
if [ ! -f "$GIT_DIR/hooks/pre-push" ]; then
echo Error downloading pre-push script!
exit 3
fi
chmod +x "$GIT_DIR/hooks/pre-push"
echo "You're all set!"
echo "P.S. There is now a way to install this globally, see the instructions on the gist page."
exit 0
#!/bin/bash
# Prevents force-pushing to master/main.
# Install:
# cd path/to/git/repo
# curl -fL -o .git/hooks/pre-push https://gist.githubusercontent.com/stefansundin/d465f1e331fc5c632088/raw/pre-push
# chmod +x .git/hooks/pre-push
BRANCH=`git rev-parse --abbrev-ref HEAD`
PUSH_COMMAND=`ps -ocommand= -p $PPID`
if [[ "$BRANCH" =~ ^(master|main)$ && "$PUSH_COMMAND" =~ force|delete|-f ]]; then
echo
echo "Prevented force-push to $BRANCH. This is a very dangerous command."
echo "If you really want to do this, use --no-verify to bypass this pre-push hook."
echo
exit 1
fi
exit 0
#!/bin/bash
# Use this file to prevent force pushes only to specific remotes (e.g. work).
# You have to customize it below ("workorg/").
# Prevents force-pushing to master.
# Install:
# cd path/to/git/repo
# curl -fL -o .git/hooks/pre-push https://gist.githubusercontent.com/stefansundin/d465f1e331fc5c632088/raw/pre-push-2
# chmod +x .git/hooks/pre-push
ORIGIN="$1"
REMOTE_URL="$2"
BRANCH=`git rev-parse --abbrev-ref HEAD`
PUSH_COMMAND=`ps -ocommand= -p $PPID`
if [[ "$BRANCH" =~ ^(master|main)$ && "$PUSH_COMMAND" =~ force|delete|-f && "$REMOTE_URL" == *"workorg/"* ]]; then
echo
echo "Prevented force-push to $BRANCH. This is a very dangerous command."
echo "If you really want to do this, use --no-verify to bypass this pre-push hook."
echo
exit 1
fi
exit 0
#!/bin/bash
# Prevents any type of pushing to master/main.
# Install:
# cd path/to/git/repo
# curl -fL -o .git/hooks/pre-push https://gist.githubusercontent.com/stefansundin/d465f1e331fc5c632088/raw/pre-push
# chmod +x .git/hooks/pre-push
BRANCH=`git rev-parse --abbrev-ref HEAD`
if [[ "$BRANCH" =~ ^(master|main)$ ]]; then
echo
echo "Prevented push to $BRANCH."
echo "If you really want to do this, use --no-verify to bypass this pre-push hook."
echo
exit 1
fi
exit 0
@dlin-me
Copy link

dlin-me commented Nov 20, 2014

Thanks

@bean5
Copy link

bean5 commented Dec 17, 2014

Nice. You reference this page in the file itself. Handy.

@centerge
Copy link

centerge commented Jan 6, 2015

thanks for this!

@brange
Copy link

brange commented Jun 26, 2015

I changed the $PUSH_COMMAND comparison to =~ force|delete|-f|"+master" to prevent git push origin +master

@rajah1313
Copy link

is there a way to have this hook available on a new clone of the repository ? so basically it is somehow installed on the server and then gets installed on the client side with a clone. the idea being to stop accidental checkin to master for e.g.

@stefansundin
Copy link
Author

@rajah1313 Not from the server to a client, but you can change your default git hooks that are created when you clone a repository, and include this hook there. See this thread for details: http://stackoverflow.com/questions/1977610/change-default-git-hooks

If you have control over the server, you can install a git hook there to prevent force pushes etc. GitHub also has protected branches these days, which didn't exist when I created this.

@stefansundin
Copy link
Author

I just updated the instructions above to include instructions for a global installation using the core.hooksPath configuration option introduced in Git 2.9. Update to that and you no longer have to install this in every git repository, just do it once.

@bilogic
Copy link

bilogic commented Jan 23, 2023

I ran the global installation as user a, but I could still commit master of a repo owned by user a with no errors.
I suppose this blocks only push?

@stefansundin
Copy link
Author

@bilogic Yes. For a pre-commit hook see https://gist.github.com/stefansundin/9059706.

I also no longer use the global install myself so I do not recommend it.

@bilogic
Copy link

bilogic commented Jan 24, 2023

@stefansundin i see thanks.

Here's what I understood:

  1. I have origin at /repository/origin
  2. I check out master to /repository/local
  3. This pre-push hook must exists at /repository/local/.git/hooks to have the desired effect
  4. But seems to me that it can also be removed by the person in control of /repository/local
  5. Is there a way to prevent a push from /repository/local into /repository/origin's master, controlled by the origin?

@stefansundin
Copy link
Author

@bilogic Correct, this hook is all local. GitHub has support for protected branches now, which you can use to prevent pushes to certain branches (they didn't have this feature back when I wrote this). https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches

@bilogic
Copy link

bilogic commented Jan 24, 2023

@stefansundin I wanted to prevent a push without GitHub. Anyway, thanks!

@stefansundin
Copy link
Author

@bilogic You probably want a pre-receive hook if you have control over the server. I don't have a script ready for you so you are on your own. 😆

It would really have accelerated things if you posted more information up-front, but I guess we finally arrived at the solution. 😅

Good luck!

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