Merge request в GitLab — это не просто способ слить код в основную ветку. Это целая система для контроля качества, код-ревью и автоматизации. А если ты работаешь с API, то вообще открываются безграничные возможности для автоматизации процессов разработки.
Я много раз видел, как разработчики используют UI GitLab, кликают мышкой, создают merge requests вручную. Нормально, конечно, но когда надо автоматизировать процесс или интегрировать с внешними инструментами — начинают искать документацию и теряются. Давай разберёмся, что к чему.
Что такое merge request в GitLab и почему это важно
Merge request (MR) — это запрос на слияние одной ветки с другой. По сути, это то же самое, что pull request в GitHub, но GitLab свой термин использует. Если ты работал с GitHub — забудь привычное PR, здесь MR.
Merge request нужен для нескольких вещей:
Во-первых, это контроль качества. Перед тем как код попадёт в main или develop, его смотрит другой разработчик, оставляет замечания, просит изменить. Во-вторых, это триггер для CI/CD конвейера. Когда ты создаёшь MR, автоматически запускаются тесты, линтеры, статический анализ — все проверки, которые ты настроил. В-третьих, это документация. В MR ты оставляешь описание, почему ты делал эти изменения, какой проблеме это решает.
А если ты работаешь с API, то можешь автоматизировать весь этот процесс. Создавать MR программно, назначать ревьюеров, добавлять метки, мёржить автоматически когда все проверки пройдены — всё это из кода.
Установка и подготовка: python-gitlab и токен доступа
Чтобы работать с GitLab API из Python, используй библиотеку python-gitlab. Это официальная библиотека от GitLab, поддерживается хорошо.
pip install python-gitlab
Дальше тебе нужен токен доступа. Это как пароль, но для API. Заходишь в GitLab, в профиль, ищешь "Access Tokens" или "Personal Access Tokens", создаёшь новый. Нужны права:
api— для полного доступа к APIread_repository— чтобы читать репозиторийwrite_repository— чтобы писать в репозиторий (создавать ветки и т.д.)
Вот как это выглядит в коде:
import gitlab
# Подключаемся к GitLab
gl = gitlab.Gitlab('https://gitlab.com', private_token='your-token-here')
# Или если у тебя свой GitLab на сервере
gl = gitlab.Gitlab('https://gitlab.company.com', private_token='your-token-here')
# Проверяем, что всё работает
gl.auth()
Если ты работаешь на Linux или Mac, токен лучше хранить в переменной окружения, а не в коде:
import os
import gitlab
token = os.getenv('GITLAB_TOKEN')
gl = gitlab.Gitlab('https://gitlab.com', private_token=token)
А в .env файл пишешь:
GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxx
И не забудь добавить .env в .gitignore, чтобы случайно не залить токен в репозиторий.
Создание merge request через API
Теперь к делу. Как создать merge request из кода?
import gitlab
gl = gitlab.Gitlab('https://gitlab.com', private_token='your-token')
project = gl.projects.get('project-id-or-path')
# Создаём merge request
mr = project.mergerequests.create({
'source_branch': 'feature/my-new-feature',
'target_branch': 'main',
'title': 'Add new user authentication',
'description': 'Implements JWT-based authentication for the API'
})
print(f"Merge request created: {mr.web_url}")
Если ты не знаешь ID проекта, можешь передать path — он тоже работает. Например, 'my-group/my-project'.
А вот более реальный пример. На одном проекте мы автоматизировали создание MR для обновления зависимостей:
def create_dependency_update_mr(project, old_version, new_version, package_name):
"""Создаёт MR для обновления зависимости"""
branch_name = f"chore/update-{package_name}-{new_version}"
# Сначала проверяем, не существует ли уже такая ветка
try:
project.branches.get(branch_name)
print(f"Branch {branch_name} already exists")
return None
except gitlab.exceptions.GitlabGetError:
pass # Ветка не существует, продолжаем
# Создаём новую ветку от main
branch = project.branches.create({
'branch': branch_name,
'ref': 'main'
})
# Создаём merge request
mr = project.mergerequests.create({
'source_branch': branch_name,
'target_branch': 'main',
'title': f'Bump {package_name} from {old_version} to {new_version}',
'description': f'''
Автоматическое обновление зависимости.
- **Package**: {package_name}
- **From**: {old_version}
- **To**: {new_version}
/label dependencies
/assign @devops-team
'''
})
return mr
Видишь, я добавил слеш-команды в описание? /label dependencies — это добавит метку, /assign @devops-team — назначит ревьюеров. Очень удобно.
Работа с существующими merge requests
Иногда нужно не создать MR, а работать с уже существующим. Например, добавить комментарий, изменить статус, добавить метку.
# Получаем конкретный MR
mr = project.mergerequests.get(1) # 1 — это номер MR
# Смотрим базовую информацию
print(f"Title: {mr.title}")
print(f"Status: {mr.state}")
print(f"Author: {mr.author['name']}")
print(f"Approvals: {mr.approvals_before_merge}")
# Добавляем комментарий
mr.notes.create({
'body': 'Looks good! Just need to add tests for the new function.'
})
# Одобряем MR (если у тебя есть права)
mr.approve()
# Добавляем метку
mr.labels.append('reviewed')
mr.save()
# Назначаем ревьюера
mr.assignees = [{'id': 123}] # ID ревьюера
mr.save()
Если честно, самая полезная штука — это добавление комментариев. Можешь настроить автоматический код-ревью, которое будет оставлять замечания прямо в MR. На этом, кстати, построен Distiq — AI-бот, который анализирует каждый MR и оставляет инлайн-комментарии с замечаниями о стиле, безопасности и производительности.
Интеграция с CI/CD и автоматическое мёржение
Самая мощная штука — это интеграция MR с CI/CD конвейером. В .gitlab-ci.yml можешь настроить правила, когда MR мёржится автоматически.
Вот конфиг для проекта, где мы мёржим MR автоматически, если все тесты и проверки пройдены:
stages:
- test
- review
- merge
test:
stage: test
image: python:3.11
script:
- pip install -r requirements.txt
- pytest --cov=src --cov-report=term
coverage: '/TOTAL.*\s+(\d+%)$/'
lint:
stage: test
image: python:3.11
script:
- pip install flake8 black isort
- black --check src/
- isort --check-only src/
- flake8 src/
security:
stage: review
image: python:3.11
script:
- pip install bandit
- bandit -r src/ -f json -o bandit-report.json
artifacts:
reports:
sast: bandit-report.json
allow_failure: true
auto_merge:
stage: merge
image: python:3.11
script:
- pip install python-gitlab
- python scripts/auto_merge.py
only:
- merge_requests
when: on_success
А вот скрипт, который мёржит MR если всё хорошо:
# scripts/auto_merge.py
import gitlab
import os
gl = gitlab.Gitlab(os.getenv('CI_SERVER_URL'),
private_token=os.getenv('CI_JOB_TOKEN'))
project = gl.projects.get(os.getenv('CI_PROJECT_ID'))
mr = project.mergerequests.get(os.getenv('CI_MERGE_REQUEST_IID'))
# Проверяем, что все проверки прошли
if mr.merge_status == 'can_be_merged':
# Мёржим с удалением исходной ветки
mr.merge(
should_remove_source_branch=True,
squash=False
)
print(f"Merged MR !{mr.iid}")
else:
print(f"Cannot merge: {mr.merge_status}")
Это очень полезно для автоматизации. Например, для обновления зависимостей, генерации документации или автоматического мёржа hotfix-ветвей в production.
Удаление merge request и управление ветками
Иногда нужно закрыть или удалить MR. Удалить нельзя полностью (GitLab так не позволяет), но можешь закрыть:
# Закрываем MR
mr.state_event = 'close'
mr.save()
# Или через метод
mr.close()
# Переоткрываем MR если нужно
mr.state_event = 'reopen'
mr.save()
Если нужно удалить исходную ветку, которую создал для MR:
# Удаляем ветку
branch = project.branches.get('feature/my-feature')
branch.delete()
# Или сразу при мёрже
mr.merge(should_remove_source_branch=True)
На практике мы часто используем такой подход: создаём MR, если он не был одобрен за 3 дня, закрываем его автоматически и удаляем ветку. Экономит место в репозитории.
from datetime import datetime, timedelta
# Ищем старые MR
open_mrs = project.mergerequests.list(state='opened')
for mr in open_mrs:
created_at = datetime.fromisoformat(mr.created_at.replace('Z', '+00:00'))
age = datetime.now(created_at.tzinfo) - created_at
if age > timedelta(days=3):
print(f"Closing old MR !{mr.iid}: {mr.title}")
mr.close()
# Удаляем ветку
try:
branch = project.branches.get(mr.source_branch)
branch.delete()
except:
pass
Практические советы и типичные ошибки
Вот что я узнал на своём опыте:
Используй iid вместо id. В GitLab есть два разных идентификатора для MR: id (глобальный в системе) и iid (номер MR в проекте, тот, который ты видишь в UI). В API лучше использовать iid.
# Правильно
mr = project.mergerequests.get(1, lazy=True)
# Неправильно (работает, но медленнее)
mr = project.mergerequests.list(all=True)
mr = [m for m in mr if m.id == 123][0]
Кэшируй информацию о проекте. Если ты работаешь с одним проектом, получи его один раз и переиспользуй:
# Плохо: каждый раз запрашиваем проект
for i in range(10):
mr = gitlab.Gitlab(...).projects.get(id).mergerequests.get(i)
# Хорошо: один раз получили, дальше используем
gl = gitlab.Gitlab(...)
project = gl.projects.get(id)
for i in range(10):
mr = project.mergerequests.get(i)
Обрабатывай исключения. API может быть недоступен, MR может быть удалён, может быть проблема с правами доступа:
try:
mr = project.mergerequests.get(1)
mr.approve()
except gitlab.exceptions.GitlabGetError:
print("MR not found")
except gitlab.exceptions.GitlabUpdateError:
print("Cannot update MR (probably no permissions)")
except gitlab.exceptions.GitlabAuthError:
print("Authentication failed")
Не создавай MR без проверки. Перед созданием MR проверь, что ветка существует и что в ней есть изменения:
def safe_create_mr(project, source, target, title):
# Проверяем, что ветки существуют
try:
project.branches.get(source)
project.branches.get(target)
except gitlab.exceptions.GitlabGetError as e:
print(f"Branch not found: {e}")
return None
# Проверяем, что нет уже открытого MR между этими ветками
existing = project.mergerequests.list(
source_branch=source,
target_branch=target,
state='opened'
)
if existing:
print(f"MR already exists: {existing[0].web_url}")
return existing[0]
# Создаём новый MR
return project.mergerequests.create({
'source_branch': source,
'target_branch': target,
