The following pre-receive hook will block those:
#/bin/bash
# Copyright (c) 2016 G. Sylvie Davies. http://bit-booster.com/
# Copyright (c) 2016 torek. http://stackoverflow.com/users/1256452/torek
# License: MIT license. https://opensource.org/licenses/MIT
while read oldrev newrev refname
do
if [ "$refname" = "refs/heads/master" ]; then
MATCH=`git log --first-parent --pretty='%H %P' $oldrev..$newrev |
grep $oldrev |
awk '{ print \$2 }'`
if [ "$oldrev" = "$MATCH" ]; then
exit 0
else
echo "*** PUSH REJECTED! FOXTROT MERGE BLOCKED!!! ***"
exit 1
fi
fi
done
If you’re using Github / Gitlab / Bitbucket Cloud, you may need to look into creating some kind of call into their commit status apis (here’s api docs for: bitbucket, github; not sure if gitlab has one), because you don’t have access to the pre-receive hooks, and even if you did, you’d still have to deal with people clicking the “merge” button directly in the web ui of those products (in which case there is no “push”).
With Bitbucket Server you can install the add-on I created.
Once it’s installed you click “enable” on the “Protect First Parent Hook” in a given repository’s “hook” settings:
It will block foxtrot merges via push and via the “merge” button in the Bitbucket Server UI. It does this even if its license is expired, making the “Protect First-Parent Hook” a free component of the larger add-on.
Here’s an example of my Bit-Booster “Protect First Parent” pre-receive hook in action:
$ ​git pull
$ git push
remote: *** PUSH REJECTED BY Protect-First-Parent HOOK ***
remote:
remote: Merge [1f70043b34d3] is not allowed. *Current* master must appear
remote: in the 'first-parent' position of the subsequent commit. To see how
remote: master is merging into the wrong side (not as 1st parent), try this:
remote:
remote: git show --graph -s --pretty='%h %d%n' \
remote: 1f70043b34d3 1f70043b34d3~1 origin/master
remote:
remote: To fix, there are two traditional solutions:
remote:
remote: 1. (Preferred) rebase your branch:
remote:
remote: git rebase origin/master
remote: git push origin master
remote:
remote: 2. Redo the merge in the correct direction:
remote:
remote: git checkout master
remote: git reset --hard origin/master
remote: git merge --no-ff 1f70043b34d3eaedb750~1
remote: git push origin master
remote:
For more background on foxtrot merges I wrote a blog post.