Is there a way to restrict merging from a specific branch into other branches? Allow me to explain:
I have a 'testing' branch and a 'master' branch in Gitlab. The team creates feature branches, merges them into 'testing' for approval and then merge the feature branch into 'master' once approved.
Sometimes, it can take months to get approval of some features, and therefore code is sat in the 'testing' branch for a while. Meanwhile, another feature branch may try to merge into 'testing' and conflicts will arise. This is expected, however, we are only human, and occasionally someone may accidentally merge 'testing' into their feature branch when handling the conflict, which is obviously wrong. Instead, we should switch to 'testing' and merge our feature branch into 'testing' thus managing the conflict within the testing branch.
Any advise is appreciated.
No, GitHub doesn't let you restrict who can perform a merge. However, if you want to require a specific group of people to approve a PR before merging, use the CODEOWNERS file and require an approval from a code owner before merging in the branch protection settings.
Use git-reset or git merge --abort to cancel a merge that had conflicts. Please note that all the changes will be reset, and this operation cannot be reverted, so make sure to commit or git-stash all your changes before you start a merge.
To begin, be sure your needs is very normal and traditional. The answer is ... Yes.
These are some useful links:
To help you (and for fun ^^), I wrote a dedicated hook in Python to reach your specific needs (you just need to adapt FORBIDDEN_SOURCE_BRANCH and FORBIDDEN_IF_NOT_DEST_BRANCH if you want to work with some other branches).
#!/bin/python
##
## Author: Bertrand Benoit <mailto:[email protected]>
## Description: Git Hook (server-side) allowing to prevent merge from some branches to anothers
## Version: 0.9
import sys, subprocess, re
FORBIDDEN_SOURCE_BRANCH='testing'
FORBIDDEN_IF_NOT_DEST_BRANCH='master'
# Considers only merge commit.
if not (len(sys.argv) >=2 and sys.argv[2] == 'merge'):
sys.exit(0)
# Defines which is the source branch.
with open(sys.argv[1], 'r') as f:
mergeMessage=f.readline()
mergeBranchExtract=re.compile("Merge branch '([^']*)'.*$").search(mergeMessage)
if not mergeBranchExtract:
print('Unable to extract branch to merge from message: ', mergeMessage)
sys.exit(0) # Ensures normal merge as failback
# Checks if the merge (source) branch is one of those to check.
mergeBranch=mergeBranchExtract.group(1)
if mergeBranch != FORBIDDEN_SOURCE_BRANCH:
sys.exit(0) # It is NOT the forbidden source branch, so keeps on normal merge
# Defines which is the current branch.
currentBranchFullName=subprocess.check_output(['git', 'symbolic-ref', 'HEAD'])
currentBranchExtract=re.compile("^.*/([^/]*)\n$").search(currentBranchFullName)
if not currentBranchExtract:
print('Unable to extract current branch from: ', currentBranchFullName)
sys.exit(1) # Ensures normal merge as failback
# Checks if the current (destination) branch is one of those to check.
currentBranch=currentBranchExtract.group(1)
if currentBranch != FORBIDDEN_IF_NOT_DEST_BRANCH:
print("FORBIDDEN: Merging from '" + mergeBranch + "' to '" + currentBranch + "' is NOT allowed. Contact your administrator. Now, you should use git merge --abort and keep on your work.")
sys.exit(1) # This is exactly the situation which is forbidden
# All is OK, so keeps on normal merge
sys.exit(0)
To share all this work, I created a new Gitlab repository, in which I'll add further hooks when needed :)
This is the complete documentation about that.
Let me know if you need further help.
So, what you want your pre-receive hook to reject is any branch push that incorporates the current testing
tip if there's something there, unless the push is to testing
itself, or to master
. It's almost as easy to do that as to say it:
testtip=`git rev-parse testing`
[[ `git merge-base testing master` = $testtip ]] && exit 0 # okay if testing's merged
while read old new ref; do
[[ $ref = refs/heads/* ]] || continue # only care about branches
[[ $new = *[^0]* ]] || continue # not checking branch deletions
[[ $ref = */master ]] && continue # not checking pushes to master
[[ $ref = */testing ]] && continue # nor testing
range=$new; [[ $old = *[^0]* ]] && range=$old..$new
[[ `git rev-list --first-parent $range` != *$testtip* ]] \
&& [[ `git rev-list $range` = *$testtip* ]] \
&& {
echo push to $ref merges from unmerged testing commits, rejecting push
exit 1
}
done
edit: whoops, it was rejecting anything based on testing, not just anything that merged it. fixed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With