Technology Engineering

178inaba の技術ブログ

削除済みリモートブランチを追跡しているローカルブランチを1発で全削除するコマンド

リモートブランチはマージ後GitHubが削除するか聞いてくれるのでその時点で削除するようにしている。

f:id:i178inaba:20181103173338p:plain

だが、そのリモートブランチを追跡しているローカルブランチの削除を忘れて溜まっていることがよくある。

$ git branch -v
* develop           fef1cdd Merge pull request #30 from abcdef/fix/bazbarfoo
  feature/foo       e57408f [gone] Fix deploy
  feature/bar       a9b73ad [gone] Upload image
  feature/baz       b833113 [gone] Fix test
  feature/foobar    703e2ad [gone] Add foobar.c
  feature/foobaz    1587581 [gone] Fix foobaz.rb
  feature/barfoo    37265cb [gone] Add barfoo.php
  feature/barbaz    3c121ca [gone] Fix barbaz.py
  feature/bazfoo    cb69767 [gone] Add bazfoo.go
  feature/bazbar    bb8575f [gone] Fix bazbar.pl
  feature/foobarbaz 66d2807 [gone] Fix README.md
  fix/foobazbar     84a781e [gone] Add gitignore
  fix/bazfoobar     cd36a6b [gone] Fix ci
  master            0a41ce8 Merge pull request #20 from abcdef/develop

[gone] が付いているのがリモートブランチへの追跡が切れているローカルブランチ。

いつも git branch -d の後ろに1つずつブランチ名をコピペして削除していた。

$ git branch -d feature/foo feature/bar feature/baz feature/foobar feature/foobaz feature/barfoo feature/barbaz feature/bazfoo feature/bazbar feature/foobarbaz fix/foobazbar fix/bazfoobar
Deleted branch feature/foo (was e57408f).
Deleted branch feature/bar (was a9b73ad).
Deleted branch feature/baz (was b833113).
Deleted branch feature/foobar (was 703e2ad).
Deleted branch feature/foobaz (was 1587581).
Deleted branch feature/barfoo (was 37265cb).
Deleted branch feature/barbaz (was 3c121ca).
Deleted branch feature/bazfoo (was cb69767).
Deleted branch feature/bazbar (was bb8575f).
Deleted branch feature/foobarbaz (was 66d2807).
Deleted branch fix/foobazbar (was 84a781e).
Deleted branch fix/bazfoobar (was cd36a6b).

面倒なので1発コマンド打つだけで全部消えるコマンドを組もうと思い調べた。

コマンド

git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)' | xargs git branch -d

解説

最初は git branch コマンドを使って作ろうとしていた。
だが、先の記事にも書いた通り、 git branch を使ってスクリプトを書かないほうがいいらしい。

inaba.hatenablog.com

なので git for-each-ref を使って書いた。
これはGitレポジトリの各refの情報を条件に従って出力していくコマンド。

Git - git-for-each-ref Documentation

何点か解説を書いてみる。

ブランチ名

ブランチ名は %(refname:short) で取得。
short をつけないと refs/heads がついてしまい、ブランチ名として認識されないので注意が必要。

# `refs/heads` がついてしまう。
$ git for-each-ref --format '%(refname)'
refs/heads/develop
refs/heads/feature/foo
refs/heads/feature/bar
refs/heads/feature/baz
refs/heads/feature/foobar
refs/heads/feature/foobaz
refs/heads/feature/barfoo
refs/heads/feature/barbaz
refs/heads/feature/bazfoo
refs/heads/feature/bazbar
refs/heads/feature/foobarbaz
refs/heads/fix/foobazbar
refs/heads/fix/bazfoobar
refs/heads/master
refs/remotes/origin/HEAD
refs/remotes/origin/develop
refs/remotes/origin/feature/aaa
refs/remotes/origin/feature/bbb
refs/remotes/origin/ccc
refs/remotes/origin/master
refs/remotes/origin/try/ddd
refs/remotes/origin/try/eee

# shortをつければ正しいブランチ名が取得できる。
$ git for-each-ref --format '%(refname:short)'
develop
feature/foo
feature/bar
feature/baz
feature/foobar
feature/foobaz
feature/barfoo
feature/barbaz
feature/bazfoo
feature/bazbar
feature/foobarbaz
fix/foobazbar
fix/bazfoobar
master
origin/HEAD
origin/develop
origin/feature/aaa
origin/feature/bbb
origin/ccc
origin/master
origin/try/ddd
origin/try/eee

gone

削除対象は [gone] が付いたブランチなのでこれを取得する。

upstream の項で以下のように説明されている。

:track also prints "[gone]" whenever unknown upstream ref is encountered.

試すと

$ git for-each-ref --format '%(upstream:track)'

[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]








[gone] のみでは分かりづらいと思うのでブランチ名をつけてみる。

$ git for-each-ref --format '%(upstream:track) %(refname:short)'
 develop
[gone] feature/foo
[gone] feature/bar
[gone] feature/baz
[gone] feature/foobar
[gone] feature/foobaz
[gone] feature/barfoo
[gone] feature/barbaz
[gone] feature/bazfoo
[gone] feature/bazbar
[gone] feature/foobarbaz
[gone] fix/foobazbar
[gone] fix/bazfoobar
 master
 origin/HEAD
 origin/develop
 origin/feature/aaa
 origin/feature/bbb
 origin/ccc
 origin/master
 origin/try/ddd
 origin/try/eee

リモートへの追跡が切れていない develop, master やリモートにのみ存在しているブランチには [gone] がついておらず、リモートへの追跡が切れたローカルブランチにのみ [gone] がついている事がわかる。

if

git for-each-ref ではif文( %(if)…​%(then)…​%(end) )が使える。
if文を使い、 [gone] があるときだけブランチ名を表示するようにする。

$ git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)'

feature/foo
feature/bar
feature/baz
feature/foobar
feature/foobaz
feature/barfoo
feature/barbaz
feature/bazfoo
feature/bazbar
feature/foobarbaz
fix/foobazbar
fix/bazfoobar








xargs

取得できたブランチ名をxargsを使って git branch -d に流す。 ちなみにxargsだけ実行すると git branch -d にどんな値が渡っているのかわかる。

$ git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)' | xargs
feature/foo feature/bar feature/baz feature/foobar feature/foobaz feature/barfoo feature/barbaz feature/bazfoo feature/bazbar feature/foobarbaz fix/foobazbar fix/bazfoobar

こんな感じ。

別解

サブコマンドとして実行してもよい。

git branch -d $(git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)')