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
srcfolder with aninfo.jsonfile 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-dotenvScript
-
Load
GITLAB_TOKEN(highly recommended)For this create a file called
.envin the project rootfrom dotenv import load_dotenvload_dotenv()GITLAB_TOKEN = os.environ["GITLAB_TOKEN"] -
Setup variables
Setting up necessary constants for the script.
GITLAB_URL = 'http://gitlab.example.com:8929' # update the gitlab link hereNAMESPACE = "basdemo" # update namespace here it could also be <group_name>/<sub_group_name>PROJECT_NAME = "batch-file-executor"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_branchNEW_BRANCH = "add_new_branch" # update branch name here#check if the branch exists, if not create itif 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 -
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 > Tokensfrom dotenv import load_dotenvimport os
load_dotenv()
GITLAB_TOKEN = os.environ["GITLAB_TOKEN"]
## Setup variablesimport gitlabimport base64
GITLAB_URL = 'http://gitlab.example.com:8929' # Update the gitlab link here
NAMESPACE = "basdemo"PROJECT_NAME = "automatic-mr"ASSIGNEE_USERNAME = "basdemo"
## Authenticate with Gitlabgl = gitlab.Gitlab(GITLAB_URL, private_token=GITLAB_TOKEN)
## Get the projectPROJECT_ID = f"{NAMESPACE}/{PROJECT_NAME}"project = gl.projects.get(PROJECT_ID)
## Create a new branch from default branchMAIN_BRANCH = project.default_branchNEW_BRANCH = "add_new_branch" # update branch name here
#check if the branch exists, if not create itif 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 IDuser = 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 filewith open(local_file, 'r') as file: file_content = file.read()
# Check if the file exists, if not, create ittry: 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 contentreadme_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 contentupdated_content = readme_content.replace(old_text, new_text)file.content = updated_contentfile.encoding = 'text'
# Append to the filesfiles.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-requestmerge_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}")GITLAB_TOKEN=<your-token-here>{ "string": "Text", "number": 1, "boolean": true}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.
