Gitのマージ、リベース再学習メモ

散々記事として書かれている内容ですが Git におけるマージ、リベース関連の話です。
車輪の再発明な感じはしますが、ツール操作の再学習もかねて試したのでそのメモとして残します。

お断り。
コマンドのサンプル以外では、マージ、リベースのようにカタカナで記載しています。

## 準備

### 初期状態

履歴のイメージ。
![pic01.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/e089cd89-cb74-d327-c11e-1067dc6de649.png "初期状態")

**補足**
矢印の方向が上記のよう描いているのは、各コミットは親のコミット ID を持っているからです。

### topic ブランチ作成

```shell
git branch topic
```
履歴のイメージ。この時点では、main ブランチ、topic ブランチ共に `C2` を指している。
![pic02.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/2ddd270c-163f-4bd2-9da8-428d472353f4.png)


### main ブランチでコミット

履歴のイメージ。main ブランチは `C3` を指している。
![pic03.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/8dbfccb2-c640-bafb-b5be-71f692b0b244.png)

### topic ブランチでコミット

履歴のイメージ。topic ブランチは `C4` を指している。
![pic04.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/efac4ffe-31f5-4ade-b093-1db2723592c9.png)
アプリでの見え方。GitKrakenでは main ブランチが枝分かれしたように見えますが、この辺はツール依存です。

| GitKraken | Fork |
|-----------|------------
|![pic04k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/abc08794-ba4e-b9df-0428-b406bec63720.png)| ![pic04f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/26349b4c-eeb1-9445-9498-5b95f676dd13.png)|

いったん、マージする前の準備ができました。

## 変更を取り込む

topic ブランチ作成後に main ブランチが変更されています。複数人で共同開発している場合、変更内容を topic ブランチに取り込んでテストし、main ブランチにマージします。

ここでは2種類の方法を記載します。

### topic ブランチをリベースする

`C2` から topic ブランチを作成しましたが、これを `C3` から topic ブランチを作成したようにするのがリベースです。

```shell
git checkout topic
git rebase main

# もしくは
git rebase main topic
```
履歴のイメージ。
![pic06.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/508436c2-3ad4-4968-d6a3-6e25625ac915.png)

この絵では枝分かれしたよう描いていますが、実際は一直線に並ぶイメージです。
説明はじめの方で main ブランチで `C3` を作成したときに似ています。
![pic07.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/84f9dadd-1e05-6632-7adc-6c3806451b5c.png)

リベース前後の履歴イメージ比較。コミット `C4` の親が `C2` から `C3` に替わりました。

| リベース前 | リベース後 |
|-----------|------------|
|![pic04.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/efac4ffe-31f5-4ade-b093-1db2723592c9.png)|![pic07.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/84f9dadd-1e05-6632-7adc-6c3806451b5c.png)|

アプリでの見え方。

| GitKraken | Fork |
|-----------|------------|
|![pic07k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/96254b3c-a440-9cac-5661-364c34ece506.png)|![pic07f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/64400ada-44b3-73a5-271d-42b79a6e663f.png)|

これをマージするとどうなるでしょう。

### チェリーピックで変更内容を取り込む

他のブランチの特定のコミットだけを取り込む仕組みがチェリーピックです。

履歴のイメージ。新しいコミットが作成されますが、変更内容は `C3` と同じ。
![pic102.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/14105681-f6d4-c44b-5fc9-3914d0f24437.png)


アプリでの見え方。画像内の `C3` などはコミットコメントで、コメントもそのまま新しいコミットとして作成されていることが分かります。

| GitKraken | Fork |
|-----------|------------|
|![pic10k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/1b30b9fa-f39b-da9c-77e1-762aa3c51cb7.png)|![pic10f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/3d6bf583-c7bb-e910-e04b-e3401effa399.png)|

これをマージするとどうなるでしょう。

## マージする

変更取り込み有無やマージの方法で履歴がどう変わるでしょうか。

### 変更を取り込まずにマージする

通常テスト後にマージするため、topic ブランチに変更を反映しないことはないと思いますが、直接マージできるケースの例です。

```shell
git checkout main
git merge topic
```

履歴のイメージ。main ブランチはマージによって新しいコミット `C5` となります。
![pic05.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/bc3b1a64-d31a-269e-0e42-541ab1ffd565.png)

マージ前後の履歴イメージの比較。

| マージ前 | マージ後 |
|-----------|------------|
|![pic04.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/efac4ffe-31f5-4ade-b093-1db2723592c9.png)|![pic05.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/bc3b1a64-d31a-269e-0e42-541ab1ffd565.png)|
アプリでの見え方。

| GitKraken | Fork |
|-----------|------------|
|![pic05k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/ec8bd1da-8707-d89a-093b-bd835a906519.png)| ![pic05k2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/a89d5a7f-4409-5c5c-f140-5ce39a567e3d.png)|

### リベース後にファストフォワードなしでマージする

