🔁

GitLab CIでブランチをオートマージ(自動追従)させる

2022/10/28に公開

はじめに

こんにちは。フォルシア株式会社エンジニアの谷井です。

フォルシアでは、ソースコード管理にGitLabを使用してチーム開発を行っていますが、その中で「あるブランチに入る変更を、自動で別のブランチに取り込みたい」という状況が時折発生します。

例えば、

  • 「大型新機能開発のためのブランチを切って開発を行うが、その間も本流のmasterブランチには通常開発の変更が入る」
  • 「検証環境反映用のブランチにだけ入れておきたい機能があるが、それ以外は本番環境用のブランチと同じ状態にしておきたい」

というようなケースです。

あとでまとめて取り込もうとすると大量のコンフリクトと格闘することになるが、一方でこまめに手動で取り込むには労力がかかる...ということで、GitLab CIを使ってブランチの自動追従(オートマージ)を行うスクリプトを作成して使っているのでご紹介します。

(GitLab CI/CDについては以前フォルシアのアドベントカレンダーでも扱っているので、詳細はぜひそちらの記事をご覧ください。)

前準備

GitLab CI/CD実行時のdocker内からの接続はreadonlyになっているため、単に.gitlab-ci.yml内でgit merge, git pushと書くだけでは変更をリポジトリ側に反映できません。

今回は特定のGitLabユーザー(こうした用途に利用するためのbotアカウントを想定)に対してssh公開鍵を登録し、その鍵を使って接続します。

ssh鍵を生成

マージを行うbotユーザーに公開鍵を登録

  • botユーザーでログイン
  • 画面右上のアイコン > Preferences > SSH Keys を選択
  • Key欄に公開鍵を貼り付け

ユーザーへの公開鍵登録

プロジェクトの環境変数に秘密鍵を登録

  • 秘密鍵の内容はリポジトリ内に含めるのは望ましくないため、.gitlab-ci.yml内には定義せずプロジェクトの環境変数として登録
  • CIを設定したいプロジェクトの Settings > CI/CD > Variables > Expand
  • Add Variableを押してKeyにAUTO_MERGE_SSH_KEY, Valueに生成した秘密鍵を貼り付け

環境変数に秘密鍵を登録

これで事前準備は完了です。

.gitlab-ci.ymlの設定

リポジトリ直下に.gitlab-ci.yml, auto_merge.shを作成し、以下の通り記述します。

yml内のvariablesに定義された変数や、sh内の変数のデフォルト値については適宜実際の値に書き換えてください。

.gitlab-ci.yml
auto_merge:
  stage: auto_merge
  variables:
    # どのブランチへのpush時にマージを実行するかの設定
    SOURCE_BRANCH: develop
    # push時にどのブランチにマージをするのかの設定
    TARGET_BRANCH: develop-feature
    # submoduleの設定
    GIT_SUBMODULE_STRATEGY: recursive
    # マージを行うbotユーザーの設定
    GIT_USER: yourBotUserName
    GIT_USER_EMAIL: bot@email.com
  before_script:
    - echo "merge ${SOURCE_BRANCH} into ${TARGET_BRANCH} branch"
  script:
    - bash auto_merge.sh
  rules:
    - if: $CI_COMMIT_BRANCH == $SOURCE_BRANCH
auto_merge.sh
#!/bin/bash
# ブランチをpushした時に自動で別の特定のブランチにマージさせるCI

set -eux
ROOT_DIR=$(git rev-parse --show-toplevel)
GITLAB_DOMAIN="yourOwnDomainName"

TARGET_BRANCH=${TARGET_BRANCH:?}
SOURCE_BRANCH=${SOURCE_BRANCH:?}
GIT_USER=${GIT_USER:-"yourBotUserName"}
GIT_USER_EMAIL=${GIT_USER_EMAIL:-"bot@email.com"}

set +xu
# SOURCEとTARGETに同一ブランチが指定されていればスキップ
if [ ${SOURCE_BRANCH} = ${TARGET_BRANCH} ]; then
	echo "#### SOURCE_BRANCH(${SOURCE_BRANCH}) AND TARGET_BRANCH(${TARGET_BRANCH}) IS SAME. SKIP..."
	exit 0
fi

which ssh-agent || ( apk add --update openssh )
eval "$(ssh-agent -s)"
ssh-add <(echo "$AUTO_MERGE_SSH_KEY")

mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -H "${GITLAB_DOMAIN}" >> ~/.ssh/known_hosts

echo "Clone Repository.(git@${GITLAB_DOMAIN}:${CI_PROJECT_PATH}.git)"
git clone git@${GITLAB_DOMAIN}:${CI_PROJECT_PATH}.git
cd ${CI_PROJECT_NAME}
git config user.name ${GIT_USER}
git config user.email ${GIT_USER_EMAIL}
git checkout ${TARGET_BRANCH}
git merge --no-commit --no-ff origin/${SOURCE_BRANCH}

if [[ "$?" = "1" ]]; then
	echo "#### FAILED TO MERGE SOURCE_BRANCH(${SOURCE_BRANCH}) INTO TARGET_BRANCH(${TARGET_BRANCH})."
  exit 1
fi

git commit -m "Merge branch ${SOURCE_BRANCH} into ${TARGET_BRANCH}"
git push origin ${TARGET_BRANCH} --no-verify

exit 0

テンプレート化

さて、ここまでで必要な機能は実現されましたが、
複数のプロジェクトに対して今回のようなCIを設定したい場合、このままではプロジェクトごとに上記のスクリプトファイルをコピー&ペーストする必要があり、とても面倒ですよね。

フォルシアでは、このようなプロジェクトに依存せず共通で使えるGitLab CI/CDのテンプレートをまとめたリポジトリが作られており、CIスクリプトも手軽に使えるように工夫されています。

テンプレートリポジトリへの配置

ここでは、gitlab-ci-templateというリポジトリに先ほどのスクリプトを登録して、他のプロジェクトからも手軽に呼び出せるようにしたいと思います。

先ほどの.gitlab-ci.ymlとauto_merge.shを上記リポジトリのauto_mergeディレクトリ以下に配置します。

さらに、ymlファイルのファイル名はtemplate.ymlと変更した上で、以下のようにテンプレートリポジトリの情報を追記しておきます。

template.yml
auto_merge:
  stage: auto_merge
  variables:
    # どのブランチへのpush時にマージを実行するかの設定
    SOURCE_BRANCH: develop
    # push時にどのブランチにマージをするのかの設定
    TARGET_BRANCH: develop-feature
    # submoduleの設定
    GIT_SUBMODULE_STRATEGY: recursive
    # マージを行うbotユーザーの設定
    GIT_USER: bot
    GIT_USER_EMAIL: bot@email.com
    # 参照するスクリプトの情報
    TEMPLATE_DOMAIN: "http://yourOwnDomain"
    TEMPLATE_REPOSITORY: "gitlab-ci-template"
    TEMPLATE_REVISION: "master"
  before_script:
    - echo "merge ${SOURCE_BRANCH} into ${TARGET_BRANCH} branch"
  script:
    - wget ${TEMPLATE_DOMAIN}/${TEMPLATE_REPOSITORY}/raw/${TEMPLATE_REVISION}/auto_merge/auto_merge.sh
    - bash auto_merge.sh
  rules:
    - if: $CI_COMMIT_BRANCH == $SOURCE_BRANCH

呼び出すプロジェクト側での設定

ここまでくるとプロジェクト側での設定はシンプルで、

  • プロジェクトの環境変数AUTO_MERGE_SSH_KEYへの秘密鍵の登録
  • .gitlab-ci.ymlへの下記記述
.gitlab-ci.yml
image: alpine/git
include:
  - project: 'gitlab-ci-template'
    ref: 'master'
    file: '/auto_merge/template.yml'
stages:
  - auto_merge
auto_merge:
  variables:
    SOURCE_BRANCH: develop
    TARGET_BRANCH: develop-feature

これだけです。

GitLab CIでの設定は後勝ちになるので、includeされたtemplateの変数をプロジェクトの固有の設定で上書きすることができます。

注意点

  • オートマージによってコンフリクトが発生する場合、jobはfailedとなり、マージは実行されません。必要に応じてslack等への通知を設定して、手動でコンフリクトを解消してください。
  • 上述のrules設定では、SOURCE_BRANCHに対する全てのコミットをトリガーとしてオートマージが起動します。
    • 十分に検証された内容がコミットされるブランチを設定することを推奨します。
    • フォルシアでは、developブランチ(レビューを経たマージコミットのみpushされる開発の本流ブランチ)を設定することがほとんどです。

最後に

今回はGitLab CI/CDを使って特定のブランチをオートマージする方法をご紹介しました。

スクリプト自体は3年近く前に書いたものがベースになっていますが、思いのほか別のチームでも使われることがあったので、当時は無かったCIテンプレートリポジトリへの追加と合わせてあらためて記事にまとめてみました。

開発業務で必ず行うGitへのコミットをトリガーに様々なアクションを実行できるCI/CDツールは、やはりとても便利ですね。

FORCIA Tech Blog

Discussion