#!/usr/bin/env bash # create-mr.sh "" [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