feat(jira-workflow): add GitLab create-mr.sh helper

- creates a merge request via GitLab API (curl + GITLAB_TOKEN, PRIVATE-TOKEN header)
- derives host + project path from git remote origin (or GITLAB_HOST); no hardcoded URLs
- refuses to open an MR from a protected source branch; MR_PUSH/MR_DRY_RUN toggles
- optionally attaches the MR link to a Jira ticket via link-mr.sh
- verified parsing for ssh (scp-form) and https remotes incl. nested namespaces
This commit is contained in:
2026-07-16 11:34:55 +03:00
parent bc4ceb100d
commit 7fc16777a6
2 changed files with 125 additions and 0 deletions

View File

@@ -17,6 +17,9 @@ description: Используй, когда нужно работать с ко
- **Токен доступа** — из переменной окружения `JIRA_TOKEN` (personal access token).
- Токен в команды и логи не подставляй в открытом виде — скрипты читают его из окружения сами.
Для создания merge request в GitLab дополнительно нужен `GITLAB_TOKEN` (токен с scope `api`);
хост и путь проекта скрипт определяет из `git remote origin` (или `GITLAB_HOST`) — адреса не хардкодятся.
Быстрая проверка окружения:
```bash
@@ -32,6 +35,9 @@ description: Используй, когда нужно работать с ко
- `./scripts/add-comment.sh <KEY> "<текст>"` — добавить комментарий.
- `./scripts/link-mr.sh <KEY> "<url-MR>" ["<заголовок>"]` — приложить ссылку на MR
(добавляется комментарием со ссылкой; при доступном remote link API — см. TODO в скрипте).
- `./scripts/create-mr.sh "<title>" [target] [source] [jira-key]` — создать merge request в
GitLab (по текущей ветке), вывести его URL и, если передан `jira-key` и заданы
`TRACKER_URL`/`JIRA_TOKEN`, автоматически приложить ссылку к тикету.
Пример:
@@ -39,8 +45,15 @@ description: Используй, когда нужно работать с ко
./scripts/get-issue.sh PROJ-123
./scripts/add-comment.sh PROJ-123 "Начал работу, воспроизвёл баг"
./scripts/link-mr.sh PROJ-123 "https://<git-host>/ai/foo/-/merge_requests/42" "Fix PROJ-123"
# создать MR из текущей ветки в main и сразу привязать к тикету:
GITLAB_TOKEN=... ./scripts/create-mr.sh "Fix PROJ-123: NPE" main "" PROJ-123
```
Флаги окружения `create-mr.sh`: `GITLAB_TOKEN` (обязателен), `GITLAB_HOST` (override хоста),
`MR_PUSH=0` (не пушить ветку), `MR_DRY_RUN=1` (показать запрос без вызова API).
MR из защищённой ветки (`main`/`master`/`release/*`) скрипт создавать откажется.
## Соглашения
<!-- TODO: заполнить реальными значениями компании -->

View File

