I was looking for a way to accomplish remote branches protection using a pre-push git hook. What I needed was basically a way to prevent people, and myself, from accidentally pushing to
master. I took the lazy approach went to Google for a quick solution which I could just copy and paste.
And I found a lot. However I noticed that most of them share one issue, which makes them unreliable in some scenarios. The most common way to implement such a hook is to use
git rev-parse or
git symbolic-ref to figure out the current local branch, the next step is to assume that the current branch will be pushed to a branch with the same name on the remote and prevent it if that’s the case. This, as mentioned, is not always the best approach.
Consider a repository where
push.default has been set to
matching, according to the git-config documentation the behavior of
git push will be the following:
Push all branches having the same name on both ends.
This means that a
git push done from a
feature branch or
bugfix branch will push all the other branches as well, including
master and any branch we are trying to protect. Since the hook only checks the current branch name, it can easily be fooled with this setup. If there are unpublished commits on
master they will just be pushed and the hook will not notice.
Another dangerous scenario is the use of
git push --all this would also not be detected and write directly to master.
So there are cases in which looking at the command line or using
git rev-parse or
git symbolic-ref is just not good enough.
A different approach
This is why I came up with my own pre-push hook trying to address this kind of issues. Turns out that the pre-push hook receives from
stdin a list of very useful values, again from the githooks documentation
local_ref local_sha1 remote_ref remote_sha1
Reading these values provided to us by git is a lot more reliable and secure, the check is done on
remote_ref, which is compared against a list of protected remote refs. If a push is attempted to a protected remote ref then the hook returns error. That simple.
So that’s it, I thought it would be nice to share this. The python3 code for this hook can be found below. To use it, copy it inside
.git/hooks/pre-push in your repository and remember to set it to executable. If the hook is not executable it will be skipped with no error.