【Git/GitHub】force pushでリモートの歴史を変える

:::note warn リモートの歴史を変えることは基本的に推奨されていません。個人開発であれば問題は少ないですが、チーム開発のリポジトリなどでは極力避けましょう :::

gitによる歴史のすり合わせ

大前提の話をします。

gitにリモートリポジトリを紐付けると、その時点で「ローカルのgitの歴史」と「リポートのgitの歴史」が出来上がります。基本的にローカルで変更を行うので、その変更をリモートに反映して、歴史を擦り合わせるのがgit pushのお仕事というわけです。

逆にgit pullはリモート側の変更を取り込んで歴史を擦り合わせるのがお仕事です。

ここで問題です

リモート側に変更があり、ローカル側でpullをすべき状態でpushを行うとどうなるでしょうか。

A. 怒られる

答えは怒られるです。

gitがリモートの歴史を見て「ん、歴史的にはローカルのほうが遅れてるな。じゃあpushはおかしい」と意見しているわけですね。

 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:malsuke/gitbra.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

このようにgitはそれぞれの歴史を見てどのように擦り合わせるのが適切か考えてすり合わせを行っているのです。

force pushで無理やりローカルの歴史を採用させる

git push -f って何

そもそもgit push -fとは簡単に言うと「黙ってこちらの歴史を採用しろ」ということです。 先程2つの歴史があると説明しましたが、強制的にローカルの歴史を正しい歴史だとして採用させます。

実際に検証

以下のようにリモートに変更を行いました。

ローカルでも変更が存在することを確認できる状態です。

$ git status    
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

ここで以下を実行します

git push -f

はい、何も変更がない状態でpushに成功しました。

Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:xxxxx
 + 2ee7788...51bf8eb main -> main (forced update)

ではリモート側を確認すると...

このように歴史が変更されていました 🎉

つまり、ローカルの歴史が採用されリモートがローカルの歴史に強制的に合わせられたというわけです。

まとめ

応用すればいろいろと便利なことができます。ただし、こういった歴史の改ざんはよくない結果を招いたりすることがあるので扱いには十分注意しましょう