🥌

ブランチ名によるマイルストーン管理の自動化 [GitHub Actions]

2022/03/13に公開

概要

プロジェクトをマイルストーンで管理している場合にブランチ名を milestone/v1release/v1 のようにすることがあるかと思います。
手動でマイルストーンを編集するのは手間なので自動化します。

ワークフロー

マイルストーンブランチが作成/削除されたときにマイルストーンを作成/クローズします。
また PR のマージ先がマイルストーンブランチだった場合にマイルストーンをセットします。

.github/workflows/milestone.yml
name: Milestone

on:
  create:
  delete:
  pull_request:
    types: [ opened, edited ]
    branches:
      - milestone/**

env:
  GH_TOKEN: ${{ github.token }}
  GH_REPO: ${{ github.repository }}

jobs:
  create-milestone:
    if: github.event_name == 'create' && startsWith(github.event.ref, 'milestone/')
    runs-on: ubuntu-20.04
    timeout-minutes: 2
    env:
      BRANCH: ${{ github.event.ref }}

    steps:
      - name: Create a milestone
        run: |
          set -x
          milestones=$(gh api -X GET /repos/${GITHUB_REPOSITORY}/milestones -f state=all)
          if ! $(echo "${milestones}" | jq ". | any(.title == \"${BRANCH#milestone/}\")"); then
            gh api -X POST repos/${GITHUB_REPOSITORY}/milestones -f title=${BRANCH#milestone/}
          elif $(echo "${milestones}" | jq ". | any(.title == \"${BRANCH#milestone/}\" and .state == \"closed\")"); then
            milestone_number=$(echo "${milestones}" | jq ". | map(select(.title == \"${BRANCH#milestone/}\")) | first | .number")
            gh api -X PATCH repos/${GITHUB_REPOSITORY}/milestones/${milestone_number} -f state=open
          fi

  close-milestone:
    if: github.event_name == 'delete' && startsWith(github.event.ref, 'milestone/')
    runs-on: ubuntu-20.04
    timeout-minutes: 2
    env:
      BRANCH: ${{ github.event.ref }}

    steps:
      - name: Close a milestone
        run: |
          set -x
          milestone_number=$(gh api -X GET /repos/${GITHUB_REPOSITORY}/milestones --jq ". | map(select(.title == \"${BRANCH#milestone/}\")) | first | .number")
          gh api -X PATCH repos/${GITHUB_REPOSITORY}/milestones/${milestone_number} -f state=closed

  set-milestone:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-20.04
    timeout-minutes: 2
    env:
      PR_NUMBER: ${{ github.event.number }}

    steps:
      - name: Set a milestone
        run: gh pr edit ${PR_NUMBER} --milestone ${GITHUB_BASE_REF#milestone/}

マイルストーンブランチが作成されたとき

マイルストーンを新規作成します。
既にクローズ済みのマイルストーンがある場合は再オープンします。
再オープンするときはマイルストーン番号が必要なのでリストから取得します。

ブランチやタグの作成 (create) かつマイルストーンブランチのときだけ実行します。

${BRANCH#milestone/} でブランチ名からマイルストーン名を抽出します。

https://qiita.com/t_nakayama0714/items/80b4c94de43643f4be51#parameterword-前方一致除去最短一致

マイルストーンブランチが削除されたとき

マイルストーンをクローズします。
削除してしまうと完全になかったことになってしまうのでログを残す目的でクローズしています。

マイルストーンの削除

何かしらのフローで削除が必要な場合は以下のコマンドで削除できます。

gh api -X DELETE repos/${GITHUB_REPOSITORY}/milestones/${milestone_number}

ブランチやタグの作成 (delete) かつマイルストーンブランチのときだけ実行します。

PR にマイルストーンをセットする

PR のマージ先がマイルストーンブランチの場合にマイルストーンをセットします。

pull_request がオープンまたは(マージ先が)変更されたときかつマイルストーンブランチのときだけ実行します。

逆マージについて

逆マージを PR ベースでやるとブランチの自動削除ができなくなるのであまりやらないと思いますが、もしやる場合はフィルターにヘッドブランチの判定を追加した方がいいかもしれません。

    if: >-
      github.event_name == 'pull_request' &&
      !startsWith(github.head_ref, 'milestone/') &&
      github.head_ref != 'main'

余談:マイルストーンの編集をトリガーにするには?

ブランチをトリガーにした場合のワークフローを紹介しましたが、逆にマイルストーン (milestone) をトリガーにすることを考えてみます。

マイルストーンが作成されたとき

ブランチをどこから切るかは人間が判断するしかないので自動化は難しそうです。
もちろん明確なルールがあれば可能だと思います。

マイルストーンがクローズされたとき

マイルストーンが削除またはクローズされたときにトリガーします。
ブランチがどこにも取り込まれていない場合でもそのまま削除されてしまうので注意が必要です。
クローズされたときにトリガーするコード例です。

.github/workflows/milestone.yml
name: Milestone

on:
  milestone:
    types: [ closed ]

env:
  GH_TOKEN: ${{ github.token }}
  GH_REPO: ${{ github.repository }}

jobs:
  delete-branch:
    if: github.event_name == 'milestone'
    runs-on: ubuntu-20.04
    timeout-minutes: 2

    steps:
      - run: gh api -X DELETE repos/${GITHUB_REPOSITORY}/git/refs/heads/milestone/${MILESTONE}
        env:
          MILESTONE: ${{ github.event.milestone.title }}

結論

マイルストーンの編集をトリガーにブランチを操作するのは運用が難しそう。
どこかへ通知するくらいがいいのかもしれません。

Discussion