くりにっき

フルスタックキュアエンジニアです

GitHub Actionsのpushイベントとpull_requestイベントではGITHUB_SHAが異なる

tl;dr;

タイトルが全て

検証内容

サンプルコード

GitHub Actionsで使える(事前定義済みの)環境変数 *1を列挙するだけのシンプルなワークフローです

on:
  - push
  - pull_request

jobs:
  show_env:
    runs-on: ubuntu-latest

    steps:
      - run: env | grep GITHUB_ | sort

https://github.com/sue445/github_actions_sandbox_20210106/blob/master/.github/workflows/sandbox.yml

masterブランチに普通にpushした時

コミットグラフはこんな感じ

f:id:sue445:20210106234932p:plain

https://github.com/sue445/github_actions_sandbox_20210106/runs/1656991711 には

GITHUB_SHA=f308d17b80228b432d841e96624b583f632d2411

と出ているので、 master ブランチ *2 のHEADと一致していることが分かると思います。(分かる)

PullRequestに対してpushした場合

検証用のPR https://github.com/sue445/github_actions_sandbox_20210106/pull/1

onpushpull_request を指定してるのでそれぞれイベントが発火します。

f:id:sue445:20210106235514p:plain

この時点でコミットグラフはこんな感じ

f:id:sue445:20210106235440p:plain

pushイベントの結果

https://github.com/sue445/github_actions_sandbox_20210106/pull/1/checks?check_run_id=1657017337 には

GITHUB_SHA=8d1396316903bd747589ff2a81930e659cca1a4d

と出ているので、 tmp/pr_test ブランチのHEADと一致しています。(分かる)

pull_requestイベントの結果

https://github.com/sue445/github_actions_sandbox_20210106/pull/1/checks?check_run_id=1657017535

GITHUB_SHA=9b0ee21ee4668fab1d29961f60108b9ef3c1946b

え!!!???

解説

https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#push によるとpushイベントでの GITHUB_SHA

Commit pushed, unless deleting a branch (when it's the default branch)

とのこと。

https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#pull_request によるとpull_requestイベントでの GITHUB_SHA

Last merge commit on the GITHUB_REF branch

とのこと。

これしか書いていなくていまいち意味が分からなかったのですが id:r7kamura さんが RuboCop Problem Matchers

pull_request イベントを起点に動かす場合、actions/checkout はそのPull Requestが生成しようとしているmerge commitをチェックアウトする。

と書いてるように、この GITHUB_SHA はそのPull Requestで生成しようとしてるmerge commitのSHAだと思われます。

ちなみにこの挙動はTravis CIと同じような仕様です。

2021/01/08 追記

  • Travis CI以外にもCircleCIやJenkinsのGithub Pull Request Builderが同じ挙動
  • BitriseはGitHubのPullRequestのマージコミットではなく、内部でマージした時のマージコミットをcheckoutしてる
  • WerckerはブランチのHEADを使ってる

トノコト

GITHUB_SHAが異なることで何が困るか

ほとんどのケースではそれほど困ることはないと思います。*3

しかしジョブの中で GITHUB_SHA を使おうとした場合に注意が必要です。

具体的には tfnotify でTerraformの実行結果をPull Requestのコメントに出そうとした時に、pull_requestイベントを使うとPullRequestと直接紐付かない前述のマージコミットに対してコメントがつくため、初見だと謎のコミットに対してコメントが付く状態になります。(PullRequest上に登場するコミットに対してコメントがつけばPullRequest上で表示されます)

f:id:sue445:20210107001641p:plain

f:id:sue445:20210107001655p:plain

余談:tfnotifyでpull_requestイベントの時にもPullRequestにコメントをつけたい

半年くらいずっと例の謎コミットにコメントがついて困ってたのですがつい先日下記で解決しました。

- name: terraform plan
  run: |
    # NOTE: tfnotify uses GITHUB_SHA, but GITHUB_SHA can't be override in env
    if [ -n "$PR_HEAD_SHA" ]; then
      export GITHUB_SHA=$PR_HEAD_SHA
    fi
    terraform plan -input=false | tfnotify plan
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}

実際にPull Requestに対してtfnotifyのコメントがついた図

f:id:sue445:20210107004749p:plain

ちなみにコード中のコメントでも書いていますが

env:
  GITHUB_SHA: ${{ github.event.pull_request.head.sha }}

のように書いても GITHUB_SHA を上書きできないので、別名として取得してジョブの中で代入して上書きするしかないです。

FAQ

Q. だったらpull_requestは不要では?

業務リポジトリのように開発者全員が同一リポジトリ内でPullRequestをやり取りする場合にはpushイベントだけで問題ないです。

しかしpushイベントだとforkされたリポジトリからPullRequestがきた時にイベントが発火しなくてCIのジョブが実行されないのでOSSリポジトリで困ります。(経験済)

かといって今回のサンプルみたいに

on:
  - push
  - pull_request

で書くとPullRequestがきた時に2つジョブが実行されて無駄です。

色々検証した結果個人リポジトリだと下記に落ち着きました。

on:
  push:
    branches:
      - master
  pull_request:
    types:
      - opened
      - synchronize
      - reopened

40個以上のリポジトリで上記設定を半年以上使ってますが今の所不満はないです。

今の心境

*1:公式の説明は https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables

*2:最近GitHubではmainブランチがデフォルトになってますが毎回両方併記のは大変なので本エントリでは masterブランチで統一します

*3:トピックブランチを作った時点でのmasterブランチとPullRequestを出した時のmasterブランチが大きく剥離してるとローカルではテストが通るのにCIだとテストがコケるという事象は発生するかもしれません