【検証】Git 2.56 の git branch --delete-merged、いつもの push -u 運用では1本も消えなかった

投稿 最終更新

文:株式会社佐野組 佐野 凛

環境(先に書く)

この記事の実測は、次の環境でだけ確認した。

GitHub などのホスティング側の挙動は見ていない。ローカルの Git の判定だけを測った記事だ。

触れ込み

GitHub Blog の「Highlights from Git 2.56」では、こう紹介されている。

$ git branch --delete-merged 'origin/*' 'topic-*' --dry-run

マージ済みのローカル枝を、上流と比べてまとめて消す。--dry-run を付ければ一覧を出すだけ。聞くかぎり、git branch --merged main | xargs git branch -d の置き換えに見える。

実際に8通りの枝を作って試した。

同梱ドキュメントに書いてあること

Documentation/git-branch.adoc の要点はこうだ(原文は英語)。

次の場合は消さない。

未マージの枝は「黙ってスキップ」するとも書いてある。

3つめの条件は、読んだだけでは効き方が分からなかった。実測したら、これが一番効いた。

用意した枝

すべて main から生やし、1コミットずつ積んで push した。マージは別クローンで行い、main を push している。

記号枝やったこと
Atopic-merge--no-ff でマージ。リモート枝は残す
Btopic-fffast-forward でマージ。リモート枝は残す
Ctopic-squash-keepsquash マージ。リモート枝は残す
Dtopic-squash-delsquash マージ後にリモート枝を削除し、作業側で git fetch --prune
Etopic-unpushed-workpush した分はマージ済み。その後、未 push のコミットを1つ追加
Ftopic-merged-then-del通常マージ後にリモート枝を削除し、fetch --prune
Gtopic-keep-config通常マージ。branch.topic-keep-config.deleteMerged=false を設定
Htopic-worktree通常マージ。別 worktree でチェックアウト中
Itopic-never-merged対照用。push しただけで、マージしない

これを、上流の張り方だけ変えて2通り作った。

パターン1:push -u 運用では、1本も消えない

作業側の状態はこうだ。

$ git branch -vv
* main                  ec88ac9 [origin/main] Merge topic-worktree
  topic-ff              f75151d [origin/topic-ff] topic-ff work
  topic-keep-config     5174cb5 [origin/topic-keep-config] topic-keep-config work
  topic-merge           73b2701 [origin/topic-merge] topic-merge work
  topic-merged-then-del 535e5f8 [origin/topic-merged-then-del: gone] topic-merged-then-del work
  topic-never-merged    5d9018a [origin/topic-never-merged] topic-never-merged work
  topic-squash-del      ced93ef [origin/topic-squash-del: gone] topic-squash-del work
  topic-squash-keep     2d3d3a4 [origin/topic-squash-keep] topic-squash-keep work
  topic-unpushed-work   3abeb8f [origin/topic-unpushed-work: ahead 1] unpushed local commit
+ topic-worktree        0663fa9 (../wt-topic-worktree) [origin/topic-worktree] topic-worktree work

従来の --merged は、素直に拾う。

$ git branch --merged main
* main
  topic-ff
  topic-keep-config
  topic-merge
  topic-merged-then-del
+ topic-worktree

--delete-merged は、何を渡しても無言だった。

$ git branch --dry-run --delete-merged 'origin/*'

$ git branch --dry-run --delete-merged 'origin/*' 'topic-*'

$ git branch --dry-run --delete-merged origin

$ git branch --dry-run --delete-merged '*/*'

$ git branch --delete-merged 'origin/*'

--dry-run を外した本番も、終了コードは 0 で、何も消えていない。確かにマージ済みの A と B も残った。

理由:自分の push 先を上流にしている枝は、判定のしようがない

これは不具合ではなく、先の3つめの条件だ。builtin/branch.c の branch_pushes_to_upstream() を読むと、判定はこうなっている。

  1. remote.origin.push があれば、それで push 先を求める。無ければ同名(refs/heads/topic-X)
  2. push 先を fetch refspec に通すと refs/remotes/origin/topic-X になる
  3. それが上流と同じなら、スキップ

push -u した枝は、上流が「自分を push したもの」だ。push した直後なら、先端は当然上流から到達できる。マージされたのか、push しただけなのか、区別がつかない。だから最初から候補にしない。

