Skip to content
Home

Create GITLAB merge request programmatically

Introduction

In the world of DevOps and software engineering, manual tasks are the enemy of productivity. While creating a Merge Request (MR) in GitLab only takes a minute, doing it hundreds of times for version bumps, configuration updates, or boilerplate synchronization adds up quickly.

In this guide, we will walk through how to programmatically create branches, commit file changes, and open Merge Requests using the python-gitlab library.

Why Automate MRs?

Automating this workflow is incredibly useful for:

  • Bot updates: Automatically updating dependency versions.
  • Template synchronization: Rolling out config changes across multiple repositories.
  • CI/CD Pipelines: Triggering MRs based on build artifacts.

Prerequisites

Before diving into the code, you will need a few things set up:

  • GitLab Personal Access Token: Generate this in your User Settings > Preferences > Access Tokens.
  • Python Libraries: You will need to install the GitLab wrapper and dotenv for security.
  • Local Files: For this tutorial, ensure you have a src folder with an info.json file inside it.

Jump to complete code here

Prepare script environment

I will be using poetry. check out installation details here

Setup a poetry project using the command poetry init

Add the required packages

poetry add python-gitlab python-dotenv

Script

  1. Load GITLAB_TOKEN (highly recommended)

    For this create a file called .env in the project root

    from dotenv import load_dotenv
    load_dotenv()
    GITLAB_TOKEN = os.environ["GITLAB_TOKEN"]
  2. Setup variables

    Setting up necessary constants for the script.

    GITLAB_URL = 'http://gitlab.example.com:8929' # update the gitlab link here
    NAMESPACE = "basdemo" # update namespace here it could also be <group_name>/<sub_group_name>
    PROJECT_NAME = "batch-file-executor"
    ASSIGNEE_USERNAME = "basdemo"
  3. Authenticate with Gitlab

    gl = gitlab.Gitlab(GITLAB_URL, private_token=GITLAB_TOKEN)
  4. Get the project

    PROJECT_ID = f"{NAMESPACE}/{PROJECT_NAME}"
    project = gl.projects.get(PROJECT_ID)
  5. Create a new branch from default branch

    MAIN_BRANCH = project.default_branch
    NEW_BRANCH = "add_new_branch" # update branch name here
    #check if the branch exists, if not create it
    if not any(branch.name == NEW_BRANCH for branch in project.branches.list()):
    project.branches.create({"branch": NEW_BRANCH, "ref": MAIN_BRANCH})
    print(f"Branch '{NEW_BRANCH}' created.")
  6. Get User ID.

    user = gl.users.list(username=ASSIGNEE_USERNAME)[0]
    assignee_id = user.id
  7. Create merge-request

    merge_request = project.mergerequests.create({
    "source_branch":NEW_BRANCH,
    "target_branch":MAIN_BRANCH,
    "title":"feat/adding automatic merge request",
    "description":"This merge request is created programmatically",
    "assignee_id": assignee_id
    })
    print(f"Merge Request created: {merge_request.web_url}")

Complete code

Folder structure

  • Directorysrc
    • info.json
  • .env
  • automatic-merge-request.py
  • pyproject.toml
## Load Gitlab Token from User > Prefrence > Tokens
from dotenv import load_dotenv
import os
load_dotenv()
GITLAB_TOKEN = os.environ["GITLAB_TOKEN"]
## Setup variables
import gitlab
import base64
GITLAB_URL = 'http://gitlab.example.com:8929' # Update the gitlab link here
NAMESPACE = "basdemo"
PROJECT_NAME = "automatic-mr"
ASSIGNEE_USERNAME = "basdemo"
## Authenticate with Gitlab
gl = gitlab.Gitlab(GITLAB_URL, private_token=GITLAB_TOKEN)
## Get the project
PROJECT_ID = f"{NAMESPACE}/{PROJECT_NAME}"
project = gl.projects.get(PROJECT_ID)
## Create a new branch from default branch
MAIN_BRANCH = project.default_branch
NEW_BRANCH = "add_new_branch" # update branch name here
#check if the branch exists, if not create it
if not any(branch.name == NEW_BRANCH for branch in project.branches.list()):
project.branches.create({"branch": NEW_BRANCH, "ref": MAIN_BRANCH})
print(f"Branch '{NEW_BRANCH}' created.")
## Get User ID
user = gl.users.list(username=ASSIGNEE_USERNAME)[0]
assignee_id = user.id
## Add files (optional)
files = []
local_file = "src/info.json"
remote_file_path = "demo/info.json"
# Read the file content from local file
with open(local_file, 'r') as file:
file_content = file.read()
# Check if the file exists, if not, create it
try:
file = project.files.get(file_path=remote_file_path, ref=NEW_BRANCH)
print(f"File '{remote_file_path}' already exists in '{NEW_BRANCH}'.")
except gitlab.exceptions.GitlabGetError:
# Create the file in the new branch
files.append({
'action': 'create',
'file_path': remote_file_path,
'content': file_content
})
## Update Readme (optional)
file_path = 'README.md'
file = project.files.get(file_path=file_path, ref=NEW_BRANCH)
# Read the content
readme_content = base64.b64decode(file.content).decode('utf-8')
old_text = "# automatic-mr"
new_text = f"# automatic-mr\n - Adding `info.json` using automatic merge request"
# Update the content
updated_content = readme_content.replace(old_text, new_text)
file.content = updated_content
file.encoding = 'text'
# Append to the files
files.append({
'action': 'update',
'file_path': file_path,
'content': updated_content
})
## Create commit (optional)
if len(files) > 0:
commit_message = 'Update multiple files - info.json and README.md'
commit_data = {
'branch': NEW_BRANCH,
'commit_message': commit_message,
'actions': files,
}
commit = project.commits.create(commit_data)
print(f"Commit created: {commit.id} - {commit_message}")
## Create merge-request
merge_request = project.mergerequests.create({
"source_branch":NEW_BRANCH,
"target_branch":MAIN_BRANCH,
"title":"feat/adding automatic merge request",
"description":"This merge request is created programmatically",
"assignee_id": assignee_id
})
print(f"Merge Request created: {merge_request.web_url}")

Summary

By leveraging the python-gitlab library, you can turn a multi-step manual process into a script that runs in seconds. This approach ensures consistency in your commits, reduces human error, and allows your team to focus on coding rather than administrative Git tasks.