Gitは最初1244行しかなかった
概要
Junio C Hamanoさんに興味を持って調べていると、Linusさんが書いたGitの初版は1244行ということが分かりました。Gitの初版について、軽く行数の確認とビルドチャレンジをして、あまり調べずに動かしながら機能を推測してみました。
はじめに
Highlights from Git 2.39 の冒頭で登場するcommit数が一番多い方「Junio C Hamano」さんを知らなかったので調べてみました。
gihyoのインタビュー記事が面白かったです。Junio C HamanoさんはGitのメンテナで、LinusさんからGitのメンテナを引き継いだすごい方だということを知りました。
このgihyoのインタビュー記事の中で「MLで流れてきたGitのコード行数は1244行だった」というところが気になりました。調べてみると、2020年にTwitterでRui Ueyamaさんへのリプライでも「最初の版は1244行しかなかったんだけどね。」と述べられています。
Gitは現在も活発に開発が続けられている巨大ソフトウェアで、私も日々お世話になっています。どんな巨大ソフトウェアも最初は短い行数からスタートするということは頭で分かってもなかなか直観的にしっくりこないので、興味が湧いてきました。
それでは git clone https://github.com/git/git.git
して調べていきます。
目次
- 環境
- 最初のコミットを調べる
- 最初のコミットの行数を調べる
- 初版のGitをビルドする
- 実際に動かしてみる
- 推測
- 今後
環境
今回は以下の環境で調べました。
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy
$ uname -a
Linux <hostname> 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
...
最初のコミットを調べる
最初のコミットを調べます。
今回はMLを見にいくことはせず、GitHubのミラーの最初を見にいくことにしました。また、 README
は最初にあるだろうし更新も少ないだろうと考えて、GitHubのwebページの README.md
のhistoryを眺めていきます。
一番下の「Renamed from README (Browse History)」をクリックしてさらに辿っていきます。
どうやらそれらしいものを見つけます。
最初のコミットへのリンク: https://github.com/git/git/tree/e83c5163316f89bfbde7d9ab23ca2e25604af290
(関係ないけど最初のコミットが巡礼スポットになっていて、関係ないコメントを残す人がたくさんいてうむむという気持ちに)
commit hashが分かったので、手元にcloneしてきたリポジトリをcheckoutします。
$ git checkout e83c5163316f89bfbde7d9ab23ca2e25604af290
最初のコミットの行数を調べる
行数を計測します。
$ find . -type d -name '.git' -prune -o -type f -print | xargs wc -l
43 ./read-tree.c
81 ./show-diff.c
93 ./cache.h
248 ./update-cache.c
259 ./read-cache.c
51 ./init-db.c
172 ./commit-tree.c
168 ./README
23 ./cat-file.c
66 ./write-tree.c
40 ./Makefile
1244 total
うおお!本当に1244行です!感動!
どうやらこれはOKっぽいので、MLを調べずにこれが初版のGitということにしてしまいます。
今度はこの初版のGitをビルドしてみようと思います。
ちなみに現在のGitの行数は...
$ find . -type d -name '.git' -prune -o -type f -print | xargs wc -l
...
1397709 total
139万行!?すごいですね
初版のGitをビルドする
Makefile
があるのでmakeしてみます。...うまくいきません。ビルドバトル開始です。
一時間ほど格闘して、以下のようにしてビルドできました。(もし間違っているところあればアドバイスいただけると嬉しいです!)
-
git diff -- Makefile
の結果
diff --git a/Makefile b/Makefile
index a6bba79ba1..6bdc90fdbd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,14 @@
-CFLAGS=-g
+CFLAGS=-g -std=c99 -fcommon
CC=gcc
PROG=update-cache show-diff init-db write-tree read-tree commit-tree cat-file
all: $(PROG)
-install: $(PROG)
- install $(PROG) $(HOME)/bin/
+# install: $(PROG)
+# install $(PROG) $(HOME)/bin/
-LIBS= -lssl
+LIBS= -lssl -lz -lcrypto
init-db: init-db.o
-
git diff -- show-diff.c
の結果
diff --git a/show-diff.c b/show-diff.c
index b8522886a1..bfada78a58 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)
-
git diff -- update-cache.c
の結果
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;
Warningはたくさん出ますが、一応コンパイルできたので動かしてみます。
実際に動かしてみる
まずはあまりコードを読まずにとりあえず動かしてみることにします。
update-cache
, show-diff
, init-db
, write-tree
, read-tree
, commit-tree
, cat-file
の7つのコマンドがあります。
色々試した結果、以下のように使えそうです。
-
init-db
はgit init
相当?- カレントディレクトリに
.dircache
という.git
みたいなフォルダが作られて、その中にobjectsがあります。
- カレントディレクトリに
- 適当にファイルを作り、
update-cache
してwrite-tree
すると、hash値が返ってくる -
write-tree
の結果のhash値を渡してread-tree
すると、見慣れた数字100664が!
$ ../read-tree 506a71adf66631615fb1735bc039d237b65a2994
100664 hoge.txt (cf31c276cff9de473ffbedf78ce5b464ef240a7d)
- さらに、上の
read-tree
で返ってきた値をcommit-tree
に渡すとcommitっぽいことができる!
$ echo 'changelog #1' | ../commit-tree cf31c276cff9de473ffbedf78ce5b464ef240a7d
Committing initial tree cf31c276cff9de473ffbedf78ce5b464ef240a7d
db11de0f8cb21c126c3a701a20aa6dd040367637
推測
- commitの概念、git objectの概念は最初から存在していた
- ステージングの概念はまだっぽい?(gihyoのインタビューでもそのような話があったような気がします)
今後
-
README
を読みたいです。 - せっかく1244行しかないので、コードを読み解いていきたいと思います。
- gihyoのインタビューでstageの概念がLinusさんから提案された、みたいな話があったので、commit logをたどりながら調べてみたいです。
Discussion