最初のgitと最新のgitをソースコードと挙動から比較する

# はじめに

"Gitは最初1244行しかなかった" とのこと。手軽に読めそうな規模のソースコードなので初期の`git`の挙動とソースコードを確認する。

https://zenn.dev/uta8a/articles/b7169fd147fb31

# ソースコードと挙動を確認しての所見
最初の`git`で実装されている管理用オブジェクトやSHA1ハッシュを利用した`git`の参照の仕組みは基本的に最新の`git`と同様だ。特にSHA1ハッシュと利用することで実現するソースコード管理の詳細は興味深い。複雑になった現状の`git`のソースコードよりも、初期の`git`のソースコードで`git`の実装を学ぶもの面白いかもしれない。

https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Git%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88

https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Git%E3%81%AE%E5%8F%82%E7%85%A7

ちなみに最新の `git v2.29` ではSHA256が実験的にサポートされたとのこと。ただしSHA1と互換性はない。

https://zenn.dev/spiegel/articles/20201025-sayonara-sha1

図を使って説明は次回以降に記載する。

https://qiita.com/rairaii/items/1bed4a1fc1bdd74d32d1

# ソースコードを取得する

`git`の最初のバージョンの挙動をソースコードを読みながら確認する。

https://github.com/git/git/tree/e83c5163316f89bfbde7d9ab23ca2e25604af290

`git`のソースコードは下記のように取得できる。

```terminal:gitのソースコードを取得する
$ git clone https://github.com/git/git.git
$ cd git
$ git checkout e83c5163316f89bfbde7d9ab23ca2e25604af290
warning: unable to rmdir 'sha1collisiondetection': Directory not empty
Note: switching to 'e83c5163316f89bfbde7d9ab23ca2e25604af290'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at e83c516331 Initial revision of "git", the information manager from hell
$ git log
commit e83c5163316f89bfbde7d9ab23ca2e25604af290 (HEAD)
Author: Linus Torvalds <torvalds@ppc970.osdl.org>
Date:   Thu Apr 7 15:13:13 2005 -0700

    Initial revision of "git", the information manager from hell
$ ls
Makefile	README		cache.h		cat-file.c	commit-tree.c	init-db.c	read-cache.c	read-tree.c	show-diff.c	update-cache.c	write-tree.c

```

# ソースコードをビルドする

オリジナルの最初の`git`のソースコードは2005年に作成されている。C言語の仕様やLinux環境が異なるため、そのままのソースコードではビルドはできないだろう。Ubuntu22.04で動作確認したソースコードを用意した。

## ソースコードの修正方法
次の記事の修正を参考にソースコードを修正しビルドする
- コンパイラの仕様変更対応に対するCFLAGオプション追加
- リンクするライブラリの指定を追加
- 構造体変更に対応
- [追加]　show-diffの不具合修正

https://zenn.dev/uta8a/articles/b7169fd147fb31

## ビルド

https://github.com/aRaikoFunakami/git/tree/update_for_2024

```terminal:Build
$ git clone https://github.com/aRaikoFunakami/git.git
$ cd git
$ git checkout 1085a7c7d8e9966f368ea7511745723438481e16
$ make
$ ls
ls
Makefile  cache.h   cat-file.c  commit-tree    commit-tree.o  init-db.c  read-cache.c  read-tree    read-tree.o  show-diff.c  update-cache    update-cache.o  write-tree.c
README    cat-file  cat-file.o  commit-tree.c  init-db        init-db.o  read-cache.o  read-tree.c  show-diff    show-diff.o  update-cache.c  write-tree      write-tree.o
```

## 動作確認環境
Ubuntu22.04で動作確認を行った。
```terminal
$ uname -a
Linux K230703-03 6.2.0-33-generic #33~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Sep  7 10:33:52 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
```

## 修正内容
修正内容は下記の通り

```terminal:修正差分
$ git diff
```
```diff:git diffの出力
iff --git a/Makefile b/Makefile
index a6bba79ba1..1ab6dba902 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-CFLAGS=-g
+CFLAGS=-g -std=c99 -fcommon
 CC=gcc

 PROG=update-cache show-diff init-db write-tree read-tree commit-tree cat-file
@@ -8,7 +8,7 @@ all: $(PROG)
 install: $(PROG)
        install $(PROG) $(HOME)/bin/

-LIBS= -lssl
+LIBS= -lssl -lz -lcrypto

 init-db: init-db.o

diff --git a/show-diff.c b/show-diff.c
index b8522886a1..89d1d80b3a 100644
--- a/show-diff.c
+++ b/show-diff.c
@@ -11,11 +11,11 @@ static int match_stat(struct cache_entry *ce, struct stat *st)
 {
        unsigned int changed = 0;

-       if (ce->mtime.sec  != (unsigned int)st->st_mtim.tv_sec ||
-           ce->mtime.nsec != (unsigned int)st->st_mtim.tv_nsec)
+       if (ce->mtime.sec  != (unsigned int)st->st_mtime ||
+           ce->mtime.nsec != (unsigned int)st->st_mtimensec)
                changed |= MTIME_CHANGED;
-       if (ce->ctime.sec  != (unsigned int)st->st_ctim.tv_sec ||
-           ce->ctime.nsec != (unsigned int)st->st_ctim.tv_nsec)
+       if (ce->ctime.sec  != (unsigned int)st->st_ctime ||
+           ce->ctime.nsec != (unsigned int)st->st_ctimensec)
                changed |= CTIME_CHANGED;
        if (ce->st_uid != (unsigned int)st->st_uid ||
            ce->st_gid != (unsigned int)st->st_gid)
@@ -30,6 +30,7 @@ static int match_stat(struct cache_entry *ce, struct stat *st)
        return changed;
 }

+/*
 static void show_differences(struct cache_entry *ce, struct stat *cur,
        void *old_contents, unsigned long long old_size)
 {
@@ -41,6 +42,22 @@ static void show_differences(struct cache_entry *ce, struct stat *cur,
        fwrite(old_contents, old_size, 1, f);
        pclose(f);
 }
+*/
+
+static void show_differences(struct cache_entry *ce, struct stat *cur,
+                             void *old_contents, unsigned long long old_size) {
+    char temp_file[] = "/tmp/show_diff.XXXXXX";
+    int fd = mkstemp(temp_file);
+    write(fd, old_contents, old_size);
+    close(fd);
+
+    static char cmd[1000];
+    snprintf(cmd, sizeof(cmd), "diff -u %s %s", temp_file, ce->name);
+    system(cmd);
+
+    unlink(temp_file);
+}
+

 int main(int argc, char **argv)
 {
diff --git a/update-cache.c b/update-cache.c
index 5085a5cb53..a6b304ee07 100644
--- a/update-cache.c
+++ b/update-cache.c
@@ -139,9 +139,9 @@ static int add_file_to_cache(char *path)
        memset(ce, 0, size);
        memcpy(ce->name, path, namelen);
        ce->ctime.sec = st.st_ctime;
-       ce->ctime.nsec = st.st_ctim.tv_nsec;
+       ce->ctime.nsec = st.st_ctimensec;
        ce->mtime.sec = st.st_mtime;
-       ce->mtime.nsec = st.st_mtim.tv_nsec;
+       ce->mtime.nsec = st.st_mtimensec;
        ce->st_dev = st.st_dev;
        ce->st_ino = st.st_ino;
        ce->st_mode = st.st_mode;
```


