今さら学ぶgitの超基礎

## gitはfetch, add, commit, pushだけ知ってれば大丈夫
これは私が新卒時代に「Git は何を勉強すればいいですか？」と先輩に聞いたときの言葉です。
実際、プロジェクトのブランチ運用ルールが確立され、適切に守られているのであれば、この言葉の通りかもしれません。

しかし、ブランチルールから逸脱した操作 が発生すると、コミット履歴を追跡し、原因を特定して対応しなければならなくなります。そのためには、Git の仕組みを理解しておく必要があります。

この記事では、「Git のブランチの正体」と「2つのマージ方法」 について解説し、私自身の理解を深めるためにまとめました。
## ブランチの正体
git commit を実行すると、Git は「コミットオブジェクト」と呼ばれるデータを作成します。
このコミットオブジェクトには、以下の情報が含まれています。

- コミットの内容（変更履歴）
- 親コミット（修正）へのポインタ
- コミットメッセージ

ブランチの正体は、**最新のコミットを指すポインタ**です。

例えば、以下のようなコミット履歴があるとします：
```
A - B - C  (main)
```
ここで `main` ブランチは **Cのコミットを指しているポインタ** です。
そして、今どのブランチを見ているかを指すポインタが`HEAD`と呼ばれるものです。例えば`main`ブランチから`feature`ブランチを作成し、`feature`ブランチに切り替えたとします。そうすると`HEAD`は`main`から`feature`に移動します。
```
A - B - C  (main)
         \
          (feature, HEAD)
```
以降の説明では HEAD は省略しますが、ブランチを移動すると HEAD も移動している ということを意識してください。
## 2つのマージ方法
ブランチの仕組みが分かったところで、マージ方法について説明します。Gitのマージには、以下の2種類があります。
- Fast-forward マージ
- Three-way マージ

この違いを説明するために、以下のブランチを使います。
- main ブランチ（基準となるブランチ）
- feature/1 ブランチ（main から分岐）
- feature/2 ブランチ（main から分岐）
- 
Gitのブランチは**最新のコミットを指すポインタ**なので、作成直後の3つのブランチは同じコミットを指している状態です。以下の図では、各ブランチが指すコミットを箱（コミットハッシュ）で表しています。

![スクリーンショット 2025-02-26 12.47.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467943/be5a8344-7647-4058-b607-018d4cac2173.png)

以降の説明はこの状態からスタートします。
### fast-forward
今mainブランチにいるとして、feature/1ブランチに移ります。そしてそこでtext_1.txtを追加するという変更を行い、それをcommitします。するとコミット履歴とブランチの状態は以下のようになります。

![スクリーンショット 2025-02-26 13.14.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467943/05c0dba7-3b52-4c6a-8682-6999c34106cb.png)

この状態で`main`に`feature/1`をマージすると、Gitは次の3つの情報を確認します。
| 対象 | ブランチ名 | コミットハッシュ |
| ---- | ---- | ---- |
| マージ元 | feature/1 | `032fe65` |
| マージ先 | main | `7e2afb5` |
| マージ元／先の共通先祖 | main | `7e2afb5` |

この状態をみると、マージ先である`main`の最新コミットが`feature/1`との共通の祖先のコミットと一致しているため、**単純にポインタを`feature/1`に進めるだけでOK**です。

:::note info
マージ元／先の共通の先祖はgitが自動的に判別してくれています
:::

```bash
% git merge feature/1
Updating 7c2afb5..032fe65
Fast-forward
 text_1.txt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
```
このように、ポインタを進めるだけのマージを**Fast-forwardマージ**と呼びます。

![スクリーンショット 2025-02-26 13.14.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467943/c0609d89-4511-424a-9544-44b54c8c8858.png)

### three-way-merge
次に`feature/2`ブランチに移り、text_2.txtというファイルを追加する変更を行います。するとコミット履歴とブランチの状態は以下のようになります。

![スクリーンショット 2025-02-26 13.17.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467943/d296bc3e-580e-44a6-b294-7464e67eb58a.png)

この状態でfeature/2をmainブランチにマージします。今回のケースではマージ元／先、共通の先祖は以下のようになっています。
| ブランチ種別 | ブランチ名 | コミットハッシュ |
| ---- | ---- | ---- |
| マージ元 | feature/2 | `9512fe5` |
| マージ先 | main | `032fe65` |
| マージ元／先の共通先祖 | main | `7e2afb5` |

先ほどのfast-forwardと違ってすべてコミットハッシュがバラバラになっています。この状態で`main`ブランチに移り`feature/2`をマージするとどうなるでしょう？

```bash
% git merge feature/2
Merge made by the 'ort' strategy.
 text_2.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 text_2.txt
```

このように無事マージはできましたが、fast-forwardの文字が表示されていません。これは、今回のようなパターンでは**three-way-merge**と呼ばれる方法でマージが行われたからです。

![スクリーンショット 2025-02-26 13.23.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467943/1bd288c4-02ed-4622-ac3f-b248387baf4d.png)

Gitは共通の祖先`7e2afb5`を基に`main`の`032fe65`と`feature/2`の`9512fe5`を統合し、新しいマージコミット`a899dae`を作成します。
「共通の祖先」をもとに 3 つのコミットを比較して統合するのが**Three-way Merge**と呼ばれるマージ方法になります。おそらく案件などでよく見かけるのはこちらのパターンになると思います。

## まとめ
gitブランチの正体とマージのパターンについてまとめました。時々必要に駆られて`revert`や`reset`の操作をするのですが、仕組みがよくわからかったのがこのgitの基本を知ってからようやく意味がわかるようになりました。gitの公式ドキュメントがかなり優しく詳しい解説をしているので一度読んでみるのをおすすめします。

## 参考記事
https://git-scm.com/book/ja/v2