Gitでマージが未完了のリモートブランチ一覧を作る

Gitを使ってチーム開発をしていると、知らぬ間に削除に漏れたブランチが増えていきます。GitLabだとMerge Requestをマージするときにソースブランチを削除したり、マージ済みブランチを一括削除したりする機能もありますが、それでも残ったブランチは個別に確認して削除するしかありません。この記事では、そういった棚卸し作業をするときに便利なブランチ一覧を作る方法を紹介します。

### 解決方法

`git for-each-ref` コマンドを使って、`origin` にあるブランチと最終コミットの情報を一覧にして出力できます。この一覧があれば、チーム内で最後にコミットしたユーザーで分担して棚卸しをすることができます（半年経ったら強制自動削除、といったルールの方が運用が楽だとは思いますが）。

```shell
$ git for-each-ref --sort='-authordate' --format='%(authordate:short) %09 %(authorname) %09 %(refname:lstrip=3) %09 %(subject)' refs/remotes/origin --no-merged
2022-03-02 	 dependabot[bot] 	 dependabot/npm_and_yarn/karma-6.3.16 	 build(deps-dev): bump karma from 3.1.4 to 6.3.16
2022-02-22 	 dependabot[bot] 	 dependabot/npm_and_yarn/trim-off-newlines-1.0.3 	 build(deps): bump trim-off-newlines from 1.0.1 to 1.0.3
2022-02-12 	 dependabot[bot] 	 dependabot/npm_and_yarn/follow-redirects-1.14.8 	 build(deps): bump follow-redirects from 1.14.7 to 1.14.8
2022-01-15 	 dependabot[bot] 	 dependabot/npm_and_yarn/shelljs-0.8.5 	 build(deps-dev): bump shelljs from 0.8.3 to 0.8.5
2021-12-15 	 Evan You 	 patreon-update-1639564268812 	 chore: update sponsors [ci skip]
2021-12-05 	 vue-bot 	 patreon-update-1638680711475 	 chore: update backers [ci skip]
2021-10-08 	 vue-bot 	 patreon-update-1633726294879 	 chore: update sponsors [ci skip]
2021-10-07 	 vue-bot 	 patreon-update-1633630862625 	 chore: update sponsors [ci skip]
2021-09-01 	 dependabot[bot] 	 dependabot/npm_and_yarn/jszip-3.7.1 	 build(deps): bump jszip from 3.1.5 to 3.7.1
2021-08-11 	 dependabot[bot] 	 dependabot/npm_and_yarn/path-parse-1.0.7 	 build(deps): bump path-parse from 1.0.6 to 1.0.7
...
```

### 補足: git for-each-refコマンドの使い方

オプションなしで実行すると、ローカル＆リモートのすべてのブランチ＆タグを一覧できる。

```shell
$ git for-each-ref
6aa11872c88481dfa2da151536317176c48f226c commit refs/heads/dev
6aa11872c88481dfa2da151536317176c48f226c commit refs/heads/wip
...
0c9b831db4d149006de9ef62979d53cee3d41b69 commit refs/remotes/origin/dependabot/npm_and_yarn/acorn-5.7.4
a32f8b9d46491a072a656d4b533d105a388e148e commit refs/remotes/origin/dependabot/npm_and_yarn/browserslist-4.16.6
370d39f7a157923d5aa58fbbff318cb41a140b59 commit refs/remotes/origin/dependabot/npm_and_yarn/codecov-3.7.1
...
8b3b8fbd0751c46c7c149383bb4e072be0d01dff commit refs/tags/0.10.0-rc
5bc3e30b694dc3b6aeeb87c24f88b9cd7a7259d6 commit refs/tags/0.11.0
...
```

`refs/remotes/origin` を指定することで、一覧対象を origin のブランチだけに制限できる。また、`--no-merged` を指定することで、マージ済みでないブランチに制限できる。

:::note info
--no-merged だけだと HEAD にマージされていないブランチ一覧になる。より厳密に指定するには、--no-merged=origin/master のように比較対象のブランチを明示する。
:::

```shell
$ git for-each-ref refs/remotes/origin --no-merged
0c9b831db4d149006de9ef62979d53cee3d41b69 commit refs/remotes/origin/dependabot/npm_and_yarn/acorn-5.7.4
a32f8b9d46491a072a656d4b533d105a388e148e commit refs/remotes/origin/dependabot/npm_and_yarn/browserslist-4.16.6
370d39f7a157923d5aa58fbbff318cb41a140b59 commit refs/remotes/origin/dependabot/npm_and_yarn/codecov-3.7.1
...
```

このままでは何に使ったブランチか分かりづらい。そこで、`--format` オプションで表示する情報を指定できる。Authorの情報を表す `author(date|name|email)` やコミットメッセージの `subject` など [色々なフィールド](https://git-scm.com/docs/git-for-each-ref#_field_names) がある。
また、`--sort` オプションで表示順を制御することもでき、`-authordate` のように先頭に `-` を付けると降順になる。これで古めのブランチと新しめのブランチを区別しやすくなる。

```shell
$ git for-each-ref --sort='-authordate' --format='%(authordate) %(authorname) %(refname) %(subject)' refs/remotes/origin --no-merged
Wed Mar 2 04:25:29 2022 +0000 dependabot[bot] refs/remotes/origin/dependabot/npm_and_yarn/karma-6.3.16 build(deps-dev): bump karma from 3.1.4 to 6.3.16
Tue Feb 22 18:22:27 2022 +0000 dependabot[bot] refs/remotes/origin/dependabot/npm_and_yarn/trim-off-newlines-1.0.3 build(deps): bump trim-off-newlines from 1.0.1 to 1.0.3
Sat Feb 12 12:08:45 2022 +0000 dependabot[bot] refs/remotes/origin/dependabot/npm_and_yarn/follow-redirects-1.14.8 build(deps): bump follow-redirects from 1.14.7 to 1.14.8
Sat Jan 15 04:30:52 2022 +0000 dependabot[bot] refs/remotes/origin/dependabot/npm_and_yarn/shelljs-0.8.5 build(deps-dev): bump shelljs from 0.8.3 to 0.8.5
Wed Dec 15 18:31:16 2021 +0800 Evan You refs/remotes/origin/patreon-update-1639564268812 chore: update sponsors [ci skip]
Sun Dec 5 05:05:13 2021 +0000 vue-bot refs/remotes/origin/patreon-update-1638680711475 chore: update backers [ci skip]
...
```

各フィールドに出力書式を指定することで、さらにノイズを減らすことができる。 `%(authordate:short)` で日付だけに制限したり、 `%(refname:lstrip=3)` でパス区切りの先頭3つ目まで（=`refs/remotes/origin`）を除外したり。

```shell
$ git for-each-ref --sort='-authordate' --format='%(authordate:short) %(authorname) %(refname:lstrip=3) %(subject)' refs/remotes/origin --no-merged
2022-03-02 dependabot[bot] dependabot/npm_and_yarn/karma-6.3.16 build(deps-dev): bump karma from 3.1.4 to 6.3.16
2022-02-22 dependabot[bot] dependabot/npm_and_yarn/trim-off-newlines-1.0.3 build(deps): bump trim-off-newlines from 1.0.1 to 1.0.3
2022-02-12 dependabot[bot] dependabot/npm_and_yarn/follow-redirects-1.14.8 build(deps): bump follow-redirects from 1.14.7 to 1.14.8
2022-01-15 dependabot[bot] dependabot/npm_and_yarn/shelljs-0.8.5 build(deps-dev): bump shelljs from 0.8.3 to 0.8.5
2021-12-15 Evan You patreon-update-1639564268812 chore: update sponsors [ci skip]
2021-12-05 vue-bot patreon-update-1638680711475 chore: update backers [ci skip]
```

最後に、タブ区切り（=`%09`）にしておけば、冒頭の出力結果になる。TSV形式なので、スプレッドシートなどに取り込んで、作業状況を管理することも簡単になる。

```shell
$ git for-each-ref --sort='-authordate' --format='%(authordate:short) %09 %(authorname) %09 %(refname:lstrip=3) %09 %(subject)' refs/remotes/origin --no-merged
2022-03-02 	 dependabot[bot] 	 dependabot/npm_and_yarn/karma-6.3.16 	 build(deps-dev): bump karma from 3.1.4 to 6.3.16
2022-02-22 	 dependabot[bot] 	 dependabot/npm_and_yarn/trim-off-newlines-1.0.3 	 build(deps): bump trim-off-newlines from 1.0.1 to 1.0.3
2022-02-12 	 dependabot[bot] 	 dependabot/npm_and_yarn/follow-redirects-1.14.8 	 build(deps): bump follow-redirects from 1.14.7 to 1.14.8
2022-01-15 	 dependabot[bot] 	 dependabot/npm_and_yarn/shelljs-0.8.5 	 build(deps-dev): bump shelljs from 0.8.3 to 0.8.5
2021-12-15 	 Evan You 	 patreon-update-1639564268812 	 chore: update sponsors [ci skip]
2021-12-05 	 vue-bot 	 patreon-update-1638680711475 	 chore: update backers [ci skip]
...
```

### おまけ

お好みでaliasに登録しておくと、毎回コマンドを打たなくてよいので便利かも。

```shell
$ vi ~/.gitconfig
[alias]
    list-remote-no-merged = !git for-each-ref --sort='-authordate' --format='%(authordate:short) %(authorname) %(refname:lstrip=3) %(subject)' refs/remotes/origin --no-merged

$ git list-remote-no-merged
```

### 参考情報

* [Git - git-for-each-ref Documentation](https://git-scm.com/docs/git-for-each-ref)