# 最初の`git`コマンド

最初の`git`コマンドについて概要を確認し、実際に使用して動作を確認する。

## コマンド一覧

最初の`git`コマンドと対応する現在の`git`コマンドを表にまとめた。
よく利用される `git clone`, `git remote`, `git feth` などネットワーク越しにソースコードを管理する仕組みや、`git branch`, `git checkout`, `git switch`といったブランチ管理の仕組みは存在しない。[配管\(Plumbing\)コマンド](https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-%E9%85%8D%E7%AE%A1%EF%BC%88Plumbing%EF%BC%89%E3%81%A8%E7%A3%81%E5%99%A8%EF%BC%88Porcelain%EF%BC%89)の初期バージョンと言えるかもしれない。

|コマンド|対応するgitコマンド| 説明|
|:-:|:-:|:-|
|init-db| git init | リポジトリを初期化する |
|update-cache | git add | 指定したファイルをインデックスに登録する |
|cat-file | git show | インデックスで管理しているファイルの内容を表示する |
|write-tree | git write-tree | 現在のインデックスの内容から`tree`オブジェクトを作成する。`tree`オブジェクトはリポジトリ内のディレクトリ構造を表現し、ファイルモードやファイルのタイプ、ファイルへの参照を含む。 |
|read-tree | git ls-tree |指定された`tree`オブジェクトの内容を表示する|
|commit-tree | git commit-tree | 指定された`tree`オブジェクトに対して新しい`commit`オブジェクトを作成する。`commit`オブジェクトには`tree`オブジェクトへの参照とコミッター情報やコミットログが含まれる。|
|show-diff | git diff | 作業ディレクトリとインデックスの間の変更点を表示する | 

## オブジェクト一覧
オブジェクトは `./dircache/objects`ディレクトリで管理される。このディレクトリで管理されるオブジェクトはzlibでdeflate(圧縮)したファイルとして保存される。

|オブジェクト|説明|
|:-:|:-|
|blob| ファイルの内容を保存する。|
|tree| 複数のファイルを木構造で管理する。ファイルを参照するためのSHA1ハッシュを保持しており、blobオブジェクト(インデックスに登録したファイル)を高速に参照できる。相対パス付きでファイル名を保存しておりディレクトリ構造を保った形で管理されている全てのファイルを取り出すことができる。コミットは`tree`オブジェクトを対象に行うため、必ずコミットを行う前に`tree`オブジェクトを作成する必要がある。|
|commit| コミット内容を管理する。`tree`オブジェクトへの参照とコミット情報を持つ。`tree`オブジェクトから元のソースコード全体を高速に取り出すことができる。|

https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Git%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88

## 最初の`git`コマンドの使用例

最初の`git`には次の7コマンドが存在する。
- init-db
- update-cache
- cat-file
- write-tree
- read-tree
- commit-tree
- show-diff

先ずはこれら7つのコマンドの使い方を確認する。

```terminal: 1.リポジトリを初期化
$ ./init-db
defaulting to private storage area
```

`.dircache`ディレクトリがリポジトリとして作成される。最初のバージョンでは`.git`ディレクトリではない。


```terminal: update-cacheでインデックスに登録するファイルinit-db.c
$ cat ./init-db.c
#include "cache.h"

int main(int argc, char **argv)
{
	char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
...
	return 0;
}
```

例として、`init-db.c`というファイルをリポジトリで管理する。


```terminal: 2.現時点のinit-db.cをblobオブジェクトとして保存してインデックスに登録する
$ ./update-cache init-db.c
```

`update-cache`コマンドで、その時点の`init-db.c`の内容を`blob`オブジェクトとしてファイルに保存する。そのファイルへの参照をインデックスに登録して管理する。

```:init-db.cのblobオブジェクトとして保存したファイル
$ ls .dircache/objects/*/*
.dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
```

