git push --force-with-lease でも他人のコミットは消える:バックグラウンド fetch と --force-if-includes

rebase 後の強制プッシュで --force-with-lease が何と何を比べるのか、エディタのバックグラウンド fetch が保護を無効にする理由、Git 2.30 の --force-if-includes と期待値を明示する方法を Git 公式ドキュメントで整理します。

開発Gitバージョン管理

rebase で整理したブランチをリモートに戻すには強制プッシュが必要です。そこで --force ではなく --force-with-lease を使え、とよく言われますが、オプションを替えるだけで同僚のコミットがいつも守られるわけではありません。このオプションが何と何を比べているかを知ると、保護が効かなくなる場面が見えてきます。

この記事は Git 2.56.0(2026年9月28日)時点の git-scm.com にある git-push と git-config のドキュメントに沿っています。git-push のページが最後に変わったのは 2.55.0 です。

なぜ強制プッシュが必要になるのか

git push は通常、リモートのブランチを fast-forward でない形では更新しません。リモートのブランチの先端が、置き換えるコミットの祖先である必要があります。rebase は公開済みのコミットを新しいコミットに置き換えるので、リモートに残っている古い先端コミットは新しい履歴の祖先ではなくなります。--force や refspec 先頭の + は、このチェックを外します。

ドキュメントは --force を「履歴を失うつもりのときに使う方法」と説明し、rebase している間に誰かが元の履歴の上にコミットを積んでいたら、何も考えずに --force でプッシュするとその作業が失われると警告しています。また --force はプッシュするすべての ref に効くため、push.default が matching だと現在のブランチ以外の ref まで上書きすることがあります。1つのブランチだけを強制したいときは git push origin +feature のように refspec の前に + を付けます。

–force-with-lease が比べる値

--force-with-lease は、リモート ref の現在の値が期待値と一致するときだけ更新し、一致しなければプッシュを失敗させます。期待値をどこから取るかは書き方によって変わります。

書き方 期待値
--force-with-lease 更新する各 ref に対応するリモート追跡ブランチ(例:origin/feature)
--force-with-lease=<ref> その ref 1つのリモート追跡ブランチ
--force-with-lease=<ref>:<expect> 自分で書いた <expect>。空文字列なら、その ref がリモートにまだ存在しないこと

ドキュメントは、期待値を明示する最後の書き方以外はまだ実験的で、意味が変わる可能性があるとしています。

バックグラウンド fetch で保護が効かなくなる流れ

期待値を書かない形は、手元のリモート追跡ブランチを「自分が見たリモートの状態」とみなします。エディタや cron がバックグラウンドで git fetch origin を実行していると、この前提が崩れます。

  1. 自分が feature を rebase している間に、同僚がリモートの feature にコミット X をプッシュする。
  2. エディタがバックグラウンドで fetch し、origin/feature が X を指す。自分は X を見ておらず、rebase にも含めていない。
  3. git push --force-with-lease を実行すると、リモートの現在値と origin/feature がどちらも X なのでチェックを通り、X はリモートのブランチから消える。

ドキュメントも、この組み合わせは非常に相性が悪いと書いています。Git にはリモート追跡情報以外に判断材料がないため、バックグラウンドで ref を更新するプロセスがあると --force に対する利点がなくなる、という説明です。

防ぐ方法は2つ

1つ目は、期待値を明示することです。ドキュメントの例のように rebase 前に見た地点にタグを付けておけば、バックグラウンド fetch が origin/master を動かしても、リモートがまだそのコミットのときだけ上書きします。

git fetch                  # リモートの master を取得
git tag base master        # 自分が見た基準点に印を付ける
git rebase -i master       # コミットを書き直す
git push --force-with-lease=master:base master:master

2つ目は、Git 2.30.0 からある --force-if-includes を併用することです。このオプションは、リモート追跡 ref の先端コミットがローカルブランチの reflog のいずれかのエントリから到達可能かを確かめ、そうでなければ強制更新を拒否します。上の例では X は自分の feature のどの reflog エントリからも到達できないので、プッシュは拒否されます。

git push --force-with-lease --force-if-includes
# 毎回書くのが面倒なら
git config --global push.useForceIfIncludes true

push.useForceIfIncludes を true にすると --force-if-includes を指定したのと同じになり、プッシュ時に --no-force-if-includes で打ち消せます。注意点もあります。--force-if-includes は --force-with-lease なしで使ったときや <ref>:<expect> 形式と一緒に使ったときは何もしません。また、ドキュメントによれば --force は --force-with-lease のチェックまで無効にします。

リモート追跡ブランチの先端が自分の reflog から到達できず、--force-if-includes がプッシュを拒否した場合は、自分が取り込んでいないリモートの作業があります。書き直したりもう一度プッシュしたりする前に、fetch してその作業を確認します。ローカルとリモート追跡ブランチがどこで分かれたかを見るときの ... 記法は git log と git diff の .. と … で説明しています。

参考にしたドキュメント

リンクと内容は 2026年10月6日に確認しました。

ブログへ