はじめに
"Gitは最初1244行しかなかった" とのこと。手軽に読めそうな規模のソースコードなので初期のgit
の挙動とソースコードを確認する。
ソースコードと挙動を確認しての所見
最初のgit
で実装されている管理用オブジェクトやSHA1ハッシュを利用したgit
の参照の仕組みは基本的に最新のgit
と同様だ。特にSHA1ハッシュと利用することで実現するソースコード管理の詳細は興味深い。複雑になった現状のgit
のソースコードよりも、初期のgit
のソースコードでgit
の実装を学ぶもの面白いかもしれない。
ちなみに最新の git v2.29
ではSHA256が実験的にサポートされたとのこと。ただしSHA1と互換性はない。
図を使って説明は次回以降に記載する。
ソースコードを取得する
git
の最初のバージョンの挙動をソースコードを読みながら確認する。
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の不具合修正
ビルド
$ 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で動作確認を行った。
$ 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
修正内容
修正内容は下記の通り
$ 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)コマンドの初期バージョンと言えるかもしれない。
コマンド | 対応する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 オブジェクトから元のソースコード全体を高速に取り出すことができる。 |
最初のgit
コマンドの使用例
最初のgit
には次の7コマンドが存在する。
- init-db
- update-cache
- cat-file
- write-tree
- read-tree
- commit-tree
- show-diff
先ずはこれら7つのコマンドの使い方を確認する。
$ ./init-db
defaulting to private storage area
.dircache
ディレクトリがリポジトリとして作成される。最初のバージョンでは.git
ディレクトリではない。
$ 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
というファイルをリポジトリで管理する。
$ ./update-cache init-db.c
update-cache
コマンドで、その時点のinit-db.c
の内容をblob
オブジェクトとしてファイルに保存する。そのファイルへの参照をインデックスに登録して管理する。
$ ls .dircache/objects/*/*
.dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
./update-cache init-db.c
コマンドで登録したinit-db.c
ファイルはSHA1ハッシュ値 0eaa053919e0cc400ab9bc40d9272360117e6978
で管理される。ハッシュ値を分割して、0e
ディレクトリのaa053919e0cc400ab9bc40d9272360117e6978
ファイルに登録したファイルの内容が保存される。
$ ./cat-file 0eaa053919e0cc400ab9bc40d9272360117e6978
temp_git_file_bpsQTQ: blob
インデックスに登録したある時点のファイルの内容を確認したいとき、blob
オブジェクトを示すSHA1ハッシュ値を示すことで直ちに取り出すことができる。cat-file
コマンドは取り出したファイルを標準出力に出力するのではなく、一時ファイルの保存する。
$ 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
のファイルと同一の内容表示される。
$ ./write-tree
425ea63d64b33ac1618ec75f05e2056d238393a9
コミットはtree
オブジェクトを対象に行うためtree
オブジェクトを作成するwrite-tree
コマンドが用意されている。update-cache
コマンドでインデックスに登録して管理している内容を基にtree
オブジェクトを作成する。425ea63d64b33ac1618ec75f05e2056d238393a9
は作成したtree
オブジェクトを管理するSHA1ハッシュ値である。
$ ./read-tree 425ea63d64b33ac1618ec75f05e2056d238393a9
100664 init-db.c (0eaa053919e0cc400ab9bc40d9272360117e6978)
作成したtree
オブジェクトの内容は、確認したいtree
オブジェクトのSHA1ハッシュ値を指定して確認できる。このコマンドではtree
オブジェクトにinit-db.c
(SHA1ハッシュ値:0eaa053919e0cc400ab9bc40d9272360117e6978)が含まれていることが確認できる。
$ echo '1st commit' | ./commit-tree 425ea63d64b33ac1618ec75f05e2056d238393a9
Committing initial tree 425ea63d64b33ac1618ec75f05e2056d238393a9
394426667836de6e4919ee0596d798207f707a81
コミットする ことは commit
オブジェクトを作成すること だと確認できる。
commit
オブジェクトは指定したtree
オブジェクトを対象に作成される。commitオブジェクトにはtreeオブジェクトへの参照とコミッター情報やコミットログが含まれる。
394426667836de6e4919ee0596d798207f707a81
は作成したcommit
オブジェクトを管理するSHA1ハッシュ値である。
$ ./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
コマンドで確認できる。
./show-diff
init-db.c: ok
show-diff
コマンドは作業ディレクトリとインデックスの間の変更点を表示する。変更がない場合はok
と表示する。最初のgit
のshow-diff
コマンドはそのままでは動作に不具合があるため修正が必要になる。
最初のgit
コマンドの動作確認
最新のgit
コマンドの動作と対比しながら動作確認を進める
リポジトリの初期化
init-db
コマンドでリポジトリを作成する。
$ ./init-db
defaulting to private storage area
init-db
で作成したリポジトリ
init-db
コマンドでリポジトリは.dircache
ディレクトに作成される。
.dircache
ディレクトリ内部に objects
ディレクトリが用意され、その下に 00
から ff
までのディレクトリが用意される。objects
ディレクトリで管理するファイルはSHA1ハッシュ値で管理され、ハッシュ値の上位2桁をフォルダ名、下位38桁をファイル名と利用する。00
からff
まで256のフォルダで多数のファイルを効率よく管理することを意図していると考えられる。
この仕組みは最新のgit
と同一である。
$ 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
ディレクトリ内部に複数のファイルとディレクトリが用意される。その1つに同名の objects
ディレクトリが存在する。初期化した段階ではinfo
と pack
のディレクトリが用意されており、init-db
で初期化した objects
ディレクトリと一見様子が異なるように見える。
しかし、update-cache
以降の章から確認できるようにblob
オブジェクトやtree
オブジェクト、commit
オブジェクトを管理する仕組みは初期から同一である。
$ git --version
git version 2.34.1
$ git init
$ 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
コマンドで指定されたファイルをインデックスに登録する。
$ ./update-cache init-db.c
このコマンドを実行すると、実行時点のinit-db.c
ファイルがblob
オブジェクトとしてファイルに保存され、そのファイルへの参照(SHA1ハッシュ)がindex
ファイルに登録される。
ここで作成されるblob
オブジェクトはinit-db.c
のファイル内容にヘッダ情報を追加した上でzlibでdeflate(圧縮)したものである。
リポジトリの状態変化
update-cache
コマンドを実行した後のリポジトリ内部の状態変化を確認する。
$ 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でリポジトリを作成した状態)と比較して下記の2ファイルが追加された。
- .dircache/index
- .dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
追加された2ファイルの内容を確認する。
index
ファイル
index
ファイルにはヘッダ情報(cache_header)とobjects
ディレクトリで管理されるファイル情報(cache_entry)がバイナリファイルとして保存される。
#define CACHE_SIGNATURE 0x44495243 /* "DIRC" */
struct cache_header {
unsigned int signature;
unsigned int version;
unsigned int entries;
unsigned char sha1[20];
};
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"と表示されることに注意。
$ 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
コマンドで展開して内容を確認できる。
$ zlib-flate -uncompress < .dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
先頭に blob 1198
というキーワードが追加された形でinit-db.c
の内容が保存されていることが確認できる。
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
のバイト数を示している。
$ wc -c ./init-db.c
1198 ./init-db.c
git add
でファイルを管理する
git add
コマンドでinit-db.c
ファイルをインデックスに登録する
$ git add init-db.c
リポジトリの状態変化
git add
コマンドを実行した後のリポジトリ内部の状態の違いを確認する。
$ 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
)した時から次の2ファイルが追加されている。
- .git/index
- .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da
この挙動はupdate-cache
コマンドを実行した場合と同一である。
追加されたファイルの内容を確認する。
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
コマンドで確認する。
$ zlib-flate -uncompress < .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
に相当するコマンドだと考えられる。
$ ls .dircache/objects/*/*
.dircache/objects/0e/aa053919e0cc400ab9bc40d9272360117e6978
$ ./cat-file 0eaa053919e0cc400ab9bc40d9272360117e6978
temp_git_file_bpsQTQ: blob
$ 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
コマンドに相当する。
$ ./write-tree
425ea63d64b33ac1618ec75f05e2056d238393a9
このコマンドで現在のインデックスの内容を基にtree
オブジェクトを作成する。tree
オブジェクトは.dircache/objects
ディレクトでblob
オブジェクトと同様に管理される。
リポジトリの更新
.dircache/objects
以下に新規ファイルが1つ追加された
- .dircache/objects/42/5ea63d64b33ac1618ec75f05e2056d238393a9
$ 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したファイルになっている。
$ 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
オブジェクトを作成した場合との違いを確認する。
$ git add init-db.c
$ ls -la .git/objects/*/*
-r--r--r-- 1 raiko raiko 656 12月 31 14:46 .git/objects/25/dc13fe101b219f74007f3194b787dd99e863da
$ git write-tree
cdb399a49eb53d128dc4baa0197c0f3a72eaa970
リポジトリの更新
git write-tree
コマンドで現在のインデックスの内容を基にtree
オブジェクトを新規作成する。
- .git/objects/cd/b399a49eb53d128dc4baa0197c0f3a72eaa970
$ 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
ファイルの更新内容を確認する。
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
オブジェクトの違い
$ 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
で確認する。
$ ./read-tree 425ea63d64b33ac1618ec75f05e2056d238393a9
100664 init-db.c (0eaa053919e0cc400ab9bc40d9272360117e6978)
このコマンドはSHA1425ea63d64b33ac1618ec75f05e2056d238393a9
で指定されたtree
オブジェクトの内容のうち、ファイルモード、ファイル名、ファイルのSHA1を表示する。
git ls-tree
で表示する
git write-tree
コマンドで作成されたtree
オブジェクトをSHA1ハッシュで指定して内容を表示する。
$ git ls-tree cdb399a49eb53d128dc4baa0197c0f3a72eaa970
100644 blob 25dc13fe101b219f74007f3194b787dd99e863da init-db.c
tree
オブジェクトで管理されているファイルタイプblob
が追加されている以外は同等の内容を出力する。
コミットの管理
git
のコミットとは、リポジトリの特定の時点でのスナップショットだと表現される。 コミットするという行為は、その時点でのファイルやディレクトリの状態を記録することを意味し、このスナップショットには、変更されたファイル、変更に関するメタデータ(著者、日付、コミットメッセージなど)、および前のコミットへの参照が含まれる。
これを実装の側面からみると、git
においてコミットとはcommit
オブジェクトを作成して管理することである。
commit-tree
コマンドでコミット
commit-tree
コマンドで新しいcommit
オブジェクト(SHA1:394426667836de6e4919ee0596d798207f707a81)を作成する。
$ echo '1st commit' | ./commit-tree 425ea63d64b33ac1618ec75f05e2056d238393a9
Committing initial tree 425ea63d64b33ac1618ec75f05e2056d238393a9
394426667836de6e4919ee0596d798207f707a81
リポジトリ更新の確認
.dircache/objects
以下に新規ファイルが1つ追加される。
- .dircache/objects/39/4426667836de6e4919ee0596d798207f707a81
$ 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
新規追加されたファイルの内容を確認する。
$ 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
オブジェクトを作成する。
$ git commit-tree -m "1st commit" cdb399a49eb53d128dc4baa0197c0f3a72eaa970
f87d6bb4b11d61e7356ed0a4c223015d5dcfbd30
このコマンドは、指定されたtree
オブジェクト(cdb399a49eb53d128dc4baa0197c0f3a72eaa970)に対してコミットを行い、新規生成されたcommit
オブジェクトのSHA-1ハッシュ(f87d6bb4b11d61e7356ed0a4c223015d5dcfbd30)を出力する。
リポジトリ更新の確認
commit-tree
コマンドの場合と同様にtree
オブジェクトのファイルが新規に作成される。
- .git/objects/f8/7d6bb4b11d61e7356ed0a4c223015d5dcfbd30
$ 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
新規作成されたファイルの内容を確認する。
$ 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
コマンドには不具合があるので修正して利用した。
変更が存在しない場合
./show-diff
init-db.c: ok
ok
と表示する。
init-db.c
に /* コメントを追加 */
という行を追加してshow-diff
コマンドを実行する。
$ ./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
コマンドを実行する。
$ 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
の差分表示は、ほぼ同等の出力を行うが確認できる。