阅读 git 第一版源码有利于加深对 git 的理解,也可以通过阅读优雅的代码提高个人的编程技能。
准备工作
准备源码
下载 git 源码
git clone https://github.com/git/git.git
查询第一次提交的记录
git log --reverse
切换到最初版本
git checkout e83c5163316
安装库
程序使用到 sha 函数库 和 zlib 函数库:
sudo apt install zlib1g-dev
sudo apt install libssl-dev
相关函数库的使用示例参照:
编译
修改Makefile,在Makefile 的 LIBS 变量后添加 -lz -cyrpto,如下
LIBS= -lssl -lz -lcrypto
编译
make all
生成以下 7 个可执行程序。
- init-db
- update-cache
- write-tree
- commit-tree
- read-tree
- cat-file
- show-diff
相关概念
第一版的git中有三种对象,关系如下图所示:
- blob : 即普通文件被压缩后的格式。
- tree : 记录当前已经添加的文件信息,根据 index 文件生成。
- commit : 记录提交的 tree 信息以及作者和日志等其他信息。
完整的命令执行流程
$ ./init-db
defaulting to private storage area
$ ./update-cache file.txt
$ ./write-tree
f6edc0f50407d4dc262fe150f34cc71050548e68
$ ./commit-tree f6edc0f50407d4dc262fe150f34cc71050548e68 < changelog
Committing initial tree f6edc0f50407d4dc262fe150f34cc71050548e68
1a3eba95a112f5b6c47d809b79055e177b6c64c1
$ ./read-tree f6edc0f50407d4dc262fe150f34cc71050548e68
100664 file.txt (0a3b8a9755adc2ed0776337ce2ee6b144bf854c2)
$ ./cat-file 0a3b8a9755adc2ed0776337ce2ee6b144bf854c2
temp_git_file_Lfe8J6: blob
$ cat temp_git_file_Lfe8J6
hello world!
$ ./show-diff
file.txt: ok
详细每步分析
-
init-db: 初始化 git 仓库,会创建一个目录结构,用于存放压缩后的文件和提交记录。相当于
git init
。仓库的目录结构如下图:
./.dircache/ └── objects ├── 00 ├── 01 ├── 02 ├── 03 ├── 04 ├── 05 ├── 06 ... ├── fa ├── fb ├── fc ├── fd ├── fe └── ff
-
update-cache 用于将文件添加到仓库中,并将记录写入staging index。相当于
git add
。$ ./update-cache file.txt
程序会将文件添加一个blob 头,再压缩,然后求sha1值。以前两位作为文件目录,后面的值作为文件名。比如创建文件 file.txt。
echo "hello world!" > file.txt
程序会将文件重写如下:
blob 13^@hello world!
再压缩,求sha1 哈希,得到的值
0a3b8a9755adc2ed0776337ce2ee6b144bf854c2
其中 0a为文件目录,3b8a9755adc2ed0776337ce2ee6b144bf854c2 为文件名。命令执行过后,仓库的目录结构下图:
./.dircache/ ├── index └── objects ├── 00 ├── 01 ... ├── 09 ├── 0a │ └── 3b8a9755adc2ed0776337ce2ee6b144bf854c2 ├── 0b ├── 0c ... ├── fe └── ff
-
write-tree: 根据 index文件的内容生成一个 tree 对象,并将其加入仓库。tree对象包含着提交的各个文件的文件名和对应的 sha1 值。
$ ./write-tree f6edc0f50407d4dc262fe150f34cc71050548e68
命令执行过后,仓库的目录结构下图:
./.dircache/ ├── index └── objects ├── 00 ├── 01 ... ├── 09 ├── 0a │ └── 3b8a9755adc2ed0776337ce2ee6b144bf854c2 ├── 0b ├── 0c ... ├── f5 ├── f6 │ └── edc0f50407d4dc262fe150f34cc71050548e68 ├── f7 ├── f8 ... ├── fe └── ff
-
commit-tree: 提交这个tree 对象,生成一条提交记录,并加入仓库,这条提交记录指向tree 对象,并且包含相应的作者信息和日志。
$ ./commit-tree f6edc0f50407d4dc262fe150f34cc71050548e68 < changelog Committing initial tree f6edc0f50407d4dc262fe150f34cc71050548e68 1a3eba95a112f5b6c47d809b79055e177b6c64c1
命令执行过后,仓库的目录结构下图:
.dircache/ ├── index └── objects ├── 00 ├── 01 ... ├── 09 ├── 0a │ └── 3b8a9755adc2ed0776337ce2ee6b144bf854c2 ├── 0b ├── 0c ... ├── 19 ├── 1a │ └── 3eba95a112f5b6c47d809b79055e177b6c64c1 ├── 1b ├── 1c ... ├── f5 ├── f6 │ └── edc0f50407d4dc262fe150f34cc71050548e68 ├── f7 ├── f8 ... ├── fe └── ff
-
read-tree: 用于输出指定的 tree 对象内容。
$ ./read-tree f6edc0f50407d4dc262fe150f34cc71050548e68 100664 file.txt (0a3b8a9755adc2ed0776337ce2ee6b144bf854c2)
-
cat-file: 通过 sha1 值去git仓库读取相应的文件,解压缩并生成一个临时文件。使用普通的文本工具可以读取其内容。相当于
git show
。$ ./cat-file 0a3b8a9755adc2ed0776337ce2ee6b144bf854c2 temp_git_file_Lfe8J6: blob $ cat temp_git_file_Lfe8J6 hello world!
-
show-diff: 通过比对当前目录下和index记录的同名文件不同。相当于
git diff
。$ ./show-diff file.txt: ok
参考: