git-flowで運用していると、releaseブランチとhotfixブランチなど複数のブランチからリリースが行われるケースがあります。その場合リリースされたものを次のreleaseブランチにきちんと反映しないと、ソースが巻き戻ってしまう事故に繋がります。

flow図 (あくまでイメージ図です、developブランチなどの詳細は省略しています)

私の業務ではgit-flowを少しカスタマイズした運用をしています。なのでまさに上図のようにhotfixブランチをreleaseブランチに反映し忘れる問題を起こしてしまうことがありました。これを防ぐための方法はいろいろありますが、今回は「releaseブランチとmasterブランチが枝分かれしたコミット」「最新のmasterブランチのコミット」を比較し結果を教えてくれるスクリプトを用意してみました。

以下のような使用を想定しています。

  • CIに仕込み、releaseブランチのPull Requestに結果をコメントさせる
  • slack botに仕込み、定期的に結果を通知する

実装

全体の構成

  1. masterブランチ(baseとなるブランチ)の最新コミットのcommit idを取得する
  2. ターゲットとなるブランチとmasterブランチ(baseとなるブランチ)が枝分かれしたcommit idを取得する
  3. それらを比較し、差分がないかどうかを判定する
  4. CIから実行された場合は判定結果をPull Requestにコメントする

ソース

#!/bin/bash
set -e

text_color_red="\033[37;41;1m"
text_color_green="\033[37;42;1m"
text_color_reset="\033[0m"

# 第1引数にチェック対象のbranch
target_branch=$1
# 第2引数に比較対象のbranch、指定がなければmasterとする
base_branch=${2:-master}

git fetch

# base_branchの最新のcommitを取得する
latest_commit=$(git log origin/$base_branch -n 1 | head -1 | sed -e "s/commit \(.*$\)/\1/")
# target_branchがbase_branchのどのcommitから枝分かれしたのかを調べる
base_commit=$(git show-branch --merge-base origin/$target_branch origin/$base_branch | head -1)

echo "latest commit: $(git log $latest_commit --oneline | head -1)"
echo "based  commit: $(git log $base_commit --oneline | head -1)"

# 「base_branchの最新commit」と「枝分かれしたcommit」を比較
if [ "$latest_commit" = "$base_commit" ]; then
  comment="最新の$base_branchが取り込まれています"
  icon="✅ "
  event="APPROVE"
  echo -e "${text_color_green}${comment}${text_color_reset}"
else
  comment="$base_branchが進んでいる可能性があります"
  icon="🚫 "
  event="REQUEST_CHANGES"
  echo -e "${text_color_red}${comment}${text_color_reset}"
fi

# CIによる実行でなければここで終了
if [ "$CI" == false ] || [ -z "$CI" ]; then
  exit 0
fi

# 該当のPull Requestを取得できているか確認する
if [ "$CI_PULL_REQUEST" == false ] || [ -z "$CI_PULL_REQUEST" ]; then
  echo 'Fail to find a pull request.' && exit 0
fi
pr_number=$(echo ${CI_PULL_REQUEST} | sed -e "s/^.*pull\/\(.*$\)/\1/")

# 結果をPull RequestにReviewとしてコメントする
curl -XPOST \
  -H "Authorization: token $GITHUB_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"body\": \"$icon$comment\", \"event\": \"$event\"}" \
 https://github.com/api/v3/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$pr_number/reviews

(CircleCIを使う前提で変数を定義しています)

ソースはGitHubでも公開しています。

感想

ログに色をつけたらいい感じでした

ログを読みやすくしたくて文字色や背景色を変えています。色属性を変数化したら、ログの見やすさもコードの見やすさもいい感じでした。こちらの記事の色見本帳がとても便利で感謝です。

head と tail が便利で乱用しました

head -n入力の冒頭からn行を抽出し出力するコマンドです。git loggit show-branchは複数に渡り情報が表示されるので、head/tailを使うと最新/最古の情報を抽出できて便利でした。

sedも便利で乱用しました

sed入力をコマンドに従って処理し出力するコマンドです。今回はsed -e s/置換前/置換後として文字列を置換します。置換前で()に入れた部分(グループ化した部分)を、置換後に\1(特殊変数)で抜き出しています。これが便利すぎて、入力から一部の文字列を切りとって出力する場面では乱用してしまいます。

そこそこ活躍するスクリプトになりました

Merge漏れ事故の対策は他にもいろいろ案がある中、諸々の事情により一旦の場しのぎとして作った程度でしたが、そこそこ活躍しているようで作った甲斐がありました。shellを書く機会があまりないので、作ること自体も楽しめました。

現場からの報告は以上です。引き続きよろしくお願いします。