Gitのローカルブランチを一括削除する方法:–mergedではなくgone判定を使う理由

Gitのローカルブランチを一括削除する方法:--mergedではなくgone判定を使う理由 Git

Gitのローカルブランチを一括削除する方法を解説します。

リモートで削除済み(gone)のブランチを判定基準に使うことで、GitHub PRフローと相性の良い安全な一括削除が可能です。この記事では、コマンドの仕組みから--mergedとの使い分け、エイリアス設定まで、実務で使えるブランチ整理術をまとめました。

リモートに存在しないローカルブランチを一括削除する方法

先に結論です。以下のコマンドで、リモートで削除済みのローカルブランチをまとめて削除できます。

git fetch -p && git branch -vv | awk '/: gone]/ && !/^[*+]/ {print $1}' | xargs git branch -D

※ git branch -D で強制削除するので、自己責任で実行してください。

やっていることを一言でまとめると、「リモートに存在しないローカルブランチを検出して、現在使用中のブランチを除外した上で一括削除する」です。

すぐ使いたい方はこのコマンドをそのまま実行すればOKです。以降では、各パーツの仕組みや注意点を詳しく解説していきます。

なぜローカルブランチは溜まるのか

GitHub PRフローで開発していると、こんな流れが日常的に発生します。

  1. ローカルでfeatureブランチを作成
  2. リモートにpushしてPRを作成
  3. PRがマージされる
  4. リモートブランチはGitHubが自動削除
  5. ローカルブランチはそのまま残る

リモート側は自動で片付きますが、ローカル側は誰も掃除してくれません。気づくとブランチが10本、20本と溜まっていき、git branchの出力が長大になっていきます。

1本ずつgit branch -d feature/xxxで消していくのは面倒ですし、そもそもどれが不要なのか判断するのも手間です。そこで、リモートで既に削除されたブランチを自動判定して一括削除する方法が役立ちます。

コマンドを1つずつ分解して理解する

git branch -vv の出力を読む

一括削除コマンドの中核はgit branch -vvです。まず、-v-vvの違いを確認しましょう。

git branch -vの出力はこのようになります。

$ git branch -v
+ feature/A 758ef4a [gone] コミットメッセージA
  feature/B b0ba559 コミットメッセージB
* main      87fb4b3 コミットメッセージC

git branch -vvの出力はこうなります。

$ git branch -vv
+ feature/A 758ef4a (/path/to/directory) [origin/feature/A: gone] コミットメッセージA
  feature/B b0ba559 [origin/feature/B] コミットメッセージB
* main      87fb4b3 [origin/main] コミットメッセージC


違いはリモート追跡ブランチ名が表示されるかどうかです。-vvでは[origin/feature/...]のようにリモートの対応ブランチが明示されます。そして、リモートで既に削除されたブランチにはgoneという表示が付きます。

一括削除コマンドでは-vvの出力から: gone]を含む行を抽出することで、削除対象を特定しています。-vでも[gone]は表示されますが、-vvのほうがリモート追跡の状態を正確に確認できるため、こちらを使います。

awk でフィルタリングする仕組み

awk '/: gone]/ && !/^[*+]/ {print $1}'

このawkコマンドは3つの条件で構成されています。

/: gone]/は、出力に: gone]を含む行だけを対象にするパターンマッチです。リモートで削除済みのブランチがここで絞り込まれます。

!/^[*+]/は、行頭が*または+で始まる行を除外する条件です。*は現在チェックアウト中のブランチ、+は別のworktreeで使用中のブランチを意味します(+マークについては次のセクションで詳しく解説します)。

{print $1}は、スペース区切りの最初のフィールド、つまりブランチ名だけを出力します。

* と + の違い:worktree使用時の注意点

git branchの出力で行頭に付くマークには2種類あります。

*は現在チェックアウト中のブランチです。これを削除しようとするとエラーになるため除外が必要です。ほとんどのブランチ一括削除の記事で触れられています。

一方、+別のworktreeで使用中のブランチを示します。git worktreeで複数の作業ディレクトリを並行して使っている場合、そのworktreeでチェックアウトされているブランチに+が付きます。

$ git branch -vv
+ feature/A 758ef4a (/path/to/directory) [origin/feature/A: gone]  ....

上の例では、feature/A/path/to/directoryというworktreeで使用中であることがわかります。このブランチはgoneですが、worktreeで使用中のまま削除するとworktree側で問題が起きます。

!/^[*+]/で両方を除外しているのはこのためです。worktreeを使っていない環境では+が出ることはありませんが、条件に含めておいて損はありません。

xargs git branch -D で一括削除

xargs git branch -D

xargsは標準入力から受け取った各行をコマンドの引数として渡します。awkで抽出されたブランチ名が1つずつgit branch -Dに渡され、順番に削除されます。

ここで-d(小文字)ではなく-D(大文字)を使っているのには理由があります。-dはマージ済みのブランチしか削除しません。gone判定で対象になるブランチは、リモートでPRがsquash mergeされたケースなど、ローカルの視点では「未マージ」に見えることがあります。そのため、-Dで強制削除する必要があります。