`./update-cache init-db.c`コマンドで登録した`init-db.c`ファイルはSHA1ハッシュ値 `0eaa053919e0cc400ab9bc40d9272360117e6978`で管理される。ハッシュ値を分割して、`0e`ディレクトリの`aa053919e0cc400ab9bc40d9272360117e6978`ファイルに登録したファイルの内容が保存される。

```terminal: 3.インデックスに登録したinit-db.cの内容を一時ファイルへ書き込む
$ ./cat-file  0eaa053919e0cc400ab9bc40d9272360117e6978
temp_git_file_bpsQTQ: blob
```

インデックスに登録したある時点のファイルの内容を確認したいとき、`blob`オブジェクトを示すSHA1ハッシュ値を示すことで直ちに取り出すことができる。`cat-file`コマンドは取り出したファイルを標準出力に出力するのではなく、一時ファイルの保存する。

```terminal: 書き出した一時ファイルの内容を確認する
$ cat temp_git_file_bpsQTQ
#include "cache.h"

int main(int argc, char **argv)
{
	char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
...
	return 0;
}
```

`cat`コマンドで一時ファイルの内容を確認する。インデックスに登録した時点の`init-db.c`のファイルと同一の内容表示される。

```terminal: 4.現在のインデックスの内容を基にtreeオブジェクトを作成する
$ ./write-tree
425ea63d64b33ac1618ec75f05e2056d238393a9
```

コミットは`tree`オブジェクトを対象に行うため`tree`オブジェクトを作成する`write-tree`コマンドが用意されている。`update-cache`コマンドでインデックスに登録して管理している内容を基に`tree`オブジェクトを作成する。`425ea63d64b33ac1618ec75f05e2056d238393a9`は作成した`tree`オブジェクトを管理するSHA1ハッシュ値である。

```terminal: 5.作成したtreeオブジェクトの内容を確認する
$ ./read-tree 425ea63d64b33ac1618ec75f05e2056d238393a9
100664 init-db.c (0eaa053919e0cc400ab9bc40d9272360117e6978)
```

作成した`tree`オブジェクトの内容は、確認したい`tree`オブジェクトのSHA1ハッシュ値を指定して確認できる。このコマンドでは`tree`オブジェクトに`init-db.c`(SHA1ハッシュ値:0eaa053919e0cc400ab9bc40d9272360117e6978)が含まれていることが確認できる。

```terminal: 6.指定したtreeオブジェクトに対して新しいcommitオブジェクトを作成する
$ echo '1st commit' | ./commit-tree 425ea63d64b33ac1618ec75f05e2056d238393a9
Committing initial tree 425ea63d64b33ac1618ec75f05e2056d238393a9
394426667836de6e4919ee0596d798207f707a81
```

**コミットする** ことは **`commit`オブジェクトを作成すること** だと確認できる。
`commit`オブジェクトは指定した`tree`オブジェクトを対象に作成される。commitオブジェクトにはtreeオブジェクトへの参照とコミッター情報やコミットログが含まれる。
`394426667836de6e4919ee0596d798207f707a81`は作成した`commit`オブジェクトを管理するSHA1ハッシュ値である。

```terminal:作成したcommitオブジェクトの内容を確認する
$ ./cat-file 394426667836de6e4919ee0596d798207f707a81
temp_git_file_nLjkGJ: commit
$ cat temp_git_file_nLjkGJ
commit 181tree 425ea63d64b33ac1618ec75f05e2056d238393a9
author raiko,,, <raiko@K230703-03> Sun Dec 31 18:08:27 2023
committer raiko,,, <raiko@K230703-03> Sun Dec 31 18:08:27 2023

1st commit
```

作成した`commit`オブジェクトの内容は`cat-file`コマンドで一時ファイルに保存し`cat`コマンドで確認できる。

```terminal:7.作業ディレクトリとインデックスの間の変更点を表示する
./show-diff
init-db.c: ok
```
`show-diff`コマンドは作業ディレクトリとインデックスの間の変更点を表示する。変更がない場合は`ok`と表示する。最初の`git`の`show-diff`コマンドはそのままでは動作に不具合があるため修正が必要になる。


# 最初の`git`コマンドの動作確認

最新の`git`コマンドの動作と対比しながら動作確認を進める

## リポジトリの初期化

`init-db`コマンドでリポジトリを作成する。

```terminal: init-dbで初期化
$ ./init-db
defaulting to private storage area
```

### `init-db`で作成したリポジトリ

`init-db`コマンドでリポジトリは`.dircache`ディレクトに作成される。
`.dircache` ディレクトリ内部に `objects` ディレクトリが用意され、その下に `00` から  `ff` までのディレクトリが用意される。`objects`ディレクトリで管理するファイルはSHA1ハッシュ値で管理され、ハッシュ値の上位２桁をフォルダ名、下位３８桁をファイル名と利用する。`00`から`ff`まで256のフォルダで多数のファイルを効率よく管理することを意図していると考えられる。
この仕組みは最新の`git`と同一である。

```terminal: リポジトリ内部
$ ls -la
total 360
drwxrwxr-x 3 raiko raiko  4096 12月 31 11:10 .
drwxrwxr-x 7 raiko raiko 36864 12月 31 11:09 ..
drwx------ 3 raiko raiko  4096 12月 31 11:10 .dircache
...

$ ls -la .dircache/
total 12
drwx------   3 raiko raiko 4096 12月 31 11:10 .
drwxrwxr-x   3 raiko raiko 4096 12月 31 11:10 ..
drwx------ 258 raiko raiko 4096 12月 31 11:10 objects

$ ls -la .dircache/objects/
total 1032
drwx------ 258 raiko raiko 4096 12月 31 11:10 .
drwx------   3 raiko raiko 4096 12月 31 11:10 ..
drwx------   2 raiko raiko 4096 12月 31 11:10 00
drwx------   2 raiko raiko 4096 12月 31 11:10 01
drwx------   2 raiko raiko 4096 12月 31 11:10 02
...
drwx------   2 raiko raiko 4096 12月 31 11:10 fd
drwx------   2 raiko raiko 4096 12月 31 11:10 fe
drwx------   2 raiko raiko 4096 12月 31 11:10 ff
```

