最初のgitの動作を図で理解する3

# はじめに

前々回では最初の`git`コマンドの基礎的な動作を図で確認し、前回はディレクトリや複数のファイルを含んだコミット動作とコミット内容を復元する処理を確認した。
今回は最初の`git`コマンドで複数のコミットの管理方法について確認する。

https://qiita.com/rairaii/items/45e1b19d431646fcd1d2

# 最初のgitコマンドを用意する

最初の`git`コマンドを[ビルド](https://り.com/rairaii/items/261071799ee3c1facd94#%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E3%83%93%E3%83%AB%E3%83%89%E3%81%99%E3%82%8B)し、`$HOME/bin-git にコピーして、PATHを通す。

```terminal:$HOME/bin-git
$ ls $HOME/bin-git
$ cat-file  commit-tree  init-db  read-tree  show-diff  update-cache  write-tree
$ export PATH=$PATH:$HOME/bin-git
$ which cat-file
/$HOME/bin-git/cat-file
```


# 1stコミット

この章の内容は前回行った作業と同等である。すでに作業を行っている場合には[2ndコミット](#2ndコミット)へ進む。

## ファイルの作成
```terminal: 確認に利用するファイルを作成する
$ echo "Hello, World" > README.md
mkdir src
$ echo 'echo "Hello"' > src/hello.sh
$ echo 'echo "World"' > src/world.sh
$ cat README.md ; cat src/hello.sh ; cat src/world.sh
Hello, World
echo "Hello"
echo "World"
```

## コミット
作成したファイルを登録して最初のコミットを行う。

```terminal:1stコミットを行う
$ update-cache README.md src/hello.sh src/world.sh
$ write-tree
937b5d80b2b3b9de8cf3241ab8dd97892f0ad3cf
$ echo "1st hello world" | commit-tree 937b5d80b2b3b9de8cf3241ab8dd97892f0ad3cf
Committing initial tree 937b5d80b2b3b9de8cf3241ab8dd97892f0ad3cf
d2970ed409efe53434b0bff727cc5bc0b56b4958
```

作成した`commitオブジェクト`はSHA1ハッシュ`d2970ed409efe53434b0bff727cc5bc0b56b4958`で管理される。

## リポジトリの状態

1stコミット直後のリポジトリの状態

![1stコミット後のリポジトリ](https://camo.qiitausercontent.com/73afad746286ea36a9e19d916b44d52c931d57c3/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f313031373438392f38363335373130632d373238332d356631612d326630312d3664326132643835623963352e706e67)


ここまでは前回と同様である。

# 2ndコミット

ファイルを修正し2ndコミットを行いリポジトリ内部を確認する

## ファイルアップデート

`src/world.sh`の内容をアップデートする。

```terminal: 確認に利用するファイル内容を修正する
$ echo 'echo "World, v2"' > src/world.sh
$ cat src/world.sh
echo "World, v2"
```

## コミット

アップデートした`src/world.sh`を登録し、`write-tree`コマンドと`commit-tree`コマンドで2ndコミットを行う。

```terminal:2ndコミットを行う
$ update-cache src/world.sh
$ write-tree
c7cfa281da8793c504aea1c515d2e9b12522cbf4
$ echo "2st hello world" | commit-tree c7cfa281da8793c504aea1c515d2e9b12522cbf4
Committing initial tree c7cfa281da8793c504aea1c515d2e9b12522cbf4
f6462e93a04c17443d782550cb20b06a354a4815
```

`update-cache`コマンドには`src/world.sh`しか引数として渡していないが、`index`ファイルには元の`README.md`, `src/hello.sh`, そして修正した後の`src/world.sh`の`blobオブジェクト`へのSHA1ハッシュが保存されている。つまり、一度`index`ファイルに登録したファイルは今後の全てのコミットの対象になるファイルだとして取り扱われる。
この動作は最新の`git`の挙動と同一であると言えるだろう。

## リポジトリの状態

2ndコミットの対象とした`treeオブジェクト`の内容を確認する。
`README.md`ファイルと`src/hello.sh`ファイルに対しては、1stコミットの`treeオブジェクト`から指していた既存の`blobオブジェクト`を指しており、`src/world.sh`ファイルについては、さきほど`update-cashe`コマンドで新規に作成した`blobオブジェクト`を指している。

```terminal:
read-tree c7cfa281da8793c504aea1c515d2e9b12522cbf4
100664 README.md (5f60b55715af0273fd592428a179c59299a0987f)
100664 src/hello.sh (c1e79ffabce97edb43548eae29e3b4a1b6fe9071)
100600 src/world.sh (616ca6f7edce8c4f755aa8344cee6383f9d780d5)
```

つまり、変更があったファイルについてのみ指す`blobオブジェクト`を変更している。
その`blobオブジェクト`は変更後のファイルをzlibで圧縮したファイルである。


![スクリーンショット 2024-01-07 20.52.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/e5d14c7b-0267-bb85-84ca-0573f7b1231e.png)


非常にシンプルな仕組みでコミットを実現していることがわかる。
ファイルの変更があった場合にその変更箇所を保存するような`CVS`や`Subversion`といった伝統的なSCM(Source Control Management)とは異なり、変更のあったファイルを **そのまま** 保存する。差分が必要な場合は動的に差分ファイルを作成する。このような挙動をとるため、[`git`のコミットはスナップショットであり差分ではない](https://github.blog/jp/2021-01-06-commits-are-snapshots-not-diffs/)、と言われたりする。
`git`からSCMに触れた人には当たり前の話なのだが、古いSCMに慣れたエンジニアからするとコミットはスナップショットと言われるとその仕組を理解したくなるくらいには衝撃的である。