【Git】簡単！コミット履歴の壊し方💥🤛【やめろ】

皆さんこんにちは。
withでiOSエンジニアをしている @zrn-ns です。

皆さんは `git rebase` コマンドを使ったことはありますか？

過去を改変する便利で強力なツールである `git rebase` コマンドですが、これを使ったことがある人であれば誰しも一度はその力に溺れ、**gitリポジトリを壊した経験をお持ちでしょう**。

思い出してみてください。「あれ、自分のPRに関係ない別の人のコミットが混ざっている...」なんて経験、ないですか？

Gitリポジトリを壊してしまったとき、最初のうちはなぜ壊れたのか分からず混乱すると思います。

今回は僕の知っている**Gitのリポジトリの壊し方と、その防ぎ方**を皆様にお伝えします。

:::note warn
注意
お気付きかと思いますが、この記事はGitリポジトリを破壊するためのものではなく、誤った操作を学び、同じ過ちを繰り返さないようにするためのものです
:::

# サンプル

分かりやすいよう、今回はコミット履歴のサンプルとして `base` ブランチと、そこから派生した `feature` ブランチを用意しました。
![Untitled (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/a78446ba-2772-99ad-f3ba-755582a8fb10.png)
ここでbaseブランチに1コミット追加し、
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/bc2de42d-d931-626a-2825-a2f2d688458a.png)
その後 `base` を `feature` にマージする、という流れを考えます。
現状は特に問題なくマージができます。
![Untitled (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/7d68c02f-d86d-b985-429e-c88f856c1bf4.png)

# コミット履歴の壊し方

基本的な考え方として、ベースブランチから派生ブランチを切って作業をする場合、 **ベースブランチ側の履歴を改変すると、ベースブランチと派生ブランチの間で整合性が取れなくなり、コミット履歴が壊れます**。

実際に履歴が壊れるパターンを2つ見てみましょう。


## 破壊パターン1. ベースブランチ上のコミットを変更する

`feature` ブランチが切られた状態で `base` ブランチの履歴を改変してしまうと、コミット履歴が壊れます。

`base` ブランチをチェックアウトし、ブランチが分岐しているコミット `7c8da09` を `git rebase -i` によって変更してみましょう。

![Untitled (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/fc4a542c-ebf3-a151-9bc2-feebbe76f84c.png)
すると、このように `base` ブランチのコミット履歴が書き換わります。
![Untitled (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/957aabc5-6e9b-7d8c-1427-913470a7a3ce.png)
**改変したコミット `7c8da09` は無くなるのかと思いきや、しっかり `feature` ブランチには残っています。**

この状態で `feature` ブランチに `base` ブランチをマージしようとすると、 以前 `base` ブランチに含まれていた `7c8da09` と、それを改変した `53dee4f` の両方が履歴に含まれた状態になります。
![Untitled (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/0198f6ee-3fe1-8457-1ee1-dcc7526979a4.png)


## 破壊パターン2. ベースブランチ上のコミットを削除

パターン1ではコミットを編集していましたが、コミットを削除した場合はよりわかりやすく履歴が壊れます。
![Untitled (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/3ea3920b-3d23-a32d-9444-f44d78a8d9ce.png)
![Untitled (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/6599a462-88cc-7bc0-c0dc-850fd31af447.png)
![Untitled (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95499/c2d8929a-e291-c2ab-9271-6e43656ce15f.png)
`base` ブランチ側でコミット `7c8da09` は削除したはずですが、マージしたときに意図せず復活してしまっています。


# 業務上よくある破壊手順

業務上よくあるパターンとして、以下のような手順を踏んでコミット履歴を壊すことが多いです。

- push済みの自分のfeatureブランチのコミット履歴を綺麗にするため、featureブランチで `git rebase -i` してforce-pushしたところ、自分のfeatureブランチから別のfeatureブランチを切っている人がおり、その人のブランチを壊してしまった
- 自分のブランチにdevelopやmasterをマージしたあと、 `git rebase -i` でマージコミットよりも前のコミットを変更してしまった（今回は詳しく解説しませんが、[こちら](https://chaika.hatenablog.com/entry/2020/09/07/083000)が詳しいです）

深く考えずに履歴改変している方。気づかないうちに他のメンバーのコミット履歴を壊しているかもしれません。


# コミット履歴破壊の予防策

以下のような点に気をつければ、コミット履歴の破壊を防ぐことができます。

- `main` や `master`, `develop` ブランチはGithub上で保護設定を行い、force-pushを行えないようにする
    - feature等のブランチについてforce-pushするにしても `--force-with-lease` オプションを付け、ブランチに対する他のメンバーによる変更に気づけるようにする
- リモートにpushした（他の人から参照可能になった）ブランチ上のコミット履歴は原則改変しない
- 他の人がpushしたfeatureブランチを取り込まない
- マージコミット後は、マージコミット以前のコミットは原則改変しない

チームにgitに不慣れなメンバーが多い場合は、一律でforce-pushを禁止してしまうというのも一つの手です。

# 最後に

コミットログを綺麗に保つには必須と言ってもいい `rebase` による履歴改変ですが、不慣れなうちはコミット履歴を破壊しがちです。
自他のブランチのコミット履歴を破壊して迷惑をかけないよう、注意していきたいですね。


# PR

withではエンジニアの採用を行っています。
興味があれば、ぜひお問い合わせください！

https://enito.co.jp/careers/