delete-github-branchesというブランチの命名ルールを定義して、そのルールにあっていないブランチを削除するCLIツールを書きました。

delete-github-branchesはCLIですが、GitHub Actionsのcron処理と合わせれば、ルールにあってないブランチをGitHubから自動的に削除できます。

不要なブランチを削除することで、リポジトリからノイズとなるブランチを減らすのが目的です。 Pull Requestなしで放置されてるブランチが単なる消し忘れ以外であることは少ないので、そのようなブランチを減らすことでpullした時のノイズやサイズを減らすの目的です。 (活発なリポジトリだと放置ブランチがconflictだらけになるのでマージが難しいです。またGitHubにはDraft PRもあります)

CLIの使い方

delete-github-branchesは主に次の3つの条件をすべて満たす場合にのみブランチを削除します。

  • includesBranchPatterns(許可するブランチ名ルール)にマッチしてない
  • excludesBranchPatterns(許可しないブランチ名ルール)にマッチしている
  • そのブランチに紐づくOpenなPull Requestが存在しない
  • そのブランチへの最後のPushからstalledDays(デフォルトは30)日以上経過している

基本的には、許可していないブランチ名にマッチして、そのブランチに紐づく開いているPull Reuqestがなければ削除します。 そのため、基本的にずっと放置されていてPull Requestも出していないブランチを削除することが目的です。

この設定はコマンドライン引数でも指定できますが、別途設定ファイルでも指定できます。

次のような設定ファイルを作成します。 この設定では基本的にはfeature/<name>というブランチを切ってからPull Requestを出すというルールになります。

delete-github-branches.json:

{
    "includesBranchPatterns":  ["/^.*$/"],
    "excludesBranchPatterns": ["master", "develop", "gh-pages", "/^feature\/.*$/"],
    "stalledDays": 30
}

そして次のようにdelete-github-branchesで削除したいGItHubの<owner>/<repo>を指定して実行します。 (例として ttps://github.com/azu/delete-github-branches-test/ に対してブランチの削除を実行しています。GITHUB_TOKENには各自のGitHubトークンを渡してください)

$ GITHUB_TOKEN=$GH_TOKEN npx delete-github-branches --owner azu --repo delete-github-branches-test --config ./delete-github-branches.json

どのように削除されるかを実際に削除はせずに確認だけしたい場合は--dryRunが利用できます。 dryRunモードの場合はGETの処理しかしないので、実際にはブランチを削除をせずにログだけ出力します。

$ GITHUB_TOKEN=$GH_TOKEN npx delete-github-branches --owner azu --repo delete-github-branches-test --dryRun --config ./delete-github-branches.json


# Deleted Branches

- [will-be-deleted](https://github.com/azu/delete-github-branches-test/tree/will-be-deleted)

# Active Branches

- [develop](https://github.com/azu/delete-github-branches-test/tree/develop)
> This branch is ignored by includes/excludes patterns
- [feature/a](https://github.com/azu/delete-github-branches-test/tree/feature/a)
> This branch is ignored by includes/excludes patterns
- [feature/b](https://github.com/azu/delete-github-branches-test/tree/feature/b)
> This branch is ignored by includes/excludes patterns
- [master](https://github.com/azu/delete-github-branches-test/tree/master)
> This branch is ignored by includes/excludes patterns

せっかくブランチ名のルールを決めているので、GitHub Actionsを使ってブランチ名の検証とブランチの自動削除もできます。

GitHub Actionとの連携

次のリポジトリにサンプルがあります

ちょっと長いですが、次のようなGitHub Actionsの設定ファイルを追加します。

.github/workflows/delete-github-branches.yml:

name: delete-github-branches

on:
  pull_request:
    types: [opened]
  # At 00:00 everyday
  schedule:
    - cron: '0 0 * * *'

jobs:
  delete-branch:
    runs-on: ubuntu-latest
    if: github.event_name == 'schedule'
    steps:
      - name: checkout
        uses: actions/checkout@v2
      - name: Setup Node $
        uses: actions/setup-node@v1
        with:
          node_version: 12.x
      - name: Run delete-github-branches
        run: |
          npm install -g delete-github-branches@1
          delete-github-branches --config ./.github/delete-github-branches.json
        env:
          GITHUB_TOKEN: $

  check-pull-request-branch:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - name: checkout
        uses: actions/checkout@v2
      - name: Setup Node $
        uses: actions/setup-node@v1
        with:
          node_version: 12.x
      - name: Check branch name
        id: check_branch_name
        shell: bash -x {0}
        run: |
          echo "GITHUB_BRANCH: ${BRANCH_NAME}"
          npm install -g delete-github-branches@1
          RESULT_DELETE_GITHUB_BRANCH=$(delete-github-branches-check-branch-name --config ./.github/delete-github-branches.json "${BRANCH_NAME}")
          RET=$?
          if [ "$RET" = "1" ]; then
              # multi-line issue https://github.community/t5/GitHub-Actions/set-output-Truncates-Multiline-Strings/td-p/37870
              RESULT_DELETE_GITHUB_BRANCH="${RESULT_DELETE_GITHUB_BRANCH//'%'/'%25'}"
              RESULT_DELETE_GITHUB_BRANCH="${RESULT_DELETE_GITHUB_BRANCH//$'\n'/'%0A'}"
              RESULT_DELETE_GITHUB_BRANCH="${RESULT_DELETE_GITHUB_BRANCH//$'\r'/'%0D'}"
              echo "::set-output name=message::${RESULT_DELETE_GITHUB_BRANCH}"
              echo "::set-output name=invalid_branch_name::true"
              echo "this branch name is invalid"
              exit 0
          fi
          echo "Good branch name"
          echo "${RESULT_DELETE_GITHUB_BRANCH}"
        env:
          BRANCH_NAME: $
          GITHUB_TOKEN: $
      - uses: actions/[email protected]
        if: steps.check_branch_name.outputs.invalid_branch_name == 'true'
        with:
          github-token: $
          script: |
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `@$ This branch name is mismatch branch naming rule.<br/><pre>$</pre>`
            })

合わせて先ほどと同じdelete-github-branches.jsonというファイルを.github以下に移動しておきます。

.github/delete-github-branches.json:

{
    "includesBranchPatterns":  ["/^.*$/"],
    "excludesBranchPatterns": ["master", "develop", "gh-pages", "/^feature\/.*$/"],
    "stalledDays": 30
}

このGitHub Actionsは次の2つのことを行います。

  • Pull Requestが出たブランチ名がルールにマッチしているかのチェック
  • Cronで削除条件を満たすブランチを毎日削除

Pull Requestのブランチ名がルールとあっていない場合は、次のようにコメントで注意が出るようになっています。

mismatch branch

Cronで毎日削除(実際には最後にPushしてから30日経過しないと削除されない)するので、リポジトリに不要なブランチが減らせるはずです。 リポジトリにゴミっぽいブランチが残ったりする場合は、delete-github-branchesを使うことで整理できるかもしれません。

別の使い方

例えば、master以外のブランチ以外を削除するルールにしておきstalledDaysを180日とかにすれば、 更新されずに放置された古いブランチを自動的に削除するルールとして実装できます。 (もちろん紐づくPRがあるならば削除されません。API的に削除しようとしてもエラーになるはずです)

{
    "includesBranchPatterns":  ["/^.*$/"],
    "excludesBranchPatterns": ["master"],
    "stalledDays": 180
}