### `git init`で作成したリポジトリ
`git init`でリポジトリを作成した場合には `.git` ディレクトリ内部に複数のファイルとディレクトリが用意される。その１つに同名の `objects`ディレクトリが存在する。初期化した段階では`info`と　`pack`のディレクトリが用意されており、`init-db` で初期化した `objects` ディレクトリと一見様子が異なるように見える。
しかし、`update-cache`以降の章から確認できるように`blob`オブジェクトや`tree`オブジェクト、`commit`オブジェクトを管理する仕組みは初期から同一である。

```terminal: git init コマンドでリポジトリを初期化
$ git --version
git version 2.34.1
$ git init
```
```terminal: リポジトリ内部
$ ls -la .git/
total 40
drwxrwxr-x 7 raiko raiko 4096 12月 31 11:21 .
drwxrwxr-x 3 raiko raiko 4096 12月 31 11:21 ..
-rw-rw-r-- 1 raiko raiko   23 12月 31 11:21 HEAD
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 branches
-rw-rw-r-- 1 raiko raiko   92 12月 31 11:21 config
-rw-rw-r-- 1 raiko raiko   73 12月 31 11:21 description
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 hooks
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 info
drwxrwxr-x 4 raiko raiko 4096 12月 31 11:21 objects
drwxrwxr-x 4 raiko raiko 4096 12月 31 11:21 refs

$ ls -la .git/objects/
total 16
drwxrwxr-x 4 raiko raiko 4096 12月 31 11:21 .
drwxrwxr-x 7 raiko raiko 4096 12月 31 11:21 ..
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 info
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 pack
```

## ファイルを登録して管理する (インデックス化)

ファイルをインデックスに登録して管理する

### `update-cache`での管理

`update-cache`コマンドで指定されたファイルをインデックスに登録する。

```terminal: init-db.c をインデックスに登録する
$ ./update-cache init-db.c
```

このコマンドを実行すると、実行時点の`init-db.c`ファイルが`blob`オブジェクトとしてファイルに保存され、そのファイルへの参照（SHA1ハッシュ)が`index`ファイルに登録される。
ここで作成される`blob`オブジェクトは`init-db.c`のファイル内容にヘッダ情報を追加した上でzlibでdeflate(圧縮)したものである。

#### リポジトリの状態変化
`update-cache`コマンドを実行した後のリポジトリ内部の状態変化を確認する。

```terminal: 変更されたファイルを確認する
$ ls -la .dircache/
total 16
drwx------   3 raiko raiko 4096 12月 31 11:34 .
drwxrwxr-x   3 raiko raiko 4096 12月 31 11:10 ..
-rw-------   1 raiko raiko  104 12月 31 11:34 index
drwx------ 258 raiko raiko 4096 12月 31 11:10 objects

$ ls .dircache/objects/*/*
.dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
```
初期化状態(init-dbでリポジトリを作成した状態)と比較して下記の２ファイルが追加された。
- .dircache/index
- .dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978

追加された２ファイルの内容を確認する。

##### `index`ファイル

`index`ファイルにはヘッダ情報(cache_header)と`objects`ディレクトリで管理されるファイル情報(cache_entry)がバイナリファイルとして保存される。

```c:cache.h
#define CACHE_SIGNATURE 0x44495243	/* "DIRC" */
struct cache_header {
	unsigned int signature;
	unsigned int version;
	unsigned int entries;
	unsigned char sha1[20];
};
```

```c: cache.h
struct cache_entry {
	struct cache_time ctime;
	struct cache_time mtime;
	unsigned int st_dev;
	unsigned int st_ino;
	unsigned int st_mode;
	unsigned int st_uid;
	unsigned int st_gid;
	unsigned int st_size;
	unsigned char sha1[20];
	unsigned short namelen;
	unsigned char name[0];
};
```

`index`ファイルの内容を`hexdump`コマンドで確認するとファイル名が保存されていることが確認できる。`signature`には "DIRC", versionには 1 が設定される。`hexdump -C`で16進数のデータを表示する場合にはリトルエンディアンで表示されるため、"DIRC"が"CRID"と表示されることに注意。

