初めて git rebase してみた

# はじめに
こちらの記事では、今まで GitHub Desktop に頼り切っており、最近漸く Git のコマンドに手を出した私が、rebase について勉強したこと・実際に rebase を実施した結果をメモとして残します。



# rebase とは
## Git の歴史を改変するコマンド
- 改変例
  - コミットメッセージを変更（reword）
  - コミットを修正（edit）
  - コミットを前のコミットとまとめて、コミットメッセージを変更（squash）


## メリット
- Git の歴史が綺麗になる
- `main` ブランチの最新の内容を取り込んだ上で開発を進めることができる
- 参考：[あなたはmerge派？rebase派？綺麗なGitログで実感したメリット](https://style.biglobe.co.jp/entry/2022/03/22/090000)



# rebase コマンドのオプションの一例
## `$ rebase -i`
- rebase を開始
- 各コミットに対して、どのコマンド（pick、reword、edit、squash など）を実行するか選択


## `$ rebase --continue`
- rebase を継続
- 使用例
  1. rebase 時にコンフリクトが発生し、rebase が中断
  2. コンフリクトを解決
  3. add と commit を実行し、次に作るべきコミットを生成
  4. `$ rebase --continue` を実行し、rebase を再開


## `$ rebase --abort`
- rebase を中止
- `$ rebase -i` の実行前に戻る



# rebase するときの注意点
## push 済みのブランチをリベースしない（特に、複数人で1つのブランチを共有して開発を進めている場合）
- push（リモートの `main` を fast-forward merge で更新）できなくなり、force push を強いられる
  - 参考：[なぜrebase後に強制プッシュをする必要があるのか](https://qiita.com/hitochan777/items/ea08df354b42be57e5fc)
  - force push により、他人のコミットを吹き飛ばす可能性がある
    - 対策：[git push -f をやめて --force-with-lease を使おう](https://qiita.com/wMETAw/items/5f47dcc7cf57af8e449f)
- ローカルの `origin/main` が指すコミットが、リモートの `main` に存在しなくなり、他の人が困る



# 試しに rebase してみる
[こちらの記事](https://style.biglobe.co.jp/entry/2022/03/22/090000#develop%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E3%82%92%E7%B6%BA%E9%BA%97%E3%81%AB%E4%BF%9D%E3%81%A4Git%E6%93%8D%E4%BD%9C%E3%83%9E%E3%83%BC%E3%82%B8%E7%B7%A8)がとても腹落ちしたので、記事の内容を自分で再現してみます。


## rabase するための準備
### 準備の完成形
![ex1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242339/35daf6d2-ff4d-45d0-78cf-591664e0c104.png)


### 各ブランチとコミットの作成
#### `main` ブランチ
- `root` コミットを作成
  - `$ echo "# rebase_test" > README.md`
- push する
#### `main` から `feat/1/機能A` ブランチを作成
- `c_1` コミットを作成
  - `$ echo "func_A_1" > feat_A.md`
- `c_2` コミットを作成
  - `$ echo "call feat_A" > call.md`
- push する
#### `main` から `feat/2/機能B` ブランチを作成
- `c_3` コミットを作成
  - `$ echo "func_B_1" > feat_B.md`
  - `$ echo "call feat_B" > call.md`
- push する
#### `feat/1/機能A` を `main` にマージ
- `c_4` コミットを作成
  - `feat/1/機能A` からプルリクエストを作成し、`main` にマージ


## rabase してみる
1. `feat/2/機能B` に移動し、`$ git fetch` と `$ git rebase -i main` を実行
   - 今回は `pick` を選択して終了
2. `HEAD` と `feat/2/機能B` でコンフリクトが発生
   - `call.md` を修正
   - 今回は `call feat_A` と `call feat_B` の両方を残す
3. `c_5` コミットを作成
   - ここまでで、何かに失敗してしまっていたら、`$ git rebase --abort` を実行して1に戻る
4. `git rebase --continue` を実行して rebase を再開し、rebase を完了させる
5. `1 ~ 4` の結果を確認
![ex1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242339/72ad153b-2a4b-31bb-cc92-3f4b7e26d4d7.png)


## テスト（今回はスキップ）
実際の開発では、機能Aが存在する状態で機能Bが正常に動作するか、確認する必要があると思います。


## 完成した `feat/2/機能B` をマージしてもらう
### push で失敗してみる
`feat/2/機能B` を push したいところですが、実際にはエラーが発生すると思います。
これは、push 済みのブランチをリベースしているからです。
このエラーが発生する原因について、もう少し説明します。

まず、現状では、下の画像のようにリモートの `feat/2/機能B` は、ローカルの `origin/feat/2/機能B` と同じコミットを指しています。
また、push では、リモートの `feat/2/機能B` を fast-forward merge で更新します。
しかし、下の画像から分かるように、リモートの `feat/2/機能B` が指すコミット（今回は、ローカルの `origin/feat/2/機能B` が指すコミットと同義）から、新しく追加したい `feat/2/機能B` が指すコミットまでは、親→子の順で辿ることができません。そのため、push に失敗します。
このような状況では、force push を実行する必要があります。
![ex1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242339/72ad153b-2a4b-31bb-cc92-3f4b7e26d4d7.png)


### force push してみる
push に `--force-with-lease` を追加して実行します。
下の画像のように、ローカルの `feat/2/機能B` と ローカルの `origin/feat/2/機能B` が指すコミットが一致すると思います。
![ex1-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242339/53ad66d7-d4a1-09dd-b533-8b61ff84c2a7.png)


### `feat/2/機能B` からプルリクエストを作成してマージしてもらう
`c_6` コミットを作成すると、下の画像のような結果になると思います。
確かに歴史が綺麗。
![ex1-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242339/2bbd37f6-0ab9-5e1f-d631-0f2fe1d88ca9.png)



# 実際に rebase するときに気になったこと
## rebase により発生したコンフリクトを解決した後の `$ git diff` の表示結果
### 予想していた表示結果
- リベース先のブランチのインデックスと、コンフリクト解決後のワーキングツリーの差分を表示
- `diff --git a/XXXXX.xxx b/XXXXX.xxx`

### 実際の表示結果
- コンフリクトを解決するために適用した全ての修正を表示
- `diff --cc XXXXX.xxx`
  - `-cc` とは？

### 調べて分かったこと
- rebase によるマージを実行したとき、このような表示になる
  - 参考：[Combined diff format](https://git-scm.com/docs/git-diff#_combined_diff_format)
    - `-cc` は `$ git diff` でマージを表示するときのデフォルトのオプション
    - 結合された差分を生成
- >リベース先のブランチの最新のコミット（インデックス）と、コンフリクト解決後の結果（ワーキングツリー）の差分を表示
  - 上を実現するためには、`$ git diff HEAD^ HEAD` を実行



# おわりに
この記事では、最近漸く Git のコマンドに手を出した私が、rebase について勉強したこと・実際に rebase を実施した結果をメモとして残しました。
間違い等があれば、ご指摘いだけると助かります。
初めて git rebase してみる人の参考になればと思います。