GtiHub Actionsでfork元の更新を定期的にfetchする
forkしてちょっとだけ変更を加えてそのまま放置…
その間にも、fork元はコミットが進んでいるかも。
fork元の先進的な変更点は取り入れたいけれど、自分が変更したところは上書きされたくない!
そんなときはGitHub Actionsを使ってみようという話。
この記事は、SLP KBIT AdventCalendar2022 5日目の記事です。
おおまかな手順
- clone
git remote add upstream https://github.com/upstream/repository.git
git fetch upstream main
git rebase -X ours upstream/main main
git push -f origin main
とすると、fork元の変更を取り入れた後に、自分の変更が加えられる。
これを毎日実行すると、fork元で更新があれば取り入れ、自分が変更したコミットはその上に積まれる。
注意点:
- 自分で変更を加えたファイルは、fork元で更新があってもそれを検知できない(変更差分の判定から除外するため)
- 変更したファイルが多いと不適
- actions/checkout の
with: fetch-depth: 0
を設定しておかなければrebaseに失敗する(デフォルトではコミット履歴は1つしか取得しないため) - workflowファイルが更新された場合、tokenが
secrets.GITHUB_TOKEN
だと失敗する(権限がないため)- よって、token は PAT(Personal Access Token)を使う。
作り方: https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-tokendocs.github.com
権限はrepo
とworkflow
でよい。 作ったらリポジトリのsecretに登録しておく。ここでは登録名をPAT
にした場合の例を書いている。
- よって、token は PAT(Personal Access Token)を使う。
更新の有無を確認する
自分が変更を加えたファイルを除き、変更があるか否かを確認する。
自分が変更したファイルはここにパスを追記して更新の確認から除外しておかなければならない。
git diff main upstream/main --name-only -- ':!own/modified/file'
force pushする
rebase したことにより、コミット履歴が書き換わっている。
よってpush時はforceオプションを使用する。
--force-with-lease
を使ってもいいが、fetchしているため使用するメリットはない。
更新がなかった場合のearly exit
更新がなかった場合はさっさと処理を終了したい。
しかしexit 0
としても次のstepは実行されてしまう。
かといってexit 1
などと異常終了するとactionが失敗したとみなされ、失敗メールがうっとおしい。
そのため今回は変数に更新成功か失敗かを記し、以降のstepではその変数をifで確認して実行させるように対処している。
Example
README.md
に変更を加えて、他のファイルはfork元の変更を取り入れる場合。
毎日03:12にチェックを行う。
update用のactionを .github/workflows/update.yaml
に作成:
on: schedule: - cron: '12 3 * * *' env: UPSTREAM_URL: "https://github.com/upstream/repository.git" jobs: update: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 token: ${{ secrets.PAT }} - name: update id: update run: | git config --local user.name "${GITHUB_ACTOR}" # メールアドレスをこれにするとbotとしてコミットされる git config --local user.email "github-actions[bot]@users.noreply.github.com" git remote add upstream "${{ env.UPSTREAM_URL }}" git fetch --quiet upstream main # 自分が変更したファイルは更新の確認から除外する if [ -n "$(git diff main upstream/main --name-only -- ':!README.md' ':!.github/workflows/update.yaml')" ]; then echo "Update to follow the upstream repository." git rebase -X ours upstream/main main git remote set-url origin "https://${{ secrets.PAT }}@github.com/${GITHUB_REPOSITORY}.git" git push --force-with-lease origin ${GITHUB_REF#refs/heads/} echo "::set-output name=UPDATE_STAT::ok" else echo "No update found." echo "::set-output name=UPDATE_STAT::fail" exit 0 fi - name: update success report if: ( steps.update.outputs.UPDATE_STAT == 'ok' ) run: echo successflly updated! - name: no update report if: ( steps.update.outputs.UPDATE_STAT == 'fail' ) run: echo not updated.