```terminal
$ hexdump -C .dircache/index
00000000  43 52 49 44 01 00 00 00  01 00 00 00 46 58 ad 43  |CRID........FX.C|
00000010  43 c7 16 57 a5 89 6a 89  aa 97 d0 8e f1 29 7e 7d  |C..W..j......)~}|
00000020  52 cd 90 65 a9 2c f4 21  52 cd 90 65 a9 2c f4 21  |R..e.,.!R..e.,.!|
00000030  02 03 01 00 88 56 bc 02  b4 81 00 00 e8 03 00 00  |.....V..........|
00000040  e8 03 00 00 ae 04 00 00  0e aa 05 39 19 e0 cc 40  |...........9...@|
00000050  0a b9 bc 40 d9 27 23 60  11 7e 69 78 09 00 69 6e  |...@.'#`.~ix..in|
00000060  69 74 2d 64 62 2e 63 00                           |it-db.c.|
00000068
```

indexファイルの内容をソースコードから紐解いて表にまとめると次のようになる。

| グループ | 変数 | 型 | HEX | 内容 |  
|:-:|:-:|:-:|:-:|:-:|
| ヘッダ | signature | unsigned int | 43 52 49 44 | "DIRC" |
| ヘッダ | version | unsigned int | 01 00 00 00 | 1 |
| ヘッダ | entries | unsigned int | 01 00 00 00 | 1 | 
| ヘッダ | sha1 | char[20] | 46 58 ad 43 43 c7 16 57 a5 89 6a 89 aa 97 d0 8e f1 29 7e 7d| 4658ad4343c71657a5896a89aa97d08ef1297e7d | 
| エントリ | ctime | cache_time | 52 cd 90 65 a9 2c f4 21 | sec:1703988562, nsec:569650345|
| エントリ | mtime | cache_time | 52 cd 90 65 a9 2c f4 21 | sec:1703988562, nsec:569650345|
| エントリ | st_dev | unsigned int | 02 03 01 00 | 66306|
| エントリ | st_ino | unsigned int | 88 56 bc 02 | 45897352|
| エントリ | st_mode | unsigned int | b4 81 00 00 | 33204|
| エントリ | st_uid | unsigned int | e8 03 00 00 | 1000 |
| エントリ | st_gid | unsigned int | e8 03 00 00 | 1000 |
| エントリ | st_size | unsigned int | ae 04 00 00 | 1198 |
| エントリ | sha1 | unsigned char[20] | 0e aa 05 39 19 e0 cc 40 0a b9 bc 40 d9 27 23 60 11 7e 69 78 |0eaa053919e0cc400ab9bc40d9272360117e6978|
| エントリ | namelen | unsigned short | 09 00 | 9|
| エントリ | name | unsigned char[0] | 69 6e 69 74 2d 64 62 2e 63 00 | "init-db.c" |

`init-db.c`を参照するためのSHA1ハッシュ`0eaa053919e0cc400ab9bc40d9272360117e6978`のうち最初の`0e`がディレクトリ名として利用され、残りの`aa053919e0cc400ab9bc40d9272360117e6978`がファイル名として利用される。最初の`0e`をディレクトリ名として利用して複数のディレクトリに分けてファイル管理しつつ、効率よくファイルにアクセスすることを可能にしている。

##### `objects`ディレクトリで管理されるファイル

`update-cache`で登録された`init-db.c`ファイルは`blob`オブジェクトとして`.dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978`で管理される。このファイルは`zlib`でdeflate(圧縮)したファイルである。したがって`zlib-flate`コマンドで展開して内容を確認できる。

```terminal: 
$ zlib-flate -uncompress < .dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
```

先頭に `blob 1198` というキーワードが追加された形で`init-db.c`の内容が保存されていることが確認できる。

```c: aa053919e0cc400ab9bc40d9272360117e6978を展開
blob 1198#include "cache.h"

int main(int argc, char **argv)
{
	char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
	int len, i, fd;
...
	return 0;
}
```

`blob 1198`の`blob`は`blob`オブジェクトであることを示し、`1198`は`init-db.c`のバイト数を示している。

```terminal: 
$ wc -c ./init-db.c
1198 ./init-db.c
```

### `git add`でファイルを管理する

`git add`コマンドで`init-db.c`ファイルをインデックスに登録する

```terminal: init-db.c をインデックスに登録する
$ git add init-db.c
```

#### リポジトリの状態変化

`git add`コマンドを実行した後のリポジトリ内部の状態の違いを確認する。

```terminal: リポジトリ内部
$ ls -la .git
total 44
drwxrwxr-x 7 raiko raiko 4096 12月 31 14:46 .
drwxrwxr-x 3 raiko raiko 4096 12月 31 14:46 ..
-rw-rw-r-- 1 raiko raiko   23 12月 31 11:21 HEAD
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 branches
-rw-rw-r-- 1 raiko raiko   92 12月 31 11:21 config
-rw-rw-r-- 1 raiko raiko   73 12月 31 11:21 description
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 hooks
-rw-rw-r-- 1 raiko raiko  104 12月 31 14:46 index
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 info
drwxrwxr-x 5 raiko raiko 4096 12月 31 14:46 objects
drwxrwxr-x 4 raiko raiko 4096 12月 31 11:21 refs

$ ls -la .git/objects/*/*
-r--r--r-- 1 raiko raiko 656 12月 31 14:46 .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da
```

リポジトリを初期化(`git init`)した時から次の２ファイルが追加されている。
- .git/index
- .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da

この挙動は`update-cache`コマンドを実行した場合と同一である。
追加されたファイルの内容を確認する。

##### indexファイル

```terminal: indexファイルの内容
hexdump -C .git/index
00000000  44 49 52 43 00 00 00 02  00 00 00 01 65 91 00 24  |DIRC........e..$|
00000010  16 ff 58 33 65 91 00 24  16 ff 58 33 00 01 03 02  |..X3e..$..X3....|
00000020  02 bc 57 c1 00 00 81 a4  00 00 03 e8 00 00 03 e8  |..W.............|
00000030  00 00 04 ae 25 dc 13 fe  10 1b 21 9f 74 00 7f 31  |....%.....!.t..1|
00000040  94 b7 87 dd 99 e8 63 da  00 09 69 6e 69 74 2d 64  |......c...init-d|
00000050  62 2e 63 00 70 db c6 b3  c1 09 59 6c fd 7a 2a cf  |b.c.p.....Yl.z*.|
00000060  64 29 3c 98 2a 87 98 92                           |d)<.*...|
00000068
```

`index`ファイルの内容は`update-cache`コマンドで作成した場合とは異なっている。ヘッダはリトルエンディアン表示で”CRID"から"DIRC"になり、バージョンが2になっているようだ。`init-db.c`のファイル名が同様に管理されていることが確認できる。

##### `objects`ディレクトリで管理されるファイル

objectsで管理されるファイルの内容を`zlib-flate`コマンドで確認する。

```terminal
$ zlib-flate -uncompress < .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da
```

```c: .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da
blob 1198#include "cache.h"

int main(int argc, char **argv)
{
	char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
	int len, i, fd;
...
	return 0;
}
```

展開したファイルの内容は`update-cache`コマンドで管理された場合と完全に一致する。

`index`ファイルの管理方法はアップデートされているが、`blob`オブジェクトのファイルの扱いは初期の`git`から変化していない。


## 登録したファイルを表示する

`objects`ディレクトリで管理されるファイルが`zlib`で圧縮されたファイルであり、`zlib-flate`コマンドを使用することでファイル内容が確認可能である。一方、`git`には管理されているファイルを表示するための専用コマンド`cat-file`が用意されている。`cat-file`コマンドは出力が一時ファイルになっているため、`cat`コマンドなどを組み合わせてファイル内容を確認する必要があるが、基本的には`git show`に相当するコマンドだと考えられる。

```terminal:登録したinit-db.cの内容を確認する
$ ls .dircache/objects/*/*
.dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
$ ./cat-file  0eaa053919e0cc400ab9bc40d9272360117e6978
temp_git_file_bpsQTQ: blob

$ cat temp_git_file_bpsQTQ
```

```c:cat temp_git_file_bpsQTQ の実行結果
#include "cache.h"

int main(int argc, char **argv)
{
	char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
	int len, i, fd;
...
	return 0;
}
```

## `tree`オブジェクトの作成

### `write-tree`で作成する
`git`ではコミットは`tree`オブジェクトを対象に行う。インデックスに登録された内容を基に`tree`オブジェクトを作成するのが`write-tree`コマンドになる。`write-tree`コマンドは`git write-tree`コマンドに相当する。

```terminal
$ ./write-tree
425ea63d64b33ac1618ec75f05e2056d238393a9
```

このコマンドで現在のインデックスの内容を基に`tree`オブジェクトを作成する。`tree`オブジェクトは`.dircache/objects`ディレクトで`blob`オブジェクトと同様に管理される。

#### リポジトリの更新

`.dircache/objects` 以下に新規ファイルが１つ追加された
- .dircache/objects/42/5ea63d64b33ac1618ec75f05e2056d238393a9

```terminal
$ ls -la .dircache/objects/*/*
-rw-rw-r-- 1 raiko raiko 644 12月 31 11:34 .dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
-rw-rw-r-- 1 raiko raiko  53 12月 31 16:15 .dircache/objects/42/5ea63d64b33ac1618ec75f05e2056d238393a9
```

追加されたファイル`.dircache/objects/42/5ea63d64b33ac1618ec75f05e2056d238393a9`の内容を確認する。ファイルモードやSHA1などを含むバイナリファイルをzlibでdeflateしたファイルになっている。

```terminal
$ zlib-flate -uncompress < .dircache/objects/42/5ea63d64b33ac1618ec75f05e2056d238393a9 | hexdump -C
00000000  74 72 65 65 20 33 37 00  31 30 30 36 36 34 20 69  |tree 37.100664 i|
00000010  6e 69 74 2d 64 62 2e 63  00 0e aa 05 39 19 e0 cc  |nit-db.c....9...|
00000020  40 0a b9 bc 40 d9 27 23  60 11 7e 69 78           |@...@.'#`.~ix|
0000002d
```

バイナリファイルは下記のデータを含んでいる。

|グループ|内容|意味|
|:-:|:-:|:-:|
|ヘッダ| tree| `tree`オブジェクトを示す |
|ヘッダ| 0x20 | 空白|
|ヘッダ|37| コンテンツが37バイトであることを示す |
|コンテンツ|100664 | ファイルモード (10:通常ファイル, 0664: -rw-rw-r--)
|コンテンツ|init-db.c | ファイル名 |
|コンテンツ| 0x00 | 文字列終端 |
|コンテンツ|　0e aa 05 39 19 e0 cc 40 0a b9 bc 40 d9 27 23 60 11 7e 69 78　 | init-db.cファイルのSHA1 |

`tree`オブジェクトを表す "tree" 文字列から始まり、インデックスに登録されていた`init-db.c`ファイルへの参照を持つ。今回は1ファイルのみ登録しているため、1ファイルへの参照のみ持っている。`init-db.c`ファイルのSHA1ハッシュを使って`update-cache`コマンドで登録された時点の`init-db.c`ファイルに高速アクセスすることができる。


### `git write-tree` で作成する

`git write-tree`コマンドで`tree`オブジェクトを作成した場合を確認し、`write-tree`コマンドで`tree`オブジェクトを作成した場合との違いを確認する。

```terminal:実行済み (init-db.cをインデックスに登録済み)
$ git add init-db.c
$ ls -la .git/objects/*/*
-r--r--r-- 1 raiko raiko 656 12月 31 14:46 .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da
```

```terminal:treeオブジェクトを作成する
$ git write-tree
cdb399a49eb53d128dc4baa0197c0f3a72eaa970
```

#### リポジトリの更新

`git write-tree`コマンドで現在のインデックスの内容を基に`tree`オブジェクトを新規作成する。
- .git/objects/cd/b399a49eb53d128dc4baa0197c0f3a72eaa970


```terminal
$ ls -la .git
total 44
drwxrwxr-x 7 raiko raiko 4096 12月 31 17:05 .
drwxrwxr-x 3 raiko raiko 4096 12月 31 15:35 ..
-rw-rw-r-- 1 raiko raiko   23 12月 31 11:21 HEAD
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 branches
-rw-rw-r-- 1 raiko raiko   92 12月 31 11:21 config
-rw-rw-r-- 1 raiko raiko   73 12月 31 11:21 description
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 hooks
-rw-rw-r-- 1 raiko raiko  137 12月 31 17:05 index
drwxrwxr-x 2 raiko raiko 4096 12月 31 11:21 info
drwxrwxr-x 6 raiko raiko 4096 12月 31 17:05 objects
drwxrwxr-x 4 raiko raiko 4096 12月 31 11:21 refs