ちなみに push.default は判定に使っていない。push.default=nothing にしても、結果は同じ無言だった。

$ git -c push.default=nothing branch --dry-run --delete-merged 'origin/*'

ガードを外すと、何が起きるか

push refspec を別の名前へ向けると、このガードは外れる。試しに一時的に付けて dry-run した。

$ git -c remote.origin.push='refs/heads/*:refs/heads/wip/*' branch --dry-run --delete-merged 'origin/*'
Skipping 'topic-keep-config' (branch.topic-keep-config.deleteMerged is false)
Would delete branch topic-ff (was f75151d).
Would delete branch topic-merge (was 73b2701).
Would delete branch topic-never-merged (was 5d9018a).
Would delete branch topic-squash-keep (was 2d3d3a4).

一度もマージしていない topic-never-merged が、削除候補に入った。ガードが防いでいるのは、まさにこれだ。

パターン2:origin/main を追跡している枝なら、効く

同じ9本を、上流 origin/main で作り直した。

$ git branch --dry-run --delete-merged 'origin/*'
Skipping 'topic-keep-config' (branch.topic-keep-config.deleteMerged is false)
Would delete branch topic-ff (was f75151d).
Would delete branch topic-merge (was 73b2701).
Would delete branch topic-merged-then-del (was 535e5f8).

'origin/*' 'topic-*'、origin、origin/main、'*/*' も、同じ一覧になった。'topic-*' を上流側のパターンに渡すと、何も一致しない(上流を絞る引数なので当然だ)。

本番を打つと、dry-run の一覧どおりに消えた。

$ git branch --delete-merged 'origin/*'
Skipping 'topic-keep-config' (branch.topic-keep-config.deleteMerged is false)
Deleted branch topic-ff (was f75151d).
Deleted branch topic-merge (was 73b2701).
Deleted branch topic-merged-then-del (was 535e5f8).

後ろに 'topic-m*' を付けた回は、topic-merge と topic-merged-then-del だけが消えた。消えた枝の branch.<name>.* 設定も一緒に消えていた。

ただし、この運用には代償がある。上流と名前が違うので、既定の push.default=simple のもとでは引数なしの git push が拒否される。

$ git push --dry-run
fatal: The upstream branch of your current branch does not match
the name of your current branch.  To push to the upstream branch
on the remote, use

    git push origin HEAD:main

(以下、案内文が続く。全文は検証ログにある。)

結果の一覧

記号枝パターン1(上流=同名)パターン2(上流=origin/main)--merged main
Atopic-merge残る消える出る
Btopic-ff残る消える出る
Ctopic-squash-keep残る残る出ない
Dtopic-squash-del残る残る出ない
Etopic-unpushed-work残る残る出ない
Ftopic-merged-then-del残る消える出る
Gtopic-keep-config残る残る(Skipping と表示)出る
Htopic-worktree残る残る出る
Itopic-never-merged残る残る出ない

引っかかった点

squash マージは拾わない。C も D も、パターン2でさえ残った。main にある squash コミットは枝の先端を含まないので、到達可能の判定に乗らない。これは --merged main も同じだ。GitHub の「Squash and merge」で運用しているリポジトリでは、この機能で枝は減らない。

上流が消えた枝も拾わない。F は本当にマージ済みだが、パターン1では fetch --prune で上流が gone になった時点で対象から外れる。ドキュメントの「上流の ref が存在しない」だ。GitHub の「マージ後に枝を自動削除」と組み合わせると、まさにこの形になる。

スキップ理由は、ほぼ表示されない。表示されたのは deleteMerged=false の1行だけだった。worktree 使用中、上流消滅、push ガード、未マージは、どれも無言で飛ばす。パターン1のように dry-run が空だと、なぜ空なのかは自分で調べるしかない。

まとめ

git branch --delete-merged は「main にマージ済みか」ではなく、「その枝が追跡している上流に取り込まれたか」を見る。

手元がいつもの push -u 運用なら、当面は git branch --merged main を見て git branch -d で消すほうが早い。数字と挙動は Git 2.56.0 のローカル実測で、ホスティングの設定や将来の版では変わり得る。

出典

© 2026 株式会社佐野組