git mergeとrebaseの違いについて

# はじめに
**DMM WEBCAMP Advent Calendar 2023** 9日目担当の@72_mikanです。
gitの理解のために今回git merge と rebaseについて話していきたいと思います。

# 流れ
- git mergeについて
- git rebaseについて
- git mergeとgit rebase

# git mergeについて
この記事を読んでいる方でbranchを分けてコードを書いたことがない方がいるかもしれないのでブランチを分けるについて先に説明したいと思います。ブランチは分岐させた作業のことです。
ブランチを分岐させることで分岐したブランチはほかのブランチに影響を受けないため並行して開発を行っていくことができます。
例えば、mainブランチからtask1ブランチとtask2ブランチに切った(ブランチを分けることをブランチを切るといいます。)とします。
(mainブランチの時点でそれぞれindex-1.html、index-2.htmlファイルがあったとします。)
そこから、task1ブランチに切り替えindex-1.htmlを編集してもmainブランチやtask2ブランチに切り替えるとindex-1.htmlの編集内容がmainブランチやtask2ブランチには反映されません。
逆にtask2ブランチでindex-2.htmlを編集し、mainブランチやtask1ブランチに変更が反映されません。
このように、ブランチを切ることでtask1では機能の追加などを行い、task2ではバグの修正をするなどで並行して作業をすることが可能になります。

イメージとしては次のようになります。
![branch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/c22ddd50-a4b9-6c40-ea81-a9dc95498a08.png)


```
# ブランチ作成コマンド
git branch ブランチ名

# ブランチ切り替え
git checkout ブランチ名
```
次にmergeについて説明していきたいと思います。
git mergeはbranchを分けて作業を行った際、分けたbranchを取り込むコマンドになります。
イメージとしては以下になります。
![fastfoward.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/5d57c7c5-4f60-0557-2ea0-42da9b0c6c15.png)

こちらはmainブランチからtask1ブランチにブランチを切って、task1で開発を行っていたとします。
mainブランチにtask1の作業内容を取り込みたいときに`merge`を行うとtask1の内容をmainブランチが取り込むことができます。

mergeの手順としては以下のようになります。
```
# mainブランチにいるとします。
git merge task1
```

また、mergeの種類は3種類存在します。
それぞれ説明していきたいと思います。

まず、一つ目はFast Fowardです。Fast Fowardは上で説明したmergeの内容になります。
mergeを行うことで取り込もうとするブランチ(mainブランチが)先に進む(task1ブランチの位置に進む)のことを言います。

二つ目はAuto Merge
イメージとしては以下のようになります。
![automerge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/7735bce8-0e2e-ef81-33bb-0c3b43127be8.png)

Auto MergeはAの位置からブランチが分かれそれぞれBコミットにtaskブランチ、Cコミットにmainブランチがあった時、mainブランチがtaskブランチを取り込もうとすると、新しい、Dコミットを作成し、mainブランチをDコミットに移動してくれるmergeのことです。
こちらが良くイメージされるmergeになるかと思います。

最後にConflictになります
イメージとしては以下のようになります。
![conflict.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/af1d0b74-752b-318a-65f0-7aca4c5d8f88.png)
これだけではわかりにくいと思うのでひとつづつ説明していきます。
Aコミットでは以下の内容が記載されているとします。
```file.test(Aコミット)
こんにちは！
```
コミットAからブランチを切ってそれぞれtaskブランチがBコミット、mainブランチがCコミットとします。
taskブランチでは以下のようにファイルを編集しました。
```file.txt(Bコミット)
こんにちは！
conflictを解消しよう！！
```
mainブランチでは以下のようにファイルを編集したとします。
```file.txt(コミットC)
こんにちは！
コンフリクトを解消しよう！！
```

ここから、mainブランチからtaskブランチをmergeを行いコンフリクトが起きると以下のように表示されるかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/e9fcbc40-f58d-940d-e178-8904e21d4475.png)

なぜ、このようなことが起きるのか？
mainブランチとtaskブランチで同じ個所を編集していたためです。今回、それぞれのブランチのfile.txtの2行目で異なった内容で編集を行ったと思います。それぞれのブランチで同じ個所編集を行っているとどちらのブランチの編集内容を優先すればいいのかをgit側で判断できないためにコンフリクトが起こります。

次に。コンフリクトの解消方法を説明していきたいと思います。

file.txtでは以下のようになっているかと思います。
```file.text(コンフリクト後)
こんにちは！
<<<<<<< HEAD
コンフリクトを解消しよう！！
=======
conflictを解消しよう！！
>>>>>>> task
```

コンフリクトが起きるとmainブランチ(mergeを行ったブランチ)がHEAD部分で表示され、taskブランチ(mergeに取り込まれたブランチ)で出力されます。
このままだとどちらを優先するか判断できないため今回は取り込んだtaskブランチを優先するように編集します。
```file.text(コンフリクト修正後)
こんにちは！
conflictを解消しよう！！
```

この後、以下のコマンドを行えばコンフリクトが解消されます。
```
git add file.txt
git commit -m 'conflictの解消'
```

以上がコンフリクトについての解説になります。

# git rebaseについて
ここから、rebaseについて説明していきます。
以下のコミット状況で説明していきたいと思います。
![merge1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/684d301d-a640-eafd-f190-ebfe7d28ccf3.png)

先ほど説明させていただいたmergeだと以下のようにFコミットを作成しmainブランチにtaskブランチの内容を取り込みました。
![merge2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/91f582c0-aa78-62f5-6417-83060d1a8901.png)

それに対しrebaseではmainブランチがあるコミットの前に取り組むブランチ(taskブランチ)のコミットが作成されmainブランチの前にtaskブランチが進んだ状態で作られます。
![rebase.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/e9a931aa-2327-ed4f-a190-7c598446d1fb.png)

rebaseのコマンドとしては以下になります。
```rebase例
# mainブランチにいる状態と仮定します
git checkout task # taskブランチに切り替えます

git rebase main # taskブランチでrebaseコマンドを使います

git checkout main # mainブランチに切り替え

git merge task # 前に進んでいるtaskブランチをmainブランチで取り込みtaskブランチと同じコミットに移動
```

こうすることでコミットの履歴を一列にまとめることができます。

# git mergeとgit rebaseの違い
最後にrebaseとmergeの違いはコミットをきれいにまとめられるかどうかというところです。
mergeの場合は新しいコミットを作成するためコミット履歴が分かれてしまうのに対し、rebaseではコミット履歴を一列にまとめることができます。

しかし、注意点もあります。一度、リモートブランチにpushした、ブランチでrebaseを使わない方がいいことです。
以下の例で説明します。
![merge1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517290/bb0bc09c-b769-1cf1-b25c-7856c24077e3.png)

mainブランチとtaskブランチが既にpushされているとするとここでtaskブランチでrebaseを行うとDコミットの親ブランチがCコミットになります。
しかし、pushをした段階ではDコミットの親コミットはAコミットであるためこの時点でリモートリポジトリとローカルリポジトリのコミット履歴がおかしくなってしまいます。

rebaseを使うときはgitにpushしていないブランチで行うようにしましょう。

# 最後に
いかがでしたでしょうか？
少しでも気になることがございましたらコメントなどいただければと思います。