$ ls -la .git/objects/*/*
-r--r--r-- 1 raiko raiko 656 12月 31 14:46 .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da
-r--r--r-- 1 raiko raiko  54 12月 31 17:05 .git/objects/cd/b399a49eb53d128dc4baa0197c0f3a72eaa970
```

`write-tree`コマンドと挙動の違いがあり、`tree`オブジェクトの作成に合わせて`index`ファイルが更新されている。

##### `index`ファイルの違い

`git write-tree`コマンドで更新された`index`ファイルの更新内容を確認する。

```terminal
hexdump -C .git/index
00000000  44 49 52 43 00 00 00 02  00 00 00 01 65 91 00 24  |DIRC........e..$|
00000010  16 ff 58 33 65 91 00 24  16 ff 58 33 00 01 03 02  |..X3e..$..X3....|
00000020  02 bc 57 c1 00 00 81 a4  00 00 03 e8 00 00 03 e8  |..W.............|
00000030  00 00 04 ae 25 dc 13 fe  10 1b 21 9f 74 00 7f 31  |....%.....!.t..1|
00000040  94 b7 87 dd 99 e8 63 da  00 09 69 6e 69 74 2d 64  |......c...init-d|
00000050  62 2e 63 00 54 52 45 45  00 00 00 19 00 31 20 30  |b.c.TREE.....1 0|
00000060  0a cd b3 99 a4 9e b5 3d  12 8d c4 ba a0 19 7c 0f  |.......=......|.|
00000070  3a 72 ea a9 70 f2 8a 35  45 3a 3c 8a 20 b1 25 9a  |:r..p..5E:<. .%.|
00000080  b9 61 a0 1f 35 39 2a 9b  8c                       |.a..59*..|
00000089
```

バイナリファイルのため詳細は不明だが、`init-db.c`文字列の後に`TREE`という文字列が確認できる。
さらに`TREE`文字列の後に`00 00 00 19 00 31 20 30 0a`が続き、その後に`tree`オブジェクトのSHA1を示す`cd b3 99 a4 9e b5 3d 12 8d c4 ba a0 19 7c 0f 3a 72 ea a9 70`が保存されている。 これらのことから`index`ファイルには`tree`オブジェクトの情報が追記されたと考えられる。


##### `tree`オブジェクトの違い

```terminal
$ zlib-flate -uncompress < .git/objects/cd/b399a49eb53d128dc4baa0197c0f3a72eaa970 | hexdump -C
00000000  74 72 65 65 20 33 37 00  31 30 30 36 34 34 20 69  |tree 37.100644 i|
00000010  6e 69 74 2d 64 62 2e 63  00 25 dc 13 fe 10 1b 21  |nit-db.c.%.....!|
00000020  9f 74 00 7f 31 94 b7 87  dd 99 e8 63 da           |.t..1......c.|
0000002d
```

|グループ|内容(git write-tree)|  内容(write-tree)|意味|
|:-:|:-:|:-:|:-:|
|ヘッダ| tree | tree|`tree`オブジェクトを示す|
|ヘッダ| 0x20 | 0x20 | 空白|
|ヘッダ|37 | 37| コンテンツが37バイトであることを示す |
|コンテンツ| 100644 | 100664 | ファイルモード (10:通常ファイル, 0664: -rw-rw-r--)
|コンテンツ| init-db.c | init-db.c | ファイル名 |
|コンテンツ| 0x00 | 0x00 | 文字列終端 |
|コンテンツ|　25 dc 13 fe 10 1b 21 9f 74 00 7f 31 94 b7 87 dd 99 e8 63 da | 0e aa 05 39 19 e0 cc 40 0a b9 bc 40 d9 27 23 60 11 7e 69 78　 | init-db.cファイルのSHA1 |

`init-db.c`ファイルの管理に利用されているSHA1ハッシュ値以外は同一である。


## `tree`オブジェクトの内容を表示する
### `read-tree`で表示する

`read-tree`コマンドは`git ls-tree`に相当するコマンドである。
`write-tree`で作成された`tree`オブジェクトの内容を`read-tree`で確認する。

```terminal
$ ./read-tree 425ea63d64b33ac1618ec75f05e2056d238393a9
100664 init-db.c (0eaa053919e0cc400ab9bc40d9272360117e6978)
```

このコマンドはSHA1`425ea63d64b33ac1618ec75f05e2056d238393a9`で指定された`tree`オブジェクトの内容のうち、ファイルモード、ファイル名、ファイルのSHA1を表示する。

### `git ls-tree`で表示する

`git write-tree`コマンドで作成された`tree`オブジェクトをSHA1ハッシュで指定して内容を表示する。

```terminal
$ git ls-tree cdb399a49eb53d128dc4baa0197c0f3a72eaa970
100644 blob 25dc13fe101b219f74007f3194b787dd99e863da	init-db.c
```

`tree`オブジェクトで管理されているファイルタイプ`blob`が追加されている以外は同等の内容を出力する。



## コミットの管理

`git`のコミットとは、リポジトリの特定の時点でのスナップショットだと表現される。 コミットするという行為は、その時点でのファイルやディレクトリの状態を記録することを意味し、このスナップショットには、変更されたファイル、変更に関するメタデータ（著者、日付、コミットメッセージなど）、および前のコミットへの参照が含まれる。

これを実装の側面からみると、`git`においてコミットとは`commit`オブジェクトを作成して管理することである。

### `commit-tree`コマンドでコミット
`commit-tree`コマンドで新しい`commit`オブジェクト(SHA1:394426667836de6e4919ee0596d798207f707a81)を作成する。 

```terminal
$ echo '1st commit' | ./commit-tree 425ea63d64b33ac1618ec75f05e2056d238393a9
Committing initial tree 425ea63d64b33ac1618ec75f05e2056d238393a9
394426667836de6e4919ee0596d798207f707a81
```

#### リポジトリ更新の確認

`.dircache/objects`以下に新規ファイルが１つ追加される。
- .dircache/objects/39/4426667836de6e4919ee0596d798207f707a81

```terminal
$ ls -la .dircache/objects/*/*
-rw-rw-r-- 1 raiko raiko 644 12月 31 11:34 .dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
-rw-rw-r-- 1 raiko raiko 128 12月 31 18:08 .dircache/objects/39/4426667836de6e4919ee0596d798207f707a81
-rw-rw-r-- 1 raiko raiko  53 12月 31 16:15 .dircache/objects/42/5ea63d64b33ac1618ec75f05e2056d238393a9
```

新規追加されたファイルの内容を確認する。

```terminal
$ zlib-flate -uncompress < .dircache/objects/39/4426667836de6e4919ee0596d798207f707a81
commit 181tree 425ea63d64b33ac1618ec75f05e2056d238393a9
author raiko,,, <raiko@K230703-03> Sun Dec 31 18:08:27 2023
committer raiko,,, <raiko@K230703-03> Sun Dec 31 18:08:27 2023

