Skip to content

About

Create MR in GitLab automatically

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

GitLab Auto MR - Go Version

Automatically create Merge Requests in GitLab using a lightweight Go binary.

Features

  • ✅ Zero external dependencies - uses only Go standard library
  • ✅ Small Docker image (Alpine-based)
  • ✅ Fast execution
  • ✅ Compatible with GitLab CI/CD
  • ✅ Cross-platform support (Linux, macOS, Windows)
  • ✅ Safe MR management - explicit control over creating or updating MRs
  • ✅ Prevents accidental overwrites - requires --update-mr flag to update existing MRs

Docker Images

Docker images are automatically built and published to GitHub Container Registry for each release.

Available Tags

  • latest - Latest stable release
  • vX.Y.Z - Specific version
  • vX.Y - Latest patch version of a minor release
  • vX - Latest minor and patch version of a major release (e.g., v1)

The image runs as uid 10001, not root. The tool itself only needs outbound HTTPS, but anything else in the same job that writes to the workspace must be able to do so as that user.

Pull the Image

# Latest release
docker pull ghcr.io/batonogov/gitlab-auto-mr:latest

# Specific version
docker pull ghcr.io/batonogov/gitlab-auto-mr:vX.Y.Z

Supported Platforms

  • linux/amd64
  • linux/arm64

Quick Start

  1. Set required environment variables:
export GITLAB_PRIVATE_TOKEN="your-gitlab-token"
export GITLAB_USER_ID="your-user-id"
export CI_PROJECT_ID="12345"
export CI_PROJECT_URL="https://gitlab.com/user/project"
export CI_COMMIT_REF_NAME="feature/my-branch"
  1. Run with Docker (recommended):
# Creates new MR or informs if MR already exists
docker run --rm \
  -e GITLAB_PRIVATE_TOKEN \
  -e GITLAB_USER_ID \
  -e CI_PROJECT_ID \
  -e CI_PROJECT_URL \
  -e CI_COMMIT_REF_NAME \
  ghcr.io/batonogov/gitlab-auto-mr:latest \
  gitlab_auto_mr --target-branch main --commit-prefix "Ready"
  1. Or build and run locally:
git clone https://github.com/user/gitlab-auto-mr.git
cd gitlab-auto-mr
task build
# Creates new MR or informs if MR already exists
./gitlab_auto_mr --target-branch main --commit-prefix "Ready"

Usage

GitLab CI/CD

Create MR (Default Behavior)

create_mr:
  stage: create-mr
  image: ghcr.io/batonogov/gitlab-auto-mr:latest
  script:
    - |
      # Creates new MR or informs if MR already exists
      gitlab_auto_mr \
        --target-branch main \
        --commit-prefix "Ready" \
        --remove-branch \
        --squash-commits
  rules:
    - if: $CI_COMMIT_BRANCH != "main" && $CI_PIPELINE_SOURCE != "merge_request_event"

Every run that identifies an MR prints its browser URL, so it can be clicked straight out of the job log:

Created a new MR Draft: feature/new-thing, assigned to you.
MR URL: https://gitlab.example.com/group/project/-/merge_requests/42

Update Existing MR

update_mr:
  stage: update-mr
  image: ghcr.io/batonogov/gitlab-auto-mr:latest
  script:
    - |
      # Updates existing MR (requires --update-mr flag)
      gitlab_auto_mr \
        --update-mr \
        --target-branch main \
        --commit-prefix "Ready" \
        --description .gitlab/merge_request_template.md \
        --use-issue-name \
        --reviewer-id "${REVIEWER_IDS}"
  rules:
    - if: $CI_COMMIT_BRANCH != "main" && $CI_PIPELINE_SOURCE != "merge_request_event"

Advanced Control

# Force update only (fail if MR doesn't exist)
update_only:
  script:
    - gitlab_auto_mr --update-mr --target-branch main

# Force create only (fail if MR already exists)
create_only:
  script:
    - gitlab_auto_mr --create-only --target-branch main

