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

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.


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.

Tags:

Git

Merge

Gitlab