@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# create-mr.sh "<title>" [target-branch] [source-branch] [jira-key]
#
# Creates a GitLab merge request for the current repository via the GitLab API.
# Host and project path are derived from `git remote origin` (or GITLAB_HOST),
# so no URLs are hardcoded. If a Jira key is given and TRACKER_URL/JIRA_TOKEN are
# set, the MR link is also attached to the ticket via link-mr.sh.
#
# Env:
# GITLAB_TOKEN (required) GitLab personal/project access token with api scope
# GITLAB_HOST (optional) override host, e.g. git.codelab.vc
# MR_PUSH (optional) 0 to skip pushing the source branch (default: push)
# MR_DRY_RUN (optional) 1 to print the request and exit without calling the API
#
# Examples:
# ./create-mr.sh "Fix PROJ-123: null pointer" main
# ./create-mr.sh "Add feature X" main feature/PROJ-42-x PROJ-42
set -euo pipefail
TITLE="${1:?usage: create-mr.sh \"<title>\" [target-branch] [source-branch] [jira-key]}"
TARGET="${2:-main}"
SOURCE="${3:-$(git rev-parse --abbrev-ref HEAD)}"
JIRA_KEY="${4:-}"
: "${GITLAB_TOKEN:?set GITLAB_TOKEN (GitLab access token with api scope)}"
# --- Refuse to open an MR *from* a protected branch (safety). ----------------
case "$SOURCE" in
main | master | release/*)
echo "Refusing: source branch '$SOURCE' looks protected. Create a feature branch first." >&2
exit 1
;;
esac
# --- Derive host and project path from the origin remote. -------------------
URL="$(git config --get remote.origin.url)"
URL="${URL%.git}"
case "$URL" in
*://*) # scheme://[user@]host[:port]/path
REST="${URL#*://}"
REST="${REST#*@}"
HOST_PORT="${REST%%/*}"
PATH_NS="${REST#*/}"
HOST="${HOST_PORT%%:*}"
;;
*@*:*) # scp-like: user@host:path
REST="${URL#*@}"
HOST="${REST%%:*}"
PATH_NS="${REST#*:}"
;;
*)
echo "Cannot parse origin remote URL: $URL" >&2
exit 1
;;
esac
HOST="${GITLAB_HOST:-$HOST}"
if [ -z "$HOST" ] || [ -z "$PATH_NS" ]; then
echo "Could not determine GitLab host/project from origin ($URL). Set GITLAB_HOST." >&2
exit 1
fi
# URL-encode the project path (only '/' needs encoding for a namespaced path).
PROJECT_ENC="${PATH_NS//\//%2F}"
API="https://${HOST}/api/v4/projects/${PROJECT_ENC}/merge_requests"
# --- Build request body (jq escapes; fall back to naive JSON). --------------
if command -v jq >/dev/null 2>&1; then
BODY="$(jq -n \
--arg s "$SOURCE" --arg t "$TARGET" --arg title "$TITLE" \
'{source_branch:$s, target_branch:$t, title:$title, remove_source_branch:true, squash:true}')"
else
BODY="{\"source_branch\":\"${SOURCE}\",\"target_branch\":\"${TARGET}\",\"title\":\"${TITLE//\"/\\\"}\",\"remove_source_branch\":true,\"squash\":true}"
fi
if [ "${MR_DRY_RUN:-0}" = "1" ]; then
echo "DRY RUN"
echo "POST ${API}"
echo "body: ${BODY}"
echo "would push: $([ "${MR_PUSH:-1}" = "1" ] && echo "git push -u origin ${SOURCE}" || echo "(skipped)")"
exit 0
fi
# --- Ensure the source branch exists on the remote. -------------------------
if [ "${MR_PUSH:-1}" = "1" ]; then
git push -u origin "$SOURCE"
fi
# --- Create the MR. ---------------------------------------------------------
RESPONSE="$(curl -fsS -X POST \
-H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
-H "Content-Type: application/json" \
-d "$BODY" "$API")"
if command -v jq >/dev/null 2>&1; then
WEB_URL="$(printf '%s' "$RESPONSE" | jq -r '.web_url // empty')"
else
WEB_URL="$(printf '%s' "$RESPONSE" | sed -n 's/.*"web_url":"\([^"]*\)".*/\1/p' | head -1)"
fi
if [ -z "$WEB_URL" ]; then
echo "MR request sent, but could not parse web_url from response:" >&2
echo "$RESPONSE" >&2
exit 1
fi
echo "MR created: $WEB_URL"
# --- Optionally attach the MR link to the Jira ticket. ----------------------
if [ -n "$JIRA_KEY" ] && [ -n "${TRACKER_URL:-}" ] && [ -n "${JIRA_TOKEN:-}" ]; then
"$(dirname "$0")/link-mr.sh" "$JIRA_KEY" "$WEB_URL" "$TITLE"
fi