Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to restrict merging specific branch into other branches in Gitlab?

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.

like image 792
David Avatar asked Nov 02 '18 08:11

David


People also ask

How do I restrict git merge?

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.

How do I stop a branch from merging?

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.


2 Answers

To begin, be sure your needs is very normal and traditional. The answer is ... Yes.

How to prevent merging from a branch to another, setting up a server Git Hook

These are some useful links:

  • Git Hook explanations in Official Git Book
  • GitLab server-side Hook explanations
  • An example with a Git Hook written in Ruby to prevent merging 'staging' branch to 'master' one

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 :)

For information, you can also setup protected branches to keep them safe from some users

This is the complete documentation about that.

Let me know if you need further help.

like image 158
Bsquare ℬℬ Avatar answered Oct 12 '22 23:10

Bsquare ℬℬ


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.

like image 26
jthill Avatar answered Oct 12 '22 21:10

jthill