Gitの仕組み~Gitの仕組みを「ポインタ」で理解する~

# はじめに
本記事ではGitの内部的な仕組みを説明します。

以下のUdemy教材を学習した内容を自分なりに整理したものを記載します！よろしくお願いいたします～

https://www.udemy.com/share/105Ium

### 対象読者
gitのadd, commit, branch, mergeなどの基本的な概念や使い方は抑えられている方

# コミット、ヘッド、ブランチを「ポインタ」で理解する
### コミットは「親コミットへのポインタを持つ」
まず、Gitのコミット履歴は、子のコミットが親コミットのハッシュ値を持ったポインタを持つことでつながっています。たとえば、あるコミットを「C2」とすると、C2は前のコミット（「C1」）へのポインタを保持しており、これによって履歴をさかのぼって確認することができるのです。

逆に、親コミットは子コミットへのポインタを持っていないので、親コミットからは子コミットは見られません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/f924f132-baa7-434a-8973-45d637a5794c.png)


### HEADは「現在のコミット」を指すポインタ
GitにはHEADという特別なポインタがあります。これは「現在チェックアウトしているコミット」を指し示すポインタです。作業中のブランチ上の最新コミットがどれかを示すのに使われ、Gitの操作を行う上で非常に重要な役割を果たします。



### ブランチも「最新コミットを指す」ただのポインタ
ブランチと聞くと大げさな概念に思えるかもしれませんが、実はブランチもポインタの一種です。ブランチは「そのブランチ上の最新コミット」を指しています。

- たとえば、mainブランチを作成した時点では、mainという名札を持ったポインタが、そのときの最新コミットを指すようになります。
- 別のブランチを作成すると、別のラベルを持った新しいポインタが同じ最新コミット、あるいは別のコミットを指すようになるわけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/e3f69d7b-0026-4da5-b8a2-f7e35dbf8c6f.png)



# Gitの代表的なコマンドとポインタの動き
### git commit
- HEADが指すコミットの子として、新たなコミットを追加します。
- 新しく作られたコミットは、その前のコミット（つまり現在のHEAD）を親として持ちます。
- コミットを作るたびに、HEADもブランチのポインタも自動で新しいコミットを指し示すようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/dfe30de4-ae89-49f1-ad86-31afb8026cf8.png)

### git branch <ブランチ名>
- HEADが指しているコミットに新しいブランチを追加します。
- 単に「<ブランチ名>」というラベルを付けて、そのコミットを指すポインタを一つ増やすイメージです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/68343049-2306-4067-abc8-02e89d3c8c11.png)

### git checkout <ブランチ名>
- HEADが指定したブランチを指すように切り替わるコマンドです。
- 作業中のブランチを切り替えると、ブランチの指すコミットの状態がワークツリーやステージングエリアに反映されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/e04d7c2b-2eca-4e51-bcbe-a62706404f66.png)


### git merge <ブランチ名>
- 現在のブランチ(HEADが指すブランチ)と、マージ先のブランチが指す最新コミットを統合します。
- マージの結果、共通の親を持つ新しいマージコミットを作ったり、fast-forwardと呼ばれるシンプルな移動だけで終わったり、状況に応じてさまざまな動きをします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/df68df98-9fd1-4590-aab5-1fa454f68b19.png)

### git branch -d <ブランチ名>
- 指定したブランチのポインタ（ラベル）を削除します。
- 指定のブランチポインタが消えるだけで、コミット自体が消えるわけではありません。2つ目の図のように、もしそのコミットに辿るブランチがないなど、再度参照する術がなければ、やがてGitのガーベジコレクションによって消去される可能性はありますが、すぐに消えてしまうわけではありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/d6675409-b39e-4251-826a-8bddb754a75f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/04bf562f-1bed-4f56-af23-6b580003b750.png)

### git checkout <ハッシュ値>
- 指定したコミットにHEADを移動します。
- これによって過去のコミットを直接参照できるようになります（いわゆる「タイムスリップ」）。
ただし、そのままコミットすると「detached HEAD」と呼ばれる状態になり、どのブランチからも到達できないコミットができる点には注意が必要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/4cd66b8d-efa9-4c6e-aa66-27415022a5f4.png)

##### 注) -- <ファイル名> オプション
HEADを動かさず、特定のコミットから特定のファイルだけを取り出して、ワークツリーに上書きすることができます。過去の変更の一部だけを取り込みたいときなどに便利です。

### git reset <ハッシュ値>
- HEADを指定のコミットに移動するコマンドです。
- もしHEADがあるブランチに乗っていれば、ブランチごとコミットを指し直します。その後に続くコミットはブランチから外れてしまうため、再び移動しない限り見えなくなります。
- 一定時間が経過すると不要とみなされて削除される可能性があるため、意図しないコミットの消失には要注意です。

##### 注) git checkoutがHEADのみの移動であったのに対し、git resetではbranchも動かせる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/c76daf5e-3ff9-4b9b-8cd0-7a2aa289d2c4.png)

##### --hard, --soft, そしてオプションなしの違い
- --hard: ワークツリーとステージングエリアを強制的にHEADの内容に合わせます。
- --soft: ワークツリーとステージングエリアは変更せず、HEADのみを切り替えます。
- オプションなし: ステージングエリアをHEADに合わせ、ワークツリーはそのままにしておきます。

### git revert
- 過去のコミットを打ち消すための新たなコミットを追加するコマンドです。
- コミット履歴に「戻し」を残すので、どこで何があったかを追跡しやすいのがメリットです。
- git resetと違って、過去のコミットを消してしまうわけではないので、共同開発ではこちらの方法が好まれる場合が多いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/9a8f9c9f-0265-4b24-a535-8ad0ed8f70b1.png)

# まとめ
- コミット、ブランチ、ヘッドはすべてポインタによって説明できる
- 子コミットは親コミットのポインタを持つことで、新しいコミットから古いコミットを参照することができる
- 親コミットは子コミットのポインタを持たず、古いコミットから新しいコミットは参照できない
- HEADは参照中のコミットまたはブランチを指し示すポインタ
- branchは最新のコミットを指し示すポインタ
- gitの各コマンドによる仕組みもポインタによって説明できる