`git push -f` ではなく `git push --force-with-lease --force-if-includes` を使おう

`git push -f` ではなく `git push --force-with-lease --force-if-includes` を使おう

基本的に git push -f(または git push --force)は使わない方がよいですが、開発しているうえで必要な場面はあります。例えば以下の場面です。

  1. main ブランチから my-feature ブランチを作成し、開発ブランチで新たにコミット(X)を追加してリモートにプッシュしました。
  2. その間に main ブランチに他の人がコミット(C)を追加しました。
  3. 最新 main ブランチを my-feature ブランチに取り込むために git fetch origin && git rebase origin/main を実行しました。

このとき、ブランチのコミット履歴は次のようになります。

A---B---C     origin/main
     \   \
      \   X'  my-feature(ローカルブランチ)
       \
        X     origin/my-feature(リモートブランチ)

リベースしたため、X のコミットは C の次に移動し、X’ という新しいコミットになっています。

この時点でリモートブランチ(A---B---X)とローカルブランチ(A---B---C---X')のコミット履歴が異なるため、通常の git push は拒否されます。

そのため -f をつけて強制的にリモートブランチを上書きする必要があります。

git push -f で事故が起こる例 #

基本的に my-feature ブランチを触るのがあなた1人だけであれば事故は起こりません。もし他の人と my-feature ブランチを共同開発している場合は事故が起こる可能性があります。

例えば、リベース中に他の開発者がリモートの my-feature にコミット Y を追加していたとしたら、あなたの git push -f によってそのコミットは消えてしまいます。

git push --force-with-lease --force-if-includes で事故を防ぎつつ強制プッシュする #

さきほどのような万が一の事故を防ぐためには git push --force-with-lease --force-if-includes で強制プッシュするのがよいです。

この2つのオプションを組み合わせることで、リモートブランチが想定外に更新されていないか、さらにその更新をローカルで取り込んでいるかを確認したうえで、強制プッシュできます。

--force-with-lease #

--force-with-lease は「自分のローカルのリモート追跡ブランチ」と「実際のリモートブランチ」を比較し、両者が同じコミットを指している場合にだけ強制プッシュを実行します。もし他の人がリモートブランチにコミットを追加していた場合、プッシュは拒否されます。

先ほどの例では、あなたのローカルのリモート追跡ブランチ origin/my-feature の最新コミットが X のままなのに、実際のリモートが Y まで進んでいればプッシュは拒否されます。

--force-if-includes #

先の状態で git fetch を実行すると、ローカルのリモート追跡ブランチ origin/my-feature は Y に更新されます。この場合 --force-with-lease の比較は成功します。ローカルのリモート追跡ブランチとリモートブランチの状態が一致しているためです。

しかしこのままプッシュしてしまうと、やはりリモートの Y のコミットが消えてしまいます。

--force-if-includes を追加すると、そのリモートの更新をローカルで取り込んだ形跡があるかも確認します。取り込んでいなければ、強制プッシュは拒否されます。