「あっマージ済みのブランチを大量にGitHubにプッシュしてしまった...」という時に読む記事

「ふー実装終わったしプッシュするか」
「あれっ、マージ済みのブランチも全部プッシュしちゃってる！」
「ブランチの数が338もある...どうすれば...！」
![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/9f9f3c83-ae7d-52bb-cacc-edca819f9964.png)

という時に読む記事です。

# やること

1. 不要なマージ済みのブランチを削除する
2. またプッシュしてしまわないよう対策する

# 不要なマージ済みのブランチを削除する

## (リモート) 不要なマージ済みのブランチを削除する
 
不要なブランチがリモート（今回はGitHub）にプッシュされてしまったので削除します。
ブラウザ上でぽちぽちクリックして削除していけばいつか終わりますが、`git` コマンドを使って 5秒で終わらせましょう。

```bash
# デフォルトブランチ（メインのブランチ）に checkout して pull しておく
#  ※ ここでは例として develop ブランチとする
git checkout develop
git pull

# マージ済みの不要なブランチを削除する
#  ※ 消してはならないブランチは残す
#    ここでは例として master, staging, develop ブランチは削除しない
git branch -r --format "%(refname:short)" --merged | egrep -v '/develop|/staging|/master' | sed 's|origin/||g' | xargs -n1 git push --delete origin
```

### ↑のコマンドで何をやっている解説

まずはデフォルトブランチを `checkout` して `pull` して最新状態にしておきます。
もしくは `fetch` して `origin/develop` を指定しても良いですが、意味が分からなければ `checkout` して `pull` するのが確実です。

次にマージ済みの不要なブランチを削除するコマンドです。
分かりやすく改行を入れてみましょう。

```bash
# マージ済みのブランチを抽出
#  -r で リモートブランチのみを対象
#  --format "%(refname:short)" で origin/branch名 の形式で出力
#  --merged でカレントブランチにマージ済みなものを対象にする
git branch -r --format "%(refname:short)" --merged \
| \
# develop, staging, master ブランチは対象外にする
egrep -v '/develop|/staging|/master' \
| \
# origin/branch名 の origin/ を取り除く
sed 's|origin/||g' \
| \
# xargs -n1 でブランチを1つずつ git push --delete origin で消していく
xargs -n1 git push --delete origin
```

まとめると `git push --delete origin ブランチ名` で、リモートのブランチを削除できるので、削除対象のブランチを一気に削除するようなコマンドにします。

ちなみに、もしかするとリモートのブランチを削除するには `git push origin :ブランチ名` とする、という説明を目にしたことがあるかもしれませんが、それは `git v1.7.0` より古いバージョンの時の話です。[^1]
今でも `git push origin :ブランチ名` は使えますが、 `--delete` オプションの方が分かりやすいので `--delete` を使いましょう。

[^1]: [`git push --delete` オプションは 1.7.0 で追加されました](https://github.com/git/git/blob/master/Documentation/RelNotes/1.7.0.txt)

### 削除に時間が掛かる...とお悩みのあなたへ

`git push --delete` は複数のブランチを引数に渡せます。こちらの方が早いです。爆速です。
そこで　`xargs -n1` の `-n1` を取り除けば、対象のブランチが全て `git push --delete` の引数に渡されて爆速になります。

```bash
git branch -r --format "%(refname:short)" --merged \
| \
egrep -v '/develop|/staging|/master' \
| \
sed 's|origin/||g' \
| \
xargs git push --delete origin
```

ただし、リモートに存在しないブランチが引数に混ざっていると、他のブランチの削除に失敗します。

```bash
$ git branch -r --format "%(refname:short)" --merged | egrep -v '/develop|/staging|/master' | sed 's|origin/||g' | xargs -n1 git push --delete origin
error: unable to delete 'orahen-branch': remote ref does not exist
error: failed to push some refs to 'ssh://git@github.com/yasuhiroki/sample'
```

そんな時は `git fetch -p` や `egrep -v` を駆使して取り除きましょう。

```bash
$ git branch -r --format "%(refname:short)" --merged | egrep -v '/develop|/staging|/master|/orahen-branch' | sed 's|origin/||g' | xargs -n1 git push --delete origin
To ssh://github.com/yasuhiroki/sample
...(略)...
```

これで300ブランチを5秒で削除できます。

# またプッシュしてしまわないよう対策する

## (ローカル) 不要なマージ済みのブランチを削除しておく

不要なブランチをプッシュできたということは、不要なブランチがローカルに残っているということです。
日頃、ローカルから不要なブランチを消しておけば良いのです。

```bash
# デフォルトブランチ（メインのブランチ）に checkout して pull しておく
#  ※ ここでは例として develop ブランチとする
git checkout develop
git pull

# マージ済みの不要なブランチを削除する
#  ※ 消してはならないブランチは残す
#    ここでは例として master, staging, develop ブランチは削除しない
git branch -d $(git branch --merged | egrep -v '^\*|master|staging|develop')
```

## 不要なリモートブランチをローカルから削除しておく

何を言っているのかよく分からないと思った方はとりあえず `git branch -a` を実行してみてください。

```bash
$ git branch -a
...略...
  remotes/origin/iranai1
  remotes/origin/iranai2
  remotes/origin/iranai3
  remotes/origin/iranai4
  remotes/origin/iranai5
...略...
```

`remotes/origin/不要なブランチ` が大量にありましたか？
それが **不要なリモートブランチ** です。消しましょう。
消し方は `git fetch -p` です。

```bash
$ git fetch -p
From github.com:yasuhiroki/hogehoge
 - [deleted]         (none)     -> origin/iranai1
```

不要なリモートブランチが残っていると、そのブランチに `checkout` できてしまいます。
`checkout` するとローカルにブランチが作成されます。
ローカルにブランチがあればプッシュできてしまいます。
プッシュしてしまう原因は事前に取り除いておきましょう。


### 不要なブランチを日頃から削除しておく

`git` は軽い気持ちでブランチを切っていくものです（と私は思っています）。
ちょっとこの実装を試してみようかな、と思ったらブランチを切る時です。
`temp-xxxx` なブランチがたくさんあっても別に良いです。プッシュしなければ。

軽い気持ちでブランチを切っても良いので、その代わり、たまには整理しておきましょう。
`git branch` して、もう使わないなと思うブランチがあったら消しましょう。
記憶にないブランチがあればそれは消していいブランチです。
ローカルにブランチがなければプッシュしてしまうことはありません。消しましょう。

# まとめ

日頃からゴミはゴミ箱へ :sunglasses: 。