Per discussion in the git book and on stackoverflow:
http://stackoverflow.com/questions/1754491/is-there-a-way-to-configure-git-repository-to-reject-git-push-force
http://git-scm.com/book/en/Customizing-Git-An-Example-Git-Enforced-Policy
Set these values on the git repo you want to protect:
git config receive.denyNonFastforwards true git config receive.denyDeletes true
This may also be set as a system wide default via:
git config --system receive.denyNonFastforwards true git config --system receive.denyDeletes true
Demonstration that this will indeed prevent forced pushes:
jhoblitt@leo ~/test $ git init --bare testrepo.git Initialized empty Git repository in /home/jhoblitt/test/testrepo.git/ jhoblitt@leo ~/test $ cd testrepo.git/ jhoblitt@leo ~/test/testrepo.git $ git config receive.denyNonFastforwards true jhoblitt@leo ~/test/testrepo.git $ git config receive.denyDeletes true jhoblitt@leo ~/test/testrepo.git $ cd .. jhoblitt@leo ~/test $ git clone ./testrepo.git/ testclone Cloning into 'testclone'... warning: You appear to have cloned an empty repository. done. jhoblitt@leo ~/test $ cd testclone/ jhoblitt@leo ~/test/testclone $ echo "1" > foo jhoblitt@leo ~/test/testclone $ git add foo jhoblitt@leo ~/test/testclone $ git commit -m"first commit" [master (root-commit) 98667fc] first commit 1 file changed, 1 insertion(+) create mode 100644 foo jhoblitt@leo ~/test/testclone $ echo "2" > foo jhoblitt@leo ~/test/testclone $ git add foo jhoblitt@leo ~/test/testclone $ git commit -m"second commit" [master 325237a] second commit 1 file changed, 1 insertion(+), 1 deletion(-) jhoblitt@leo ~/test/testclone $ echo "3" > foo jhoblitt@leo ~/test/testclone $ git add foo jhoblitt@leo ~/test/testclone $ git commit -m"oops, last commit should have been 3" [master bb776bd] oops, last commit should have been 3 1 file changed, 1 insertion(+), 1 deletion(-) jhoblitt@leo ~/test/testclone $ git push origin master Counting objects: 8, done. Delta compression using up to 8 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (6/6), 460 bytes, done. Total 6 (delta 0), reused 0 (delta 0) To /home/jhoblitt/test/testrepo.git/ 98667fc..bb776bd master -> master jhoblitt@leo ~/test/testclone $ git rebase -i HEAD~2 [detached HEAD b88ab29] second commit 1 file changed, 1 insertion(+), 1 deletion(-) Successfully rebased and updated refs/heads/master. jhoblitt@leo ~/test/testclone $ git push origin master To /home/jhoblitt/test/testrepo.git/ ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to '/home/jhoblitt/test/testrepo.git/' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Merge the remote changes (e.g. 'git pull') hint: before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. jhoblitt@leo ~/test/testclone $ git push --force origin master Counting objects: 5, done. Writing objects: 100% (3/3), 243 bytes, done. Total 3 (delta 0), reused 0 (delta 0) remote: error: denying non-fast-forward refs/heads/master (you should pull first) To /home/jhoblitt/test/testrepo.git/ ! [remote rejected] master -> master (non-fast-forward) error: failed to push some refs to '/home/jhoblitt/test/testrepo.git/'