1st commit
```

このコミットオブジェクトには、ツリーオブジェクトへの参照、作者とコミッタの情報、そして `1st commit` というコミットメッセージが含まれている。


### `git commit-tree`でコミット

`git commit-tree`コマンドを使ってコミットする場合を確認する。
`git commit-tree` コマンドが実行し新しい`commit`オブジェクトを作成する。

```terminal
$ git commit-tree -m "1st commit" cdb399a49eb53d128dc4baa0197c0f3a72eaa970
f87d6bb4b11d61e7356ed0a4c223015d5dcfbd30
```

このコマンドは、指定された`tree`オブジェクト（cdb399a49eb53d128dc4baa0197c0f3a72eaa970）に対してコミットを行い、新規生成された`commit`オブジェクトのSHA-1ハッシュ（f87d6bb4b11d61e7356ed0a4c223015d5dcfbd30）を出力する。

#### リポジトリ更新の確認

`commit-tree`コマンドの場合と同様に`tree`オブジェクトのファイルが新規に作成される。
- .git/objects/f8/7d6bb4b11d61e7356ed0a4c223015d5dcfbd30

```terminal
$ ls -la .git/objects/*/*
-r--r--r-- 1 raiko raiko 656 12月 31 14:46 .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da
-r--r--r-- 1 raiko raiko  54 12月 31 17:05 .git/objects/cd/b399a49eb53d128dc4baa0197c0f3a72eaa970
-r--r--r-- 1 raiko raiko 139 12月 31 18:25 .git/objects/f8/7d6bb4b11d61e7356ed0a4c223015d5dcfbd30
```

新規作成されたファイルの内容を確認する。

```terminal
$ zlib-flate -uncompress < .git/objects/f8/7d6bb4b11d61e7356ed0a4c223015d5dcfbd30
commit 211tree cdb399a49eb53d128dc4baa0197c0f3a72eaa970
author Raiko Funakami <raiko.funakami@access-company.com> 1704014729 +0900
committer Raiko Funakami <raiko.funakami@access-company.com> 1704014729 +0900

1st commit
```

`commit`オブジェクトにはツリーオブジェクトへの参照、作者とコミッタの情報、そしてコミットメッセージ`1st commit`が含まれていることが確認できる。ユーザ名、e-mailアドレス、時間の保存方法に違いがあるが、基本的に同等の内容が保存されている。


## 差分の表示

作業ディレクトリとインデックスの間の変更点を表示する

### `show-diff`で差分表示
`show-diff`コマンドは`git diff`コマンドに相当する。
オリジナルの`show-diff`コマンドには不具合があるので修正して利用した。

変更が存在しない場合
```terminal
./show-diff
init-db.c: ok
```
`ok`と表示する。

`init-db.c`に `/* コメントを追加 */` という行を追加して`show-diff`コマンドを実行する。

```terminal
$ ./show-diff
--- /tmp/show_diff.xlHi4X	2023-12-31 18:51:28.883251106 +0900
+++ init-db.c	2023-12-31 18:36:23.596840843 +0900
@@ -1,5 +1,7 @@
 #include "cache.h"

+/* コメントを追加 */
+
 int main(int argc, char **argv)
 {
 	char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
```
`/* コメントを追加 */` と空行が追加されていることが検知されている。


### `git diff`で差分表示

`init-db.c`に `/* コメントを追加 */` という行を追加して`git diff`コマンドを実行する。

```terminal
$ git diff
diff --git a/init-db.c b/init-db.c
index 25dc13f..192daa2 100644
--- a/init-db.c
+++ b/init-db.c
@@ -1,5 +1,7 @@
 #include "cache.h"

+/* コメントを追加 */
+
 int main(int argc, char **argv)
 {
        char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
```

`show-diff`と`git diff`の差分表示は、ほぼ同等の出力を行うが確認できる。