Required Environment Variables

  • GITLAB_PRIVATE_TOKEN - GitLab personal access token with api scope
  • GITLAB_USER_ID - Your GitLab user ID (comma-separated for multiple assignees)
  • CI_PROJECT_ID - GitLab project ID
  • CI_PROJECT_URL - GitLab URL
  • CI_COMMIT_REF_NAME - Source branch name

Optional Environment Variables

  • GITLAB_AUTO_MR_TARGET_BRANCH - Target branch for the MR. Overridden by --target-branch/-t; when neither is set, the project's default branch is used.
  • GITLAB_AUTO_MR_LABELS - Labels for the MR (comma-separated). Overridden by --label.
  • GITLAB_AUTO_MR_MILESTONE - Milestone ID for the MR. Overridden by --milestone.
  • GITLAB_AUTO_MR_CA_CERT - Path to a PEM CA certificate to trust in addition to the system pool
  • GITLAB_AUTO_MR_TIMEOUT - Timeout for a single API request (default 30s)
  • GITLAB_AUTO_MR_RETRIES - Retries for transient failures (default 2)
  • GITLAB_AUTO_MR_RETRY_DELAY - Delay before the first retry (default 1s)

CLI Options

Option Short Description Default
--target-branch -t Target branch for MR (GITLAB_AUTO_MR_TARGET_BRANCH) Project default branch
--commit-prefix -c MR title prefix Draft
--title Custom MR title Source branch name
--description -d Path to description file -
--remove-branch -r Delete source branch after merge false
--squash-commits -s Squash commits on merge false
--label Labels for the MR, comma-separated (GITLAB_AUTO_MR_LABELS) -
--milestone Milestone ID for the MR (GITLAB_AUTO_MR_MILESTONE) -
--draft Mark the MR as a draft false
--ready Mark the MR ready by removing a draft prefix false
--use-issue-name -i Use issue data from branch name false
--allow-collaboration -a Allow commits from merge target members false
--reviewer-id Reviewer user ID(s) (comma-separated) -
--mr-exists Only check if MR exists (dry run) false
--update-mr Update existing MR (required to update, fail if none exists) false
--create-only Force create new MR (fail if already exists) false
--auto-merge Enable merge when pipeline succeeds (auto-merge) false
--trigger-pipeline Create a merge request pipeline for the created or updated MR false
--force-pipeline With --trigger-pipeline, create one even if the commit has one false
--trigger-pipeline Create a merge request pipeline after the MR is created false
--timeout Timeout for a single API request 30s
--retries Retries for transient failures (5xx, 429, network) 2
--retry-delay Delay before the first retry, doubled each time 1s
--ca-cert PEM CA certificate to trust (GITLAB_AUTO_MR_CA_CERT) -
--insecure -k Skip SSL certificate verification false

Merge Request Pipelines

GitLab does not start a merge request pipeline on its own when the MR is created through the API for a commit that already has a branch pipeline. If your CI configuration runs jobs only on merge_request_event, those jobs never run for the newly created MR — and the branch pipeline that did run may look green while having executed none of them.

--trigger-pipeline asks GitLab for that pipeline explicitly, for the MR this run created or updated — a branch that moved is exactly when the checks are worth re-running:

open_merge_request:
  image: $GITLAB_AUTO_MR_IMAGE
  script:
    - gitlab_auto_mr --target-branch main --trigger-pipeline

The flag acts at most once per commit. Before creating a pipeline the tool lists the MR's existing ones and skips when it finds one for the same head commit, reporting the skip so it is visible in the job log:

Merge request pipeline already exists for commit abc123de (ID: 7, status: running): https://gitlab.example.com/p/-/pipelines/7
Skipping pipeline creation; pass --force-pipeline to create another.

That is what keeps a retried job, a manual re-run, or a later run against the same MR from stacking up pipelines for one commit. Use --force-pipeline when re-running the checks is exactly the point — including when the existing pipeline for that commit failed, since the check looks at the commit, not at the result.

