問題の提起:git push –force という悪夢
2年以上前、私たちのチームは決済ゲートウェイのリリースに向けてラストスパートをかけていました。機能ブランチを rebase してコミット履歴を整理した後、私は何気なくこう入力しました:git push origin feature/checkout --force。すべてが順調に見えました。しかしちょうど10分後、VNPay 連携を担当していた同僚が青ざめた顔で私の席に駆け込んできたのです。共有ブランチにプッシュしたばかりのバグ修正コミット4件が、跡形もなく消え去っていました。
その時の絶望感といったらありませんでした。-f(または --force)オプションは完全に盲目的に動作します。リモートに誰かが新しいコードをプッシュしたかどうかに関係なく、ローカルのブランチをサーバーへ強制的に上書きしてしまうのです。チーム全員が深夜2時まで起きてコードの復旧に追われるという苦い経験を経て、私たちはルールを改定しました。git push --force を完全に禁止し、--force-with-lease へ全面的に切り替えたのです。導入から6か月間で、このコマンドはチームの致命的なミスを少なくとも10回以上未然に防ぎました。
基本概念:–force-with-lease の動作メカニズム
基本的に、--force-with-lease もコミット履歴の書き換えを許可する点では同じです。決定的な違いは、「上書きする前にチェックを行う」という点にあります。
この仕組みは、データベースにおける楽観的ロック(Optimistic Locking)によく似ています。Git はリモートブランチの現在の参照(ref)と、ローカルマシンに保存されているリモート追跡ブランチ(例: origin/feature-branch)を比較します:
- 安全な場合: リモートサーバー上のコミットSHAが、ローカルのリモート追跡ブランチと完全に一致している場合。これは、最後に
git fetchを実行してから誰も新しいコミットをプッシュしていないことを意味します。Git は即座に上書きを許可します。 - 競合がある場合: あなたが rebase している間に、同僚がリモートへ新しいコードをプッシュしていた場合。サーバー上のSHAが一致しないため、Git はプッシュ処理を直ちにブロックし、エラー警告を出力します。
実用比較:–force vs –force-with-lease
以下の比較表を見れば、両オプションの違いが一目でわかります:
| 比較項目 | git push --force |
git push --force-with-lease |
|---|---|---|
| 上書きの挙動 | リモートの状態を完全に無視して無条件に上書き | リモートに未知の新しいコミットがない場合のみ上書き |
| 同僚のコード保護 | なし(他人のコミットを丸ごと消去する恐れあり) | あり(新しい変更を検知した時点でプッシュをブロック) |
| 事故リスク | 複数人で同一ブランチを共有する場合、極めて高い | 極めて低く、リスクを厳格に制御可能 |
実践ガイド:実際のプロジェクトでの適用手順
1. よく使う基本構文
ローカル環境でコミットを squash したり、ブランチを rebase したり、git commit --amend を実行した後は、-f の代わりに以下のコマンドを使用してください:
# 作業ブランチへ安全にプッシュする
git push --force-with-lease origin feature/payment-gateway
2. 他のメンバーがコードをプッシュした際のブロック挙動のシミュレーション
あなたと同僚の An さんが同一の feature/login ブランチで作業していると仮定します。あなたがローカルで rebase して3つのコミットを1つにまとめたちょうどその時、An さんが CSS 修正コミットを GitHub にプッシュしました:
# 安全な強制プッシュを実行
$ git push --force-with-lease origin feature/login
# Gitから返される実行結果:
To github.com:org/repo.git
! [rejected] feature/login -> feature/login (stale info)
error: failed to push some refs to 'github.com:org/repo.git'
この stale info というメッセージが表示された瞬間こそ、セーフティ機能が正常に作動した証拠です。Git はリモートのコミットがローカルのリモート追跡ブランチより進んでいることを検知し、上書きを拒否しました。
3. stale info エラーを解決する4つのステップ
このエラーが発生しても慌てる必要はありません。以下の4ステップに沿って順番に対処しましょう:
# ステップ1: リモートから最新情報をローカルへ取得
git fetch origin
# ステップ2: 同僚がプッシュした内容を素早く確認
git log --oneline feature/login..origin/feature/login
# ステップ3: リモートの新しいコミットの上にローカルブランチをrebase
git rebase origin/feature/login
# ステップ4: 改めて安全にプッシュを実行
git push --force-with-lease origin feature/login
これで双方の変更が綺麗に統合されます。誰のコードも失われることはありません。
4. 注意すべき盲点:バックグラウンドでの auto-fetch に注意
安全性が大幅に向上する --force-with-lease ですが、技術的な落とし穴が1つあります。このコマンドはあくまでローカルのリモート追跡ブランチとの比較しか行いません。VS Code や JetBrains などの IDE で定期的な auto-fetch 機能が有効になっていると、ローカルの Git がバックグラウンドで自動的に最新の参照を取得してしまいます。この場合、--force-with-lease はあなたが同僚の最新コミットを把握済みであると誤認し、通常通り上書きを実行してしまうのです!
極めて重要なブランチを扱う際は、パラメータでリモートの期待されるコミットSHAを直接指定することをおすすめします:
# リモートのコミットがSHA a1b2c3dと一致する場合のみ上書きを許可
git push --force-with-lease=feature/login:a1b2c3d origin feature/login
5. 入力を効率化する Git Alias の設定
毎回 --force-with-lease と入力するのは長くて面倒に感じるかもしれません。タイピングの手間を省きつつ良い習慣を定着させるために、~/.gitconfig にエイリアスを登録しておきましょう:
# 手軽に入力できるようエイリアスpfを設定
git config --global alias.pf "push --force-with-lease"
これ以降は、強制プッシュが必要な際にわずか数文字入力するだけで済みます:
git pf origin feature/my-branch
まとめ:小さな習慣がもたらす大きな安心
-f から --force-with-lease への切り替えは、ほんの数秒の設定で完了します。それだけで、rebase やコミット履歴の修正を行う際の圧倒的な安心感が手に入ります。軽はずみなコマンド入力ひとつで、チームメイトの一日の成果を台無しにしてはいけません。ぜひ今すぐエイリアスを設定し、この知見をチーム全体に共有してみてください。
