LoginSignup
0
0

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

Last updated at Posted at 2024-01-01

はじめに

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

ソースコードと挙動を確認しての所見

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

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

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

ソースコードを取得する

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

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の不具合修正

ビルド

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で動作確認を行った。

$ 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
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つのコマンドの使い方を確認する。

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

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

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というファイルをリポジトリで管理する。

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ファイルに登録したファイルの内容が保存される。

3.インデックスに登録したinit-db.cの内容を一時ファイルへ書き込む
$ ./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のファイルと同一の内容表示される。

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

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

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

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

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

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

作成した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コマンドで確認できる。

7.作業ディレクトリとインデックスの間の変更点を表示する
./show-diff
init-db.c: ok

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

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

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

リポジトリの初期化

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

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 init コマンドでリポジトリを初期化
$ 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コマンドで指定されたファイルをインデックスに登録する。

init-db.c をインデックスに登録する
$ ./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)がバイナリファイルとして保存される。

cache.h
#define CACHE_SIGNATURE 0x44495243	/* "DIRC" */
struct cache_header {
	unsigned int signature;
	unsigned int version;
	unsigned int entries;
	unsigned char sha1[20];
};
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"と表示されることに注意。

$ 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の内容が保存されていることが確認できる。

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 1198blobblobオブジェクトであることを示し、1198init-db.cのバイト数を示している。

$ wc -c ./init-db.c
1198 ./init-db.c

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

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

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ファイル
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
.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に相当するコマンドだと考えられる。

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

$ cat temp_git_file_bpsQTQ
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オブジェクトを作成した場合との違いを確認する。

実行済み (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
treeオブジェクトを作成する
$ 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-diffgit diffの差分表示は、ほぼ同等の出力を行うが確認できる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0