通常 Git はファストフォワードありでマージしようとするのですが、先にファストフォワードなしの例を示します。

topic ブランチを main ブランチに対してマージする際、 `--no-ff` オプションを付けるとファストフォワードなしのマージとなり、コミットを作成してくれます。

```shell
git checkout main
git merge --no-ff topic
```
履歴のイメージ。`C3` から分岐した `C4` が `C5` としてマージされます。 

![pic08.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/ace89bf0-0d75-0fbb-196d-a0a9e66d3fea.png)
マージ前後の履歴イメージの比較。

| マージ前 | マージ後 |
|-----------|------------|
|![pic07.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/84f9dadd-1e05-6632-7adc-6c3806451b5c.png)|![pic08.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/ace89bf0-0d75-0fbb-196d-a0a9e66d3fea.png)|

アプリでの見え方。

| GitKraken | Fork |
|-----------|------------|
|![pic08k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/7cd9fbd0-f3fa-594b-ddc3-747bfcedc4d7.png)|![pic08f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/796c815d-3e5c-3b7c-3349-a2dbe4010157.png)|

リベース有無によるForkの履歴の見え方を比較してみます。

| リベースなし | リベースあり |
|-----------|------------|
|![pic05k2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/a89d5a7f-4409-5c5c-f140-5ce39a567e3d.png)|![pic08f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/796c815d-3e5c-3b7c-3349-a2dbe4010157.png)|


### リベース後にファストフォワードありでマージする

こちらが Git デフォルトの動作です。

先ほどとは異なり `--no-ff` オプションを付けなかった場合、main ブランチにはコミットは作成されず、指し示すコミットだけが変更されます。

```shell
git checkout main
git merge topic
```
履歴のイメージ。
![pic09.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/ba64e4b8-4dde-d7fb-39d4-3217d2ed6576.png)

main ブランチが指し示すコミットを先に進めるだけの処理が「ファストフォワード（早送り）」となります。
マージ前後の履歴イメージの比較。

| マージ前 | マージ後 |
|-----------|------------|
|![pic07.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/84f9dadd-1e05-6632-7adc-6c3806451b5c.png)|![pic09.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/ba64e4b8-4dde-d7fb-39d4-3217d2ed6576.png)|

アプリでの見え方。

| GitKraken | Fork |
|-----------|------------|
|![pic09k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/acb4cad1-3e0e-f445-8846-f524816a344e.png)|![pic09f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/d5234c09-7421-9449-c018-150f87936dc7.png)|

アプリでも履歴のイメージ図のような見え方になっています。

| マージ前 | マージ後 |
|-----------|------------|
|![pic07f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/64400ada-44b3-73a5-271d-42b79a6e663f.png)|![pic09f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/d5234c09-7421-9449-c018-150f87936dc7.png)|

ご覧の通り、topic ブランチで作成した痕跡が残らないため、私はリベースするなら `--no-ff` オプションを付ける運用にしています。

### チェリーピック後にマージする

最後に、チェリーピックを行ったときのマージです。

履歴のイメージは、ごく普通に新しいコミット `C6` が作成されてマージされたものとなります。
![pic11.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/88e74e36-ab53-2d3d-bad4-da665e7a7160.png)

チェリーピックしたときにコンフリクトが発生していなければ、コミット `C6` の変更内容はコミット `C4` と同じになります。これはコミット `C3` と `C5` は変更内容が同じのためです。

アプリでの見え方。

| GitKraken | Fork |
|-----------|------------|
|![pic11k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/1d5a661e-12ba-e915-a4e9-6f31fb0dd001.png)|![pic11f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51808/9d499138-a69c-251f-564a-16d3ad09326e.png)|

## 補足：プルとマージ

今回はあえて触れなかったリモートとの連携に関して少しだけ触れておきます。

他人の更新は基本的にリモートにあるためそれをローカルへ取り込む必要があります。
この方法としてはプルとフェッチがあります。
フェッチはリポジトリーの情報をローカルへ持ってくるだけ。プルはフェッチを行った後、ローカルブランチに対するマージを行う操作になります。

今まで記載してきたリベースやマージの部分を理解すると、プル操作にあるオプションが少しは理解しやすいのではないでしょうか。

・オプションなし：可能な限りファストフォワードをします。ファストフォワードできない場合はコミットを作成してマージします。`--ff` オプションと同じ。
・`--no-ff `：リモートの変更を必ずコミットを作成してマージします。
・`--rebase`：リモートの変更をリベースして取り込み、ローカルで変更した内容を反映します。
・`--squash`：リモートの複数の変更をまとめてチェリーピックするような挙動をします。マージ済みの扱いになりますが、自動的なコミットは行われません。

## 最後に

コンフリクトが発生してない単純なケースでのまとめのため、実際はもっと複雑です。大人数で開発していると、裏ではどんどんマージが行われ、チェリーピックに追われる日々みたいなこともあります。

みんなで共有しているブランチでリベースだけはやめましょう:sob: