GitHub Actions で Fork 元に自動追従する（Cron で自動的に Upstream を Rebase & Merge するスケジュールを組む）

<h1>GitHub Actions の schedule + cron で本家に追従したい</h1>

> GitHub で [fork](https://docs.github.com/ja/github/getting-started-with-github/fork-a-repo) したリポジトリで、フォーク元に変更があった場合にフォーク先も自動で追随させたい。
>
> しかも **GitHub だけで完結させたい**。

つまり、ローカルでの `cron` 実行や、他の外部サーバーやサービスに依存させたくないのです。

具体的な追随方法はシンプルです。現在の `master` に、フォーク元である `upstream/master` の変更を `rebase` で `merge` して、リモート先の `origin/master` に反映（`push`）することでフォーク元に追随させたいのです。

この時、外部の [CI](https://ja.wikipedia.org/wiki/%E7%B6%99%E7%B6%9A%E7%9A%84%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3) サービスなどを使わずに **[GitHub Actions](https://github.co.jp/features/actions) の `schedule` イベントで `cron` を使って変更をマージさせたい**のです。

## TL; DR <sub><sup>（action、追随の同期息切れに cron）</sup></sub>

```yaml:./.github/workflows/scheduled_sync.yml
# Follow Changes of Forked/Upstream Repository.
#
# This workflow rebase-marge changes from upstream's master to origin's master. 
# - Ref:
#   - https://stackoverflow.com/a/61574295/12102603 by N1ngu @ StackOverflow (EN)
#   - https://qiita.com/KEINOS/items/3bcaa6cea853f6b63475 by KEINOS @ Qiita (JA)

name: Merge upstream branches

# Triggers the action as scheduled
on:
  # Runs on 10 minutes past every hour（毎 n 時 10 分に実行）
  schedule:
    # Ref: 
    #   - https://help.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule
    #   - https://crontab.guru/examples.html
    # Cron format:
    #         ┌───────────── minute (0 - 59)
    #         │ ┌───────────── hour (0 - 23)
    #         │ │ ┌───────────── day of the month (1 - 31)
    #         │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
    #         │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
    #         │ │ │ │ │                                   
    #         │ │ │ │ │
    #         │ │ │ │ │
    #         * * * * *
    - cron:  '10 */1 * * *'

jobs:
  merge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Merge upstream
        # 以下の設定を変更してください。REPO_FORK が fork 元です。
        env:
          NAME: KEINOS
          EMAIL: github+fork-qiita-news@keinos.com
          REPO_FORK: https://github.com/yyano/Qiita-News.git
        # 追従処理
        run: |
          git config --global user.name ${NAME}
          git config --global user.email ${EMAIL}
          
          : # git rebase をデフォルトに設定
          git config --global pull.rebase merges

          : # "git checkout master" は不要です。デフォルトで設定されます。
          : # しかし下記の設定は重要です。過去のコミットが取得されないと、コミット歴に不統合が
          : # 発生した旨のエラーが出ます。
          git pull --unshallow

          : # フォーク元のリポジトリをリモート先として "upstream" に命名
          git remote add upstream ${REPO_FORK}
          
          : # upstream のブランチをローカルに取得
          git fetch upstream
          
          : # marster ブランチの変更をマージし、clone 元の master に push
          : # ブランチ名を main などに変更していたり、別のブランチにしている場合は注意
          git checkout master
          git merge --no-edit upstream/master
          git push origin master
```

:::note warn
🐒 【**<font color=red>注意点</font>**】コンフリクトとクォータ

自分の `master`（`origin/master`） をいじりすぎてフォーク元と異なる場合、かなりの高い頻度で**コンフリクトを起こします**。ブランチを切ってから作業する習慣がない（`master` に直接コミットしている）場合には、余計な操作が増えるだけなのでオススメしません。

また、当然ですが、自分の `master`（`origin/master`） からブランチを切ると、この GitHub Action のワークフロー（`YAML` ファイル）も一緒に付いてきます。

その状態でフォーク元（`upstream/master`）に PR をするとワークフローも一緒に PR してしまうことになります。相手がよくわからないでマージすると、勝手にアクションが実行されエラーになるので、相手に迷惑をかけないように注意します。フォーク元には PR しないで、同期だけが必要なリポジトリで使うことをオススメします。

俺様カスタム用のリポジトリの場合、筆者は `master` を `original` とリネームし、`orphan` ブランチを `main`（もしくは `master`）で作成して、追随は `original` ブランチにするようにしています。

なお、[GitHub Actions で無料で使える実行時間は約 33 時間/月](https://docs.github.com/ja/github/setting-up-and-managing-billing-and-payments-on-github/about-billing-for-github-actions#)です。これを越えると、翌月まで使えなくなります。
:::

### 動作サンプル・リポジトリ

- フォーク元： https://github.com/yyano/Qiita-News
  - from 「[Qiitaの新着記事を見たい。なのでQiita APIを叩こう。](https://qiita.com/yyano/items/f8172f80cb93307af317#%E7%94%9F%E6%88%90%E3%81%97%E3%81%9F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E5%85%AC%E9%96%8B%E5%85%88)」 @ Qiita
- フォーク先： https://github.com/KEINOS/Fork_Qiita-News
  - GitHub Action の設定例: https://github.com/KEINOS/Fork_Qiita-News/blob/master/.github/workflows/schedule_sync.yml
  - アクションの実行結果例: https://github.com/KEINOS/Fork_Qiita-News/actions
- 注釈: フォーク元がアーカイブ（閉鎖）されたことにより、現在は同期を止めています。

### GitHub App

GitHub Actions でなく、サードパーティーが提供している GitHub Apps を使うなら `wei/pull` も簡単。

- [Githubでフォークしたリポジトリを自動でupstreamに追従してくれる wei/pull の導入](https://qiita.com/kojix2/items/dc4e74c1aa5857b9da42) @ Qiita

## TS; DR

### 設定方法

1. リポジトリをフォークして、フォーク元のリポジトリ URL（`HTTPS` の `.git` 付き URL）をコピーしておきます。
2. フォークしたリポジトリの GitHub 上にある "Actions" タブから "`set up a workflow yourself`" を選び、上記（TL; DR）の YAML の内容を設置（コピペ）します。ファイル名は、わかりやすければ何でも構いませんが、拡張子は `.yml` である必要があります。
3. `cron` を実行するタイミング／ユーザー／メールアドレス／フォーク元のリポジトリ URL を変更します。
4. `Start commit` → `Commt new file` でコミットします。

フォークしたリポジトリの `./.github/workflows` ディレクトリにワークフローが追加され、`cron` で指定したタイミングで同期が始まるので、GitHub の Action タブで成功しているか確認します。

### YAML ファイルの設定内容の概要

```yaml:ワークフローの名前
name: Merge upstream branches

on:
（以下略）
```

```yaml:scheduleイベントにcronの実行タイミングを指定する
name: Merge upstream branches

on:
  schedule:
    # cron の設定: https://crontab.guru/examples.html
    # GitHub にはユーザーごとに実行可能な時間の制限があります。合計時間が制限を
    # 超えないように必要最低限のインターバルで実行するようにします。
    - cron:  '10 */1 * * *'

jobs:
（以下略）
```

```yaml:mergeジョブをubuntu上で実行する
name: Merge upstream branches

on:
  schedule:
    - cron:  '10 */1 * * *'

jobs:
  merge:
    runs-on: ubuntu-latest
    steps:
      - （以下略）
```

```yaml:checkoutアクションを使ってリポジトリのclone/checkoutを行う
name: Merge upstream branches

on:
  schedule:
    - cron:  '10 */1 * * *'

jobs:
  merge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: （以下略）
```

```yaml:同期処理（pull,rebase,mergeからのpush）
name: Merge upstream branches

on:
  schedule:
    - cron:  '10 */1 * * *'

jobs:
  merge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Merge upstream
        run: |
      # コミット／マージの実行ユーザー情報
      # NAME、EMAIL は下記 env 項目で設定。
      # なお、GitHub のパスワード／トークンは checkout アクションで設定済み。
          git config --global user.name ${NAME}
          git config --global user.email ${EMAIL}
          
      # デフォルトで git rebase の --rebase-merges を付加
          git config --global pull.rebase merges
          
      # "git checkout master" は checkout アクションで実行済みなので不要。
          
      # unshallow オプションで更新情報のみを pull してくる。これを設定しないと、
      # アップストリーム（Fork 元）から pull するときにも、すべてのブランチデータを
      # 持ってきてしまう。
          git pull --unshallow
          
      # フォーク元のリポジトリの URL をリモート先として登録し、"upstream" と名付ける。
      # REPO_FORK は env で設定。
          git remote add upstream ${REPO_FORK}
          
      # アップストリームのブランチ情報を取得
          git fetch upstream
                    
      # upstream/master の変更をマージして origin/master にプッシュ
          git checkout master
          git merge --no-edit upstream/master
          git push origin master
        env:
          # コミット／マージを行うユーザー情報とフォーク元の git リポジトリの URL（要変更）
          NAME: KEINOS
          EMAIL: github+fork-qiita-news@keinos.com
          REPO_FORK: https://github.com/yyano/Qiita-News.git
```

## 参考文献

- [Can forks be synced automatically in gitHub?]() @ StackOverflow
- <code>[$ git pull --unshallow](https://git-scm.com/docs/git-pull#Documentation/git-pull.txt---unshallow)</code> | git-pull @ Git 公式マニュアル
- <code>[$ git config pull.rebase merges](https://git-scm.com/docs/git-config#Documentation/git-config.txt-branchltnamegtrebase)</code> | git-config @ Git 公式マニュアル
- [【GitHub Actions】CIを使って毎日自動でGitHubに草を生やそうｗｗｗ](https://qiita.com/ykhirao/items/65fee829ee0478187027) @ Qiita
- [GitHubでFork/cloneしたリポジトリを本家リポジトリに追従する](https://qiita.com/xtetsuji/items/555a1ef19ed21ee42873) @ Qiita