🐾

PR の差分を取得する [GitHub Actions]

2022/11/23に公開

概要

PR の差分を取得する方法あれこれ。
GitHub CLI がおすすめです。

各ワークフローは on.pull_request, on.pull_request_target を前提にしています。

git diff

https://git-scm.com/docs/git-diff

.github/workflows/diff.yml
steps:
  - uses: actions/checokut@v3
    with:
      fetch-depth: 0
  - run: git diff origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF}
  - run: git diff --name-only origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF}

分岐を特定するために fetch-depth: 0 で履歴をすべて取得する必要があります。

.. ではなく ... なのがミソです。
..: コミット同士の差分(ベースブランチの変更が含まれる)
...: 分岐からの差分(ベースブランチの変更が含まれない = PR の Files changed)

以下の 2 つは同じです。

git diff A...B
git diff $(git merge-base A B) B

以下の 2 つは同じです。

git diff A..B
git diff A B

こちらも参考に。

https://docs.github.com/ja/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-comparing-branches-in-pull-requests#three-dot-and-two-dot-git-diff-comparisons

ファイル名だけでいい場合は --name-only オプションを付けます。
追加/変更/削除は区別されないので注意してください。

他にも様々なオプションがあるので応用が効きます。

ファイル数の上限はなさそう。(5,555 ファイルの差分が取得できることは確認)[1]

GitHub CLI

https://cli.github.com/manual/gh_pr_diff

.github/workflows/diff.yml
env:
  GH_TOKEN: ${{ github.token }}
  GH_REPO: ${{ github.repository }}
  number: ${{ github.event.number }}
steps:
  - run: gh pr diff ${number}
  - run: gh pr diff ${number} --name-only

API から取得するのでチェックアウトは不要ですが、チェックアウトしている場合は GH_REPO を省略できます。

ファイル名だけでいい場合は --name-only オプションを付けます。
追加/変更/削除は区別されないので注意してください。

ファイル数の上限はなさそう。(5,555 ファイルの差分が取得できることは確認、差分が多いと API サーバーが処理しきれない可能性はあり)[1:1]

GitHub API

GraphQL API での取得方法は分からなかったので REST API のみ。

差分

https://docs.github.com/en/rest/pulls/pulls#get-a-pull-request

.github/workflows/diff.yml (cURL)
steps:
  - run: |
      curl -sS \
        -H "Accept: application/vnd.github.v3.diff" \
        -H "Authorization: Bearer ${token}" \
        ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${number}
    env:
      token: ${{ github.token }}
      number: ${{ github.event.number }}
.github/workflows/diff.yml (GitHub CLI)
steps:
  - run: |
      gh api \
        -H "Accept: application/vnd.github.v3.diff" \
        /repos/${GITHUB_REPOSITORY}/pulls/${number}
    env:
      GH_TOKEN: ${{ github.token }}
      GH_REPO: ${{ github.repository }}
      number: ${{ github.event.number }}

Custom media types という特殊なヘッダーがあります。
PR の情報を取得する API で Accept: application/vnd.github.v3.diff ヘッダーを指定すると差分が取得できるようになります。

パブリックリポジトリでは Authorization ヘッダーを省略できます。

ファイル数の上限はなさそう。(5,555 ファイルの差分が取得できることは確認、差分が多いと API サーバーが処理しきれない可能性はあり)[1:2]

ファイル名

https://docs.github.com/en/rest/pulls/pulls#list-pull-requests-files

.github/workflows/diff.yml (cURL)
steps:
  - run: |
      curl -sS \
        -H "Accept: application/vnd.github+json" \
        -H "Authorization: Bearer ${token}" \
        ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${number}/files \
        | jq -r '.[].filename'
    env:
      token: ${{ github.token }}
      number: ${{ github.event.number }}
.github/workflows/diff.yml (GitHub CLI)
steps:
  - run: gh api /repos/${GITHUB_REPOSITORY}/pulls/${number}/files --jq '.[].filename'
    env:
      GH_TOKEN: ${{ github.token }}
      GH_REPO: ${{ github.repository }}
      number: ${{ github.event.number }}

filename 以外にも patch (ファイル毎の差分)など様々な情報が含まれるので応用が効きます。
ただし 3,000 ファイルが上限であることに注意してください。

パブリックリポジトリでは Authorization ヘッダーを省略できます。
Accept: application/vnd.github+json ヘッダーは付けておくのが推奨ですが省略可能です。

GitHub API (パブリックリポジトリ)

https://docs.github.com/en/rest/pulls/pulls#get-a-pull-request

.github/workflows/diff.yml (cURL)
steps:
  - run: |
      json=$(curl -sS \
        -H "Accept: application/vnd.github+json" \
        ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${number})
      curl -sS -L $(echo "${json}" | jq -r '.diff_url')
    env:
      number: ${{ github.event.number }}
.github/workflows/diff.yml (GitHub CLI)
steps:
  - run: curl -sS -L $(gh api /repos/${GITHUB_REPOSITORY}/pulls/${number} --jq '.diff_url')
    env:
      GH_TOKEN: ${{ github.token }}
      GH_REPO: ${{ github.repository }}
      number: ${{ github.event.number }}

アンドキュメントですが PR の情報を取得する API に diff_url というプロパティがあってそこへアクセスすると差分を取得できます。
パブリックリポジトリ限定のようです。

Accept: application/vnd.github+json ヘッダーは付けておくのが推奨ですが省略可能です。

JSON で返ってくる URL はこちらなのですがリダイレクトされるので -L オプションを付けています。
最初からこの URL に直接アクセスすることもできそうですがアンドキュメントなものなので予告なく変更される可能性がありそうです。

JSON の diff_url
https://github.com/${GITHUB_REPOSITORY}/pull/${number}.diff
リダイレクト後
https://patch-diff.githubusercontent.com/raw/${GITHUB_REPOSITORY}/pull/${number}.diff

ファイル数の上限はなさそう。(5,555 ファイルの差分が取得できることは確認、差分が多いと API サーバーが処理しきれない可能性はあり)[1:3]

サードパーティアクション

たくさんあってキリがないのでここでは個別の紹介はしません。
依存と学習コストは少ない方がいいので基本的には上記で紹介した方法を推奨しますが物足りないときは探してみるといいかもしれません。
追加/変更/削除を区別したいケースなどでは便利そうです。(自分でコードを書けば git diffGitHub API でも実装できるとは思いますが)

https://github.com/marketplace?type=actions&query=files+changed

脚注
  1. cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 5555 | sort | uniq | xargs touch で生成したファイルをコミットして確認 ↩︎ ↩︎ ↩︎ ↩︎

Discussion