Only pipelines GitLab reports as merge request pipelines count. The branch pipeline running this job is attached to the same MR and the same commit, and treating it as a match would mean never creating the merge request pipeline at all. If the pipeline list cannot be read, the tool creates one anyway: a duplicate is a better outcome than checks that silently did not run. The same applies to projects using merged results pipelines, where the pipeline's commit is a simulated merge and never equals the branch head.

Two things to expect:

  • On the push that creates the MR you will have two pipelines for the same commit — the branch pipeline that ran this job, and the merge request pipeline created here. A workflow rule such as $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS == null keeps later pushes to the same branch down to one.
  • The token needs the api scope, and its owner needs at least the Developer role on the project. A CI_JOB_TOKEN cannot be used: the Merge Requests API is read-only for job tokens, so it can neither create the MR nor request a pipeline for it.

Operating

The tool acts on behalf of whoever owns GITLAB_PRIVATE_TOKEN. That dependency is easy to lose track of: merge requests start appearing "by themselves", and months later nobody remembers that behind them stands a live account holding an api-scoped token.

  • Use a service account, not a person. A personal token disappears when that person leaves the company or rotates their credentials, and every MR the tool opens is attributed to them.
  • Give the account Developer on the project — no more. That is enough to read the project and to create, update and merge merge requests.
  • CI_JOB_TOKEN cannot be substituted. The Merge Requests API is read-only for job tokens, so a personal or project access token with the api scope is required.
  • Plan for rotation. GitLab no longer issues non-expiring personal access tokens, so expiry is scheduled work. When the token expires, MR creation simply stops — and "no new MRs" is a symptom that is easy to miss for weeks.
  • Name the account so its purpose is obvious (for example svc-gitlab-auto-mr). Whoever finds it during a directory cleanup will decide its fate based on the name alone; an account called test1 gets deleted.

Draft and Ready

gitlab-auto-mr --draft               # open (or keep) the MR as a draft
gitlab-auto-mr --ready --update-mr   # take an existing MR out of draft

GitLab has no writable draft field: the draft flag in API responses is derived from the title, and the Merge Requests API accepts no draft parameter. Draft state is set by the title prefix — Draft:, [Draft] or (Draft) — which is what these two flags manage, so the caller does not have to know the convention.

  • --draft puts Draft: at the front of the title, without doubling one that is already there. A non-draft --commit-prefix is kept after the marker, so --draft --commit-prefix Feature gives Draft: Feature: my-branch.
  • --ready removes a leading Draft:/WIP: marker in any of GitLab's accepted spellings, and overrides the Draft default of --commit-prefix so the marker is not simply added back.
  • On an existing MR, --ready strips the marker from the MR's own title rather than rebuilding the title from the branch name — marking a draft ready should not also rename a title someone wrote by hand. Pass --title to rename deliberately.

--draft and --ready are refused together, and --draft is refused with --auto-merge for the same reason a Draft commit prefix already was: GitLab does not auto-merge drafts. --ready is what makes the default prefix compatible with --auto-merge.

--commit-prefix keeps working as before and is not deprecated; these flags are for when you want the state rather than a particular title.

Self-hosted GitLab with a private CA

If your instance presents a certificate from an internal CA, point the tool at that CA rather than turning verification off:

gitlab-auto-mr --ca-cert /etc/ssl/certs/internal-ca.pem

The certificate is added to the system trust store rather than replacing it, so other hosts keep working. --insecure/-k remains available for throwaway cases, but the two are mutually exclusive: disabling verification would make the CA pointless, and this tool carries an api-scoped token.

Retries

Self-hosted GitLab returns 502/503 during restarts and upgrades, and CI runners hit transient network errors. By default the tool retries such a failure twice, waiting 1s and then 2s, so a job does not fail for a reason that a re-run would have fixed anyway. --retries 0 turns it off.

What gets retried depends on what a repeat could do:

Request Retried on 5xx / 429 Retried on a network error
GET, PUT yes yes
POST no only if the connection was never established

POST /merge_requests is the reason for the asymmetry: retrying it after a timeout could open a second merge request. A failure to dial is safe to repeat, because the request demonstrably never reached GitLab.

A request that outran --timeout counts as a network error here, so a hung GET is retried. Ctrl-C is not: once the caller has given up, nothing is retried.

A Retry-After header — which GitLab sends when rate limiting — takes precedence over the backoff, capped at 30s. 4xx answers other than 429 are never retried: they are real answers, and repeating them only delays the error.

Building

Prerequisites

  • Go (see go.mod for minimum version)
  • Task (optional, for build automation)

Local Development

# Using Task (recommended)
task build      # Build binary
task test       # Run tests
task docker-build  # Build Docker image
task clean      # Clean artifacts
task --list     # Show all tasks

# Or using Go directly
go build -o gitlab_auto_mr .
go test ./...

Pre-commit Hooks

This project uses pre-commit hooks for code quality and consistency:

# Install pre-commit (if not already installed)
pip install pre-commit

# Install hooks
pre-commit install

# Run hooks on all files
pre-commit run --all-files

# Run hooks on specific files
pre-commit run --files main.go

Included hooks:

  • Go dependency verification
  • Go code formatting
  • Go linting
  • Go tests
  • Taskfile validation
  • Dockerfile validation
  • YAML validation
  • Markdown formatting
  • Large file detection
  • Merge conflict detection

Docker Build

task docker-build
# or
docker build -t gitlab-auto-mr .

Examples

Create New MR (Default)

# Creates new MR or informs if MR already exists
./gitlab_auto_mr \
  --target-branch main \
  --commit-prefix "Ready"

With Reviewers

./gitlab_auto_mr \
  --target-branch main \
  --reviewer-id "12345,67890"

With Labels and a Milestone

gitlab-auto-mr --label "bug,priority::high" --milestone 5

Both combine with --use-issue-name rather than replacing it: labels are the union of the two sources, and an explicit --milestone wins over the issue's.

Check if MR Exists

./gitlab_auto_mr \
  --mr-exists \
  --target-branch main

Update Existing MR

# Update existing MR (requires --update-mr flag, fail if MR doesn't exist)
./gitlab_auto_mr \
  --update-mr \
  --target-branch main \
  --title "Updated Title" \
  --description new_description.md

Force Create Only

# Only create new MR (fail if MR already exists)
./gitlab_auto_mr \
  --create-only \
  --target-branch main \
  --reviewer-id "12345,67890"

Troubleshooting

Authentication Error

Error: unable to get project 12345: unauthorized access, check your access token is valid and has the api scope
  • Check your GITLAB_PRIVATE_TOKEN is valid and has api scope
  • An expired or revoked token gives the same message — see Operating for who the token should belong to and why rotation is scheduled work

MR Already Exists

Merge request already exists: Feature XYZ (IID: 42). Use --update-mr flag to update it.
MR URL: https://gitlab.example.com/group/project/-/merge_requests/42
  • This is the default behavior when MR already exists
  • Add --update-mr flag to update the existing MR instead

Update Mode Error

Error: merge request does not exist for this branch feature/test to main, cannot update non-existent MR
  • This happens when using --update-mr flag but no MR exists
  • Remove --update-mr flag to create a new MR instead

Force Create Mode Error

Error: merge request already exists for this branch feature/test to main, cannot create new MR in create-only mode
  • This happens when using --create-only flag but MR already exists
  • Remove --create-only flag to allow the tool to inform about existing MR
  • Use --update-mr flag instead if you want to update the existing MR

Same Source/Target Branch

Error: source branch and target branches must be different
  • Ensure you're not running on the target branch

License

MIT License - see LICENSE file for details.

About

Create MR in GitLab automatically

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages