CircleCI で master ブランチとマージされたブランチでテストする


やりたいこと

CircleCI で普通に checkout を用いてテストを流して通っても、master にマージ後にエラーになってしまうことがある。それを防ぐためにブランチを master にマージした状態でテストを流したい。

JenkinsやTravisだと選択したらできる程度の機能だが、現状のCircleCIだと頑張らないとできない(という認識)

事前知識

GitHub に pull request (PR) すると github 上では pull/xxx/headpull/xxx/merge というブランチが作られる。

pull/xxx/head が PR されたブランチの head と同じ状態のもので pull/xxx/merge がマージ対象のブランチとマージした状態のブランチになる。

なので、基本的には pull/xxx/merge のほうを CircleCI で checkout してテストを流せば良い。
しかし、CircleCI の checkout では pull/xxx/merge のほうを選ぶことができない。

また、pull/xxx/mergecheckout だと Conflict したかどうかの判断がしづらい。Pull Request を投げた瞬間は

  • Conflict しなかった場合: pull/xxx/merge が作られる
  • Conflict した場合: pull/xxx/merge が作られない

一度 pull/xxx/merge が作られた後に、git commit && push を積むと

  • Conflict しなかった場合: pull/xxx/merge が更新される
  • Conflict した場合: pull/xxx/merge が更新されない

という挙動をする。pull/xx/merge の存在確認だけでは Conflict 判定ができない。

やり方

結局こうなった。つまるところ git checkout master して git merge --no-commit pull/xxx/head で master に merge してあげている。

制約条件としては Pull Request にしていること。
その制約の代わりに fork されたリポジトリから Pull Request を送った場合でも動くようになっている。

if [[ -n "${CIRCLE_PULL_REQUEST}" ]]; then
    # TODO: Any ways to get PR destination branch dynamically?
    PR_DEST_BRANCH=master

    # CIRCLE_PR_NUMBER is available only if PR is created from a fork (unavailable if created from a branch).
    # So, manually construct it from CIRCLE_PULL_REQUEST environment variable.
    CIRCLE_PR_NUMBER=$(basename "${CIRCLE_PULL_REQUEST}")

    # Update PR refs for testing.
    FETCH_REFS="+${PR_DEST_BRANCH}:${PR_DEST_BRANCH}"
    FETCH_REFS="${FETCH_REFS} +refs/pull/${CIRCLE_PR_NUMBER}/head:pull/${CIRCLE_PR_NUMBER}/head"
    FETCH_REFS="${FETCH_REFS} +refs/pull/${CIRCLE_PR_NUMBER}/merge:pull/${CIRCLE_PR_NUMBER}/merge"

    # Retrieve the refs
    echo "git fetch -u origin ${FETCH_REFS}"
    git fetch -u origin ${FETCH_REFS}

    # Checkout PR destination branch and merge PR head ref.
    # If conflicts occur, exit with non-zero.
    echo "git checkout ${PR_DEST_BRANCH}"
    git checkout "${PR_DEST_BRANCH}"
    git config user.name 'foo' # Need to configure something to git merge
    git config user.email '[email protected]'
    echo "git merge --no-commit \"pull/${CIRCLE_PR_NUMBER}/head\""
    git merge --no-commit "pull/${CIRCLE_PR_NUMBER}/head"
fi

これを checkout のあとに書く

jobs:
  foobar:
    steps:
      - checkout
      - run:
          name: 'Checkout merged branch'
          command: |
            ここにインデントして書く

CircleCI 2.1 以上なら commands にしておくとよいかもしれない

version: 2.1

commands:
  checkout_merge:
    steps:
      - run:
          name: 'Checkout merged branch'
          command: |
            ここにインデントして書く

jobs:
  foobar:
    steps:
      - checkout
      - checkout_merge

参考文献