ユースケースで理解するgit rebase コマンドの基本

## はじめに

エンジニアになって1年ちょっと。流石にGitにも慣れてきた！と思ってました。
つい2週間前までは・・・

これまでコミットは少ないよりも多い方がリバートしやすくて良い、と思っていたのですが
**どれがあなたが対応した内容なのかわからない**、**細かすぎて見づらい**、と言われました。 :cry: 

わかりづらいので、`rebase`してからプッシュしてくださいと言われたのですが
なんだそれ、の状態・・・

`rebase`する際に使うコマンドは調べればたくさんヒットするのですが、
どういうコマンドで、なぜこれを使って、使うとどうなるのかがいまいち理解できませんでした。
rebaseは今までコミットしてきた内容を改変することができ、初心者が使うにはハードルが高いコマンドの印象だったのでよくわからないのに使いたくありませんでした。

そんなとき、最近とある本を読んでやっと理解した気になったので、それについて書きます。

## 説明しないこと

この記事では、rebaseコマンドのユースケースについて説明することが目的のため、
コマンドのオプションなどはあまり説明致しません・・・ :bow:

## `rebase`とは

英単語の意味を覚えてから技術的な意味を理解するのが、個人的な用語の覚え方です。
[こちら](https://www.definitions.net/definition/rebase#:~:text=of%20a%20denture.-,rebaseverb,the%20final%20meaning%20is%20unchanged.)によると、rebaseは動詞で3つの意味があるそうです。

> 1. To replace the base of a denture.（入れ歯を取り替えること）
> 2. To modify core data from which other data is derived in such a way that the final meaning is unchanged.（最終的な意味が変わらないように、他のデータが派生するコアデータを変更すること）
> 3. To change the base address of.（ベースアドレスを変更します。）

１でないのは明らかですね。:sweat: おそらく２ですかね。

大体理解できたところで、`git rebase`コマンドを打つときの意味を調べてみると

> ブランチの親を変える

ということでした。（書籍「[独習Git](https://www.amazon.co.jp/%E7%8B%AC%E7%BF%92Git-%E3%83%AA%E3%83%83%E3%82%AF%E3%83%BB%E3%82%A6%E3%83%9E%E3%83%AA/dp/4798144614)」より）

これだけではよくわからないので、実際にユースケースを見ながら使い方を理解したいと思います。

## ユースケースは２つ

`rebase`コマンドを使いたい場面というのは、主に２ケースです。

先ほどrebaseに関する記事はたくさんあると書いたのですが、ユースケースが２つあることを書いている記事が日本語では多くない気がしました・・・


### 1. 上流に追いつく

英語の意味や「ブランチの親を変える」という意味に沿っているのは、こちらのユースケースですね。

> 上流の変化に対応する

ために使用する場合。（書籍「独習Git」より）

では具体的な例を考えてみます。

あなたは複数人の開発者が関わる開発に関わっています。
ブランチの運用は以下のようになっています。

|ブランチ名　　　　　　　|用途|
|----------------------|---------------|
|main|developブランチからマージされる。基本直接プッシュはしない。|
|develop|feature/<タスク名>ブランチからマージされる。基本直接プッシュはしない。
|feature/<タスク名>|developブランチから切って作成されるブランチ。作業は基本的にこのブランチで行う。|

よくある運用だと思います。
実際はもっと細かくあると思いますが、本題ではないので詳しくは踏み込みません。

では以下のような場面を考えてみます。

① あなたは、`develop`ブランチから作成した、`feature/<タスク１>`ブランチで作業をしています。
また、開発者Bさんも`develop`ブランチから作成した、`feature/<タスク２>`ブランチで作業をしています。

![rebase_explanation_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401966/924698ab-3f01-afb8-8e52-fb776d19a7e9.png)


② Bさんはタスク2の実装が完了したので、`develop`ブランチへのプルリクエストを出しました。
見事レビューが通り`develop`ブランチにマージされました。

![rebase_explanation_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401966/77e7fdae-c7f0-762c-5da0-40f536962612.png)

③ 幸運なことに、BさんはAさんの担当でも実装予定だった共通処理を実装してくれていました。
それを知ったあなたは、当然それを使って実装したくなりました。

**ここで登場するのが、`git rebase`コマンドです！**
上流ブランチの変更に追いつきましょう。
ここでいう上流ブランチとは、`feature/<タスク1>`ブランチの作成元になったブランチのことです。つまり`develop`ブランチのことですね。

まずローカルのdevelopブランチを最新にします。

```shell
# developブランチに移動
git checkout develop
# ローカルのdevelopブランチを最新に
git fetch
git merge develop
```

では、早速作業中の`feature/<タスク1>`ブランチに移動して、`git rebase`コマンドを実行します。

```shell
# feature/<タスク1>ブランチに移動
git checkout feature/<タスク1>
# 上流ブランチの変更に追いつく
git rebase develop
```

こうすることで、ツリーは以下のようになります。

元々、コミットYの時点の`develop`ブランチから切っていましたので、あなたのブランチはそこから分岐していました。
`rebase`コマンドを実行したことで、Bさんによって最新の更新されたコミットDから分岐するように、**元が変わりました**。

![rebase_explanation_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401966/33a6ac12-73c8-dbbc-08d9-e1eb55fae376.png)

ちなみになぜコミットAとBにダッシュがついているかというと、
コミットのハッシュ値つまり、`git log`した時に最初に表示される値が変わっているからです。

こうすることで、Bさんが実装した共通処理をあなたも利用できるようになりました！

これが1つ目のユースケースです。

**ブランチの親を変更することで、上流ブランチの変更を取り込みます。**


ここでこう思った人もいるのではないでしょうか？

「`git merge`コマンド」と何が違うのか？
私はmergeとrebaseの違いは、**コミットログを綺麗に保ちたいかどうか**、がもっとも大きな違いだと理解しています。

違いについては、いろんな記事で紹介されているので、説明しませんが、リンクだけ載せておきます。
[ブランチの統合](https://backlog.com/ja/git-tutorial/stepup/04/)
人によってわかりやすい説明は違うと思いますが、私はこれがわかりやすかったです！

よく`rebase`コマンドはコミット履歴を書き換えるため、使うなと言われたりします。私もちゃんと調べるまではヤバいコマンドという印象が強かったです。
ただ危険なのは、**以下を守らなかった場合の話**だと思っています。

* 自分の作業ブランチでのみ実行する（ここでは、`feature/<タスク名>`ブランチ）

つまり**他の人と共有しているブランチで実行しない（ここでは、`main`や`develop`ブランチ）**ようにすればいいのです。

**これを守って使えば、`rebase`コマンドは、コミットログを綺麗に保つのに役立ちます。**
それによってレビュワーやその他の人がコミットログの確認をしやすくなります。


### 2. 履歴を整理する

２つ目のユースケースは、**コミットログを整理する**時です。

ではよくあるであろう具体的な場面を考えてみます。

① あなたは`feature/<タスク１>`ブランチでの実装を終えたので、リモートにプッシュしました。
いつも通りプルリクエストを作成し、Bさんにレビューをお願いします。

② Bさんがプルリクエストを確認してみると、
たった１行の変更ですが、コミットを３回も行っていることがわかりました。（ここではあえて極端な例を出しています）
さすがにコミットをわけすぎです。そこでBさんはあなたに**コミットをまとめてから、再度プッシュ**するようにお願いしました。

![rebase_explanation_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401966/a0c0ff07-7589-a2a7-31ba-626596a9fe12.png)


**ここで`git rebase`コマンドを使います！**

具体的には、コミットA'、コミットB'、コミットEの3つを1つのコミットにまとめます。

とここで、前の例に沿うとコマンドの紹介をするところですが、
コマンドの実行方法については、以下の記事が大変詳しいですので、そちらの方の記事をご確認ください。:bow:
[git rebase についてまとめてみた](https://qiita.com/KTakata/items/d33185fc0457c08654a5)

ここでは、コミットをまとめたいので、`squash`または`fixup`を使います。

コミットメッセージを破棄するかどうかで使うコマンドを決めます。

そうすると、コミットが１つにまとまり、ツリーは以下のようになります。

![rebase_explanation_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401966/07fc3797-278b-53af-30b0-5fed9cb9d4cf.png)

めでたくコミットが１つにまとまりました！

では再度プッシュしましょう。
とここで注意です。今回は、既にプルリクエストを出した後にコミットをまとめたいので、

リモートの履歴を書き換えることになります。

その場合、`--force-with-lease`オプションを忘れずに付けましょう。

そもそも`rebase`コマンドは、1つ目のユースケースでも２つ目のユースケースでも、リモートにプッシュ前に行うのが原則だと思います。
**まだリモートにプッシュしていない場合は、上のオプションは使いません。**

ただそうもいかない場合があります。それが２つ目のユースケースのような場合です。

そういった場合は、以下のようなコマンドを実行しましょう。

```shell
git push --force-with-lease origin feature/<タスク1>
```

このオプションについては、以下がわかりやすいです。
[git push -f をやめて --force-with-lease を使おう](https://qiita.com/wMETAw/items/5f47dcc7cf57af8e449f)

引用させていただくと

> PUSHの際、ローカルrefとリモートrefを比較しローカルが最新か判定し、最新でなければPUSHが失敗するというもの。

というものです。**間違っても`-f`は使ってはいけません。**

ただし、`--force-with-lease`オプションを使う場合も、以下に注意です。

> ※ただし、直前にfetchしているとPUSHが成功してしまうので注意

**直前にfetchしないように！**です。
`-f`を使用した場合と結果が同じになってしまいます。つまり他人の実装を吹き飛ばす可能性があります・・・

以上で、`rebase`コマンドの２つ目ユースケースの説明は終わり・・・にしたいですが、
引っかかっていることがあるかもしれません。

最初に書きましたが、2つ目のユースケースは「**履歴を整理する**」です。
**整理、というからにはコミットをまとめる以外のことも行うことができます。**

ただ、今回はそれについては詳細は割愛します。
最もよく使うケースは、コミットをまとめるだと思います。

一応、何ができるかを列挙すると
> * 過去のコミットをまとめる（今回説明した内容）
> * 過去のコミットを削除する
> * 過去のコミットを修正する
> * コミットメッセージをまとめて変更する

だそうです。先程貼った記事から引用させていただきました。
他にもできることがあるようですが、大体列挙した中にあるのではないでしょうか？

## まとめ

以上、`rebase`コマンドについて自分なりに整理してみました。

コマンド自体やオプションについて詳しく説明してくださっている方はたくさんいたのですが、
なぜこれを使うのか、を中心に説明してくださっている記事があまりなかった気がしたので、調べながらこの記事を書いてみました。

gitを使い慣れている人にとっては、当然な話とは思うのですがこの記事を書くことで自分の理解も深まりました。
`rebase`コマンドを初めて使おうとしている人の参考になれば幸いです。:blush:

まだまだgitに関しては知らないことも多いと思っているので
誤り等ありましたら、コメントで教えていただけると大変ありがたいです。