本記事は ZOZO Advent Calendar 2024 シリーズ 2 の 7 日目の記事です。
やりたいこと
以下のような状況を考えます。
- 共通ライブラリが管理されている GitHub リポジトリ A がある
- 共通ライブラリを利用する GitHub リポジトリ B, C がある
- リポジトリ B, C ではリポジトリ A のコードを
git subtree
で取り込んでいる
ここではリポジトリ B で共通ライブラリ(リポジトリ A のコード)の変更を行った際に、その変更内容をリポジトリ A に対して Pull-Request を作成する GitHub Actions を作ります。
結論
まずは完成形を示します。リポジトリ B で作成した PR がマージされたタイミングで起動する Action の想定です。
name: Create PR to other repository
on:
pull_request:
branches:
- "main"
types: [closed]
env:
# BOT として使用する GitHub Apps の App ID
APP_ID: 1234567
jobs:
# git subtree で取り込んでいる repository-a のファイル変更を検知する
get-changed-files:
if: ${{ github.event.pull_request.merged == true }}
runs-on: ubuntu-22.04
outputs:
changed_files: ${{ steps.changed-files.outputs.all_changed_files }}
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
ref: refs/heads/${{ github.event.pull_request.base.ref }}
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v45
with:
json: true
quotepath: false
files: |
repository-a/**
- name: Show changed files
run: |
echo "${{ steps.changed-files.outputs.all_changed_files }}"
# repository-a に PR を作成する
create-pr-to-repository-A:
if: ${{ !failure() && needs.get-changed-files.outputs.changed_files != '[]' && github.event.pull_request.merged == true }}
needs: get-changed-files
runs-on: ubuntu-22.04
permissions:
contents: write
id-token: write
env:
LIBRARY_UPDATE_PR_BRANCH: feature/update-library-via-ci
steps:
- name: Check out to this repo
uses: actions/checkout@v4
with:
fetch-depth: 0
path: repository-b
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ env.APP_ID }}
private-key: ${{ secrets.GITHUB_APPS_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Check out to repository-a
uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
repository: repository-a
path: repository-a
- name: Get GitHub App User ID
id: get-user-id
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
- name: Configure git environment and switch branch
working-directory: repository-a
run: |
set -eux
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>'
git fetch origin
git switch -c $LIBRARY_UPDATE_PR_BRANCH origin/$LIBRARY_UPDATE_PR_BRANCH || git switch -c $LIBRARY_UPDATE_PR_BRANCH
- name: Sync files to repository-a
run: |
rsync -r --delete repository-b/repository-a/ repository-a/ --exclude '.git' --exclude '.github/'
- name: Show git diff
working-directory: repository-a
run: |
set -eux
git status
git branch
git add .
git diff --cached --name-status
- name: Create pr to repository-a
env:
TOKEN: ${{ steps.app-token.outputs.token }}
REVIEWER: Reviewer-name-as-you-like
working-directory: repository-a
run: |
set -eux
git commit -m "Update repository-a from repository-b's PR ${{github.event.pull_request.html_url}}"
git push origin HEAD
echo ${TOKEN} > token.txt
gh auth login --with-token < token.txt
pr_list=$(gh pr list --head $LIBRARY_UPDATE_PR_BRANCH --json=number,body)
if [[ $(echo "$pr_list" | jq '.[] | length') -eq 0 ]]; then
gh pr create \
--title "Update `repository-a` via repository-b repo" \
--body "- [ ] ${{github.event.pull_request.html_url}}" \
--reviewer ${REVIEWER}
else
echo $pr_list | jq -r '.[0].body' > pr_body.txt
echo "- [ ] ${{github.event.pull_request.html_url}}" >> pr_body.txt
gh pr edit --body "$(cat pr_body.txt)"
fi
上記の Action についてポイントをいくつかピックアップして解説します。
subtree で取り込んでいるコードの変更を検知する
tj-actions/changed-files という Action を使ってファイルの変更を検出しています。
今回作成した Action では「repository-b
のリポジトリルートで repository-a
というディレクトリ名でリポジトリ A のコードを管理している」という状況を想定しているため、repository-a/**
で変更されたファイルを選択しています。
$ tree .
.
├── README-b.md
├── repository-a
│ ├── README-a.md
│ └── src
│ └── file
└── src
└── file
リポジトリ A に checkout して変更内容を sync させる
以下の手順でリポジトリ B での変更内容を反映したブランチを作成しています。
-
repository-a
というディレクトリ名でリポジトリ A に checkout - リポジトリ A で特定のリモートブランチに switch(リモートブランチが存在しなければ新規に作成)
-
rsync
コマンドでリポジトリ B での変更内容をリポジトリ A のブランチに反映
上記の流れを踏まえつつ Action を読むと理解が捗ります。
- name: Check out to repository-a
uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
repository: repository-a
path: repository-a
- name: Get GitHub App User ID
id: get-user-id
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
- name: Configure git environment and switch branch
working-directory: repository-a
run: |
set -eux
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>'
git fetch origin
git switch -c $LIBRARY_UPDATE_PR_BRANCH origin/$LIBRARY_UPDATE_PR_BRANCH || git switch -c $LIBRARY_UPDATE_PR_BRANCH
- name: Sync files to repository-a
run: |
rsync -r --delete repository-b/repository-a/ repository-a/ --exclude '.git' --exclude '.github/'
リポジトリ B での変更内容をリポジトリ A の特定の PR に積んでいく
リポジトリ B で PR をマージするたびにリポジトリ A で新しい PR が生まれて PR が乱立することを避けます。
これは「既に PR が存在していれば PR 編集・PRが存在していなければ新規作成」を実現するシェルスクリプトを書いているだけです。
PR の body にはリポジトリ B の PR へのリンクをチェックボックス形式で記載しています。
- name: Create pr to repository-a
env:
TOKEN: ${{ steps.app-token.outputs.token }}
REVIEWER: Reviewer-name-as-you-like
working-directory: repository-a
run: |
set -eux
git commit -m "Update repository-a from repository-b's PR ${{github.event.pull_request.html_url}}"
git push origin HEAD
echo ${TOKEN} > token.txt
gh auth login --with-token < token.txt
pr_list=$(gh pr list --head $LIBRARY_UPDATE_PR_BRANCH --json=number,body)
if [[ $(echo "$pr_list" | jq '.[] | length') -eq 0 ]]; then
gh pr create \
--title "Update `repository-a` via repository-b repo" \
--body "- [ ] ${{github.event.pull_request.html_url}}" \
--reviewer ${REVIEWER}
else
echo $pr_list | jq -r '.[0].body' > pr_body.txt
echo "- [ ] ${{github.event.pull_request.html_url}}" >> pr_body.txt
gh pr edit --body "$(cat pr_body.txt)"
fi
さいごに
「共通ライブラリに対して PR を投げる」という GitHub Actions でした。
別なリポジトリに変更内容を反映するようなユースケースは稀によくあるので便利な action かと思います。