git fetch –prune を組み合わせる

一括削除コマンドの先頭にあるgit fetch -p--pruneの短縮形)は、ブランチの状態を正しく判定するための前処理です。

git fetch --pruneは、リモートで既に削除されたブランチのリモート追跡参照をローカルから削除します。この操作を行わないと、実際にはリモートで消えているブランチがgoneとして表示されず、一括削除の対象から漏れてしまいます。

つまり、正しい手順は次の通りです。

  1. git fetch -pでリモートの最新状態を取得し、削除済みブランチの追跡参照を掃除
  2. git branch -vvでgone判定が正しく反映された状態を確認
  3. awkとxargsで対象を絞って削除

fetch -pを忘れたまま実行すると、本来削除すべきブランチが残ってしまうので、セットで覚えておくのがおすすめです。

gone vs –merged:どちらをいつ使うか

ローカルブランチの一括削除には、今回解説したgone判定のほかに--mergedを使う方法もよく紹介されています。両者は削除の判定基準が異なるため、開発フローに応じた使い分けが重要です。

観点gone判定–merged
削除対象リモートで削除済みのブランチ指定ブランチにマージ済みのブランチ
判定基準リモート追跡の状態マージ履歴
未pushブランチへの影響安全(追跡がないので対象外)注意が必要

個人的にはgoneでの判定がおすすめです。その理由は、--mergedには検出できないケースが2つあるためです。

–merged が効かないケース①:squash merge

GitHubのPRでsquash mergeを使うと、マージ時にコミットが1つにまとめられます。この場合、ローカルのfeatureブランチにあるコミットとmainに入ったコミットはハッシュが異なるため、git branch --merged mainでは「マージ済み」と判定されません。

–merged が効かないケース②:階層ブランチ

featureブランチから子ブランチ、さらに孫ブランチを切るような階層構造の場合も問題が起きます。

# このような階層がある場合
main → feature/A → feature/A' → feature/A''

# feature/A'をfeature/Aにマージしたが、feature/Aはまだmainにマージしていない
# → どのブランチも --merged main で検出されない
# → 手動で管理が必要

feature/A‘をfeature/Aにマージしても、feature/A自体がまだmainにマージされていなければ、--merged mainではどのブランチも検出されません。--merged feature/Aのように個別にマージ先を指定すれば検出できますが、ブランチごとに確認するのは現実的ではありません。

gone判定であれば、判断基準は「リモートで削除されたかどうか」だけです。ブランチの階層構造やマージ方法に関係なく、PRがマージされてリモートブランチが削除されていれば確実に検出できます。

エイリアスに登録して日常的に使う

毎回長いコマンドを打つのは面倒なので、エイリアスに登録しておくと便利です。

Git エイリアスとして登録する場合

git config --global alias.cleanup '!git fetch -p && git branch -vv | awk '"'"'/: gone]/ && !/^[*+]/ {print $1}'"'"' | xargs git branch -D'

これでgit cleanupと打つだけで一括削除が実行できます。

シェルコマンド(!で始まるもの)をGitエイリアスに登録する場合、awkのシングルクォート部分のエスケープが少し複雑になります。

削除前に確認したい場合

いきなり削除するのが不安な場合は、xargs git branch -Dの部分を外して対象ブランチだけ確認できます。

git fetch -p && git branch -vv | awk '/: gone]/ && !/^[*+]/ {print $1}'

削除対象のブランチ名が一覧表示されるので、確認してから本番のコマンドを実行すると安心です。

注意点・ハマりポイント

git fetch -pを忘れない。これを実行しないとリモートの削除がローカルに反映されず、goneとして検出されません。エイリアスにセットで登録しておくのが確実です。

mainやdevelopにいる状態で実行する!/^[*+]/で現在ブランチは除外されますが、featureブランチにいる状態で実行すると、そのブランチだけ削除されずに残ります。mainに移動してから実行する習慣をつけると漏れがなくなります。

-Dは強制削除。gone判定で対象になるブランチでも、ローカルにしかないコミット(pushし忘れた変更など)があれば消えてしまいます。心配な場合は前述の確認コマンドで対象を確認してから実行してください。

worktreeで使用中のブランチに注意+マークで除外されますが、worktreeを削除した後にブランチを消し忘れるケースもあります。git worktree listで現在のworktreeを確認してから実行すると安全です。

まとめ

この記事では、Gitのローカルブランチを一括削除する方法を解説しました。

ポイント

  1. git branch -vvのgone判定を使えば、リモートで削除済みのブランチを安全に一括削除できる
  2. GitHub PRフロー(特にsquash mergeや階層ブランチ)では--mergedよりgone判定が確実
  3. git fetch -pとセットで使い、エイリアスに登録しておくと日常的にブランチを整理できる

最終的なコマンドを再掲します。

git fetch -p && git branch -vv | awk '/: gone]/ && !/^[*+]/ {print $1}' | xargs git branch -D

コメント

タイトルとURLをコピーしました