HOWTO · Git

Git Push --force-with-lease と Git Push --force の比較

この記事では、git push--force-with-lease コマンドと gitpush--force コマンドの違いについて説明します。

このページの内容

リベースや amend によって、すでに公開したコミットが変わると、リモートブランチがローカルブランチから早送りできなくなるため、通常の push は拒否されることがあります。git push --force と git push --force-with-lease はどちらも、リモートブランチの先端を置き換えられます。違いは、更新前に Git がリモートの期待値を確認するかどうかです。意図的に履歴を書き換える場合は lease 付きの形式を使い、push が拒否されたらリモートの状態を確認して調整してください。

git push --force の動作

通常の push では、ブランチを先に進める更新だけが許可されるため、リモートのコミットが保護されます。git push --force は、push 先の ref に対するこの保護を無効にします。リモートブランチは指定したコミットに設定され、表示される履歴からコミットが取り除かれる場合もあります。チームメイトが追加した変更を取り込んでいなければ、その変更はブランチからたどれなくなる可能性があります。

このコマンドの対象は、指定した push 先によって決まります。たとえば git push --force origin feature は、origin の feature ブランチを対象にします。ブランチ名を明確に指定しないと、どの ref が影響を受けるのか分かりにくくなることがあります。ブランチの履歴を置き換えることが意図的であり、そのブランチを使う全員と調整できている場合に限って force push を使ってください。force push はマージ競合を発生させず、リモートのコミットを自動で統合することもありません。

git push --force-with-lease の動作

git push --force-with-lease origin feature も早送りでない更新を許可しますが、その前にリモートブランチが期待した値のままか確認します。一般的な形式では、Git は origin/feature のようなローカルのリモート追跡 ref からその期待値を取得します。サーバー上のブランチの値が異なる場合、Git は置き換えずに更新を拒否します。

この確認は役立ちますが、誰も未確認の変更を push していないことの証明にはなりません。最後に確認した後、バックグラウンドの fetch が origin/feature を更新すると、デフォルトの lease は、その新たに取得された値を基準に履歴の書き換えを受け入れることがあります。期待するオブジェクト ID を明示すれば、比較対象を意図して指定できます。例: git push --force-with-lease=refs/heads/feature:<expected-object-id> origin HEAD:refs/heads/feature。チームの作業手順に沿って期待値を取得し、内容を確認してください。何を指すのか確かめずに ID をコピーしないでください。

履歴を書き換えた後に --force-with-lease を使う

共有ブランチが A---B---C で、共同作業者が C の上に D を追加し、あなたがローカルの作業をリベースして A---B---C' を作ったとします。この場合、ローカルの C' とリモートの D は異なる先端を指しています。期待するリモートの状態がまだ C なら、lease によって D を気付かずに置き換える事態を防ぐことが意図されています。

まずブランチを確認し、fetch で現在の状態を取得します。次に、共同作業者のコミットを履歴を書き換えたブランチに含めるべきか判断します。取得したブランチを基に作業をリベースする方法や、チームと別の統合方法を調整する方法があります。履歴を解決して確認したら、意図した内容を lease 付きで push してください。push が lease によって拒否された場合は、再度 fetch して確認します。すぐに --force で再試行しないでください。

次の自己完結型のデモでは、ローカルブランチを作成し、一時的な bare リポジトリに push してから、先端を amend し、lease を使って更新します。

set -e
root="$(mktemp -d)"
trap 'rm -rf "$root"' EXIT

git init --bare "$root/remote.git" >/dev/null 2>&1
git init -b feature "$root/work" >/dev/null 2>&1
cd "$root/work"
git config user.name "Example Developer"
git config user.email "developer@example.com"
printf 'first version\n' > README.md
git add README.md
git commit -m "Add first version" >/dev/null 2>&1
git remote add origin "$root/remote.git"
git push -u origin feature >/dev/null 2>&1
git commit --amend -m "Rewrite first version" >/dev/null 2>&1
git push --force-with-lease origin feature >/dev/null 2>&1

パスは一時的なもので、スクリプトの終了時にテスト用の環境は削除されます。実際のリポジトリで共有履歴を置き換える前に、コミットを確認し、共同作業者と調整してください。

force push の方法を安全に選ぶ

共同作業者がいない個人用ブランチなら、リモートの先端を置き換える意図がある場合、直接 force push しても差し支えないことがあります。共有ブランチでは、期待するリモートの状態を確認し、履歴の書き換えについて調整したうえで --force-with-lease を使ってください。共有ブランチの履歴を書き換えずに済むなら、プルリクエストなどのレビュー手順を優先します。

リモートとブランチを明示し、判断する前に fetch して、push 前にコミットグラフを確認してください。lease による拒否は安全のための合図です。ローカルブランチと fetch したリモートブランチを比較し、必要なコミットを保持したうえで、それらをどう扱うか決めてから再試行してください。定期的に同期すると予期しない事態を減らせますが、push する履歴の確認に代わるものではありません。

失われたコミットの復旧と予防

force push によってブランチの先端からコミットが取り除かれた場合は、後片付けの変更を加えるのをやめ、残っている参照を探してください。ローカルの clone では、git reflog に最近のブランチ位置が記録されていることがあります。共同作業者の clone、リモートのバックアップ、ホスティングサービスの復旧機能にコミットが残っている場合もあります。コミットが見つかったら、復元や統合の方法を決める前に、そのコミットを指すブランチを作成してください。reflog の記録は期限切れになり、ローカルにしかないため、復旧できるとは限りません。

共有ブランチの履歴を書き換えられる人を決め、更新前に共同作業者へ知らせ、書き換えをレビューする必要がある場合は既知の参照を保存して、コミットの消失を防ぎましょう。lease は誤った上書きの一部を減らします。明確な連携と確認済みの復旧用参照は、lease では検知できないリスクに対処します。

FAQ

git push --force と git push --force-with-lease の違いは何ですか? 前者は現在の値を確認せずにリモート ref を置き換えます。後者は期待値を確認し、リモートが一致しない場合は更新を拒否します。

git push --force はいつ使えますか? 対象ブランチの先端を置き換える意図があり、どのコミットがブランチから外れる可能性があるか理解し、影響を受ける共同作業者と調整できている場合に限って使ってください。共有作業では lease を優先し、期待値を確認してください。

共有ブランチへの force push は危険ですか? ブランチからコミットが見えなくなり、共同作業者の作業に支障が出る可能性があります。lease はリモートが期待値と異なる場合に役立ちますが、確認していない fetch によってデフォルトの期待値が更新されることがあります。

競合や予期しない事態を減らすにはどうすればよいですか? リモートを fetch して確認し、必要に応じて共同作業者のコミットを履歴を書き換えたブランチに統合し、計画を共有してください。共有変更にはレビュー手順を利用します。

force push を取り消せますか? 自動で取り消す方法はありません。reflog、別の clone、またはホスティングサービスの復旧機能から以前のコミットを見つけられる場合があります。その場合は参照を作成し、復元について関係者と調整してください。復旧できるかどうかは、そのコミットを指す参照が残っているかによります。