Gitの仕組みを垣間見てみる

Gitのコマンドが覚えられなくて都度調べて使っており、内部で何が行われているのかを理解することにより、考えてGitを使うことができるかもしれないと思い、調べてみた。

### Gitオブジェクト
Gitオブジェクトは`.git/objects`配下に格納される。
Gitオブジェクトのファイル名は、ファイルの内容から40桁のハッシュ値が算出され、
最初の2桁がサブディレクトリ名となり、残りの38桁がGitオブジェクトを管理するファイル名となる。
ファイルの内容が同一であれば、ハッシュ値も同一となる。

Gitオブジェクトは以下の4種類に分類される。
`git cat-file -p <Commit>`でGitオブジェクトの内容を確認できる。

#### blobオブジェクト
ファイルの内容を保持するオブジェクト。
ファイルと1対1に対応する。

```
❯ git cat-file -p 4856fe2
First
```

#### treeオブジェクト
ディレクトリの内容を保持するオブジェクト。
```
❯ git cat-file -p a4e2708
040000 tree e4fe49647f2e9533250257bb0b4a4877ca80856e    dir1
100644 blob 4856fe236dbdd59db2741de0235ae7db63c27a1a    file1.txt
```

#### commitオブジェクト
コミットの内容を保持するオブジェクト。
commitオブジェクトには、以下の情報が含まれる。
- 最上位ディレクトリのtreeオブジェクトへの参照
- コミットしたユーザーの情報
- タイムスタンプ
- コミットメッセージ
- 親コミットへの参照

```
❯ git cat-file -p 6c72135
tree 18e184c65133446831504aa14212765cfaaf84be
author Crawd <test@example.com> 1729080682 +0900
committer Crawd <test@example.com> 1729080682 +0900

First Commit
```

#### tagオブジェクト
タグの内容を保持する。
tagオブジェクトには以下の情報が含まれる。
- コミットへの参照
- オブジェクトのタイプ？
- タグ名
- タグをつけたユーザーの情報
- 注釈
```
❯ git cat-file -p 1900f7f
object 1b6d6d37524dde2b5a035d7a3a659ff92faba1e6
type commit
tag testtag2
tagger Crawd <test@example.com> 1729082183 +0900

Annotation Tag
```

## ステージング
`git add`した際、ステージングエリアに変更が記録される。
この変更は、`.git`ディレクトリの`index`ファイルに記録されている。
この時、**追跡している全てのファイル(変更されていないファイルも含む)の情報がtreeオブジェクトとして記録される。
`git add`されたファイルの情報だけを保持しているのではない。**
`git add`した直後はワーキングツリーとステージングエリアの内容は同一。

`git add`した時点のファイルの内容でblobオブジェクトが作成されるため、その後に`git add`したファイルの中身を変更したとしてもその内容は反映されない。

`git ls-files --stage`でindexファイルの内容を参照できる。

## ブランチ
今どのブランチにいるかは`.git/HEAD`ファイル内で確認できる。
`.git/HEAD`は今いるブランチへの参照を示している。
各々のブランチのHEADはある特定のコミットへの参照を示している。
各々のブランチのHEADがどのコミットを参照しているかは`.git/refs/heads/<branchname>`ファイルに保持している。

```
❯ cat .git/HEAD
ref: refs/heads/master

❯ cat .git/refs/heads/master
1b6d6d37524dde2b5a035d7a3a659ff92faba1e6
```

## 各コマンドが何をしているのか
実際に下記のコマンドを実行した際の`.git`ディレクトリ内部の変化を確認した。
### git add
indexファイルを更新し、ファイルに対応するblobオブジェクトを生成する。
treeオブジェクトの情報はindexファイル内に保持している。

### git commit
コミットオブジェクトを作成し、indexファイルの情報からtreeオブジェクトを作成する。
HEADを新たに作成したコミットオブジェクトを指すように更新する。

### git branch
ブランチを作成する。
`.git/refs/heads/`以下に、対応するブランチの参照を管理するファイルを作成する。そのファイルの内容を現在のHEADが指し示すコミットと同一になるように更新する。

### git switch
ブランチを切り替える。
`.git/HEAD`の内容を`.git/refs/heads/`以下の対応するブランチの参照を指すように更新する。

### git tag
`.git/refs/tags/`以下にタグの参照を管理するファイルが作成される。
アノテーションタグの場合はtagオブジェクトが作成され、注釈情報などはtagオブジェクトに格納される。
結果として、`.git/refs/tags/`以下の参照は特定のコミットを直接参照する場合もあるし、tagオブジェクトを参照する場合もある。

### git restore
`git restore`はワーキングツリーの特定のファイルをHEADないし指定されたコミットの状態に復元する。
ブランチHEADは移動しない。

#### git restore \<filename\>
指定されたファイルのワーキングツリーでの変更を取り消す。
ワーキングツリーの状態をindexファイルに保持している状態で更新する。
結果として、ワーキングツリーだけを更新して`git restore`するとファイルの変更を取り消せる。
`git add`した後にワーキングツリーを更新して、`git restore`すると`git add`した時の状態へ巻き戻せる。
(`git add`した後の更新を取り消せる。)

#### git restore --staged \<filename\>
指定されたファイルのステージングを取り消す。(git addの逆)
ワーキングツリーの内容はそのまま。
indexファイルに保持している`filename`で指定されたblobオブジェクトの参照先を`git add`された際に作成されたblobオブジェクトから、現在のHEADのblobオブジェクトに更新する。
結果として、`git add`を取り消す形となる。

### git reset
ブランチHEADの参照先を変更する。

#### git reset --soft
ブランチHEADの位置のみを変更する。
コミットをまとめる時などに使用する。

#### git reset --mixed
ブランチHEADの位置を変更し、ステージングエリアを変更後のコミットの状態に更新する。
`git add`や`git rm`を取り消す場合に使う。

#### git reset --hard
ブランチHEADの位置を変更し、ステージングエリア及びワーキングツリーを変更後のコミットの状態に更新する。
ワーキングツリーの状態も含め、特定のコミットに巻き戻す際に使用する。

## あとがき
ステージングエリアは差分情報のみを保持していると勘違いしていた。

## 参考
https://qiita.com/marchin_1989/items/2ec01553e907f3a9e6bb

https://qiita.com/_ha1f/items/2dca1047c57d4f0bd465

https://www.kaitoy.xyz/2016/10/08/git-checkout/

https://qiita.com/growsic/items/ed67e03fda5ab7ef9d08