源码解析:Git的第一个提交是什么样的?
- 2020-09-22 11:58:00
- 刘大牛 转自文章
- 537
阿里妹导读:经过不断地迭代,如今Git的功能越来越完善和强大。然而Git的第一个提交源码仅约1000行,当时的Git实现了哪些功能?本文将从源码开始,分析其核心思想,挖掘背后优秀的设计原理。
前言
编译
获取源码
# 获取 git 源码$ git clone https://github.com/git/git.git# 查看第一个提交$ git log --date-order --reversecommit e83c5163316f89bfbde7d9ab23ca2e25604af290Author: Linus Torvalds <torvalds@ppc970.osdl.org>Date: Thu Apr 7 15:13:13 2005 -0700Initial revision of "git", the information manager from hell# 变更为第一个提交,指定commit-id$ git checkout e83c5163316f89bfbde7d9ab23ca2e25604af290
文件结构
$ tree -h.├── [2.4K] cache.h├── [ 503] cat-file.c # 查看objects文件├── [4.0K] commit-tree.c # 提交tree├── [1.2K] init-db.c # 初始化仓库├── [ 970] Makefile├── [5.5K] read-cache.c # 读取当前索引文件内容├── [8.2K] README├── [ 986] read-tree.c # 读取tree├── [2.0K] show-diff.c # 查看diff内容├── [5.3K] update-cache.c # 添加文件或目录└── [1.4K] write-tree.c # 写入到tree# 统计代码行数,总共1089行$ find . "(" -name "*.c" -or -name "*.h" -or -name "Makefile" ")" -print | xargs wc -l...1089 total
编译
$ git diff ./Makefile...-LIBS= -lssl+LIBS= -lssl -lz -lcrypto...
# 编译$ make
源码分析
Write programs that do one thing and do it well. ——Unix philosophy
init-db:初始化仓库
$ init-db
创建目录:.dircache。 创建目录:.dircache/objects。 在 .dircache/objects 中创建了从 00 ~ ff 共256个目录。
# 运行init-db初始化仓库$ init-dbdefaulting to private storage area# 查看初始化后的目录结构$ tree . -a.└── .dircache # git工作目录└── objects # objects文件├── 00├── 01├── 02├── ...... # 省略├── fe└── ff258 directories, 0 files
update-cache:添加文件或目录
$ update-cache <file> ...
读取并解析索引文件 :.dircache/index。 遍历多个文件,读取并生成变更文件信息(文件名称、文件内容sha1值、日期、大小等),写入到索引文件中。 遍历多个文件,读取并压缩变更文件,存储到objects文件中,该文件为blob对象。
# 新增README.md文件$ echo "hello git" > README.md# 提交$ update-cache README.md# 查看索引文件$ hexdump -C .dircache/index00000000 43 52 49 44 01 00 00 00 01 00 00 00 af a4 fc 8e |CRID............|00000010 5e 34 9d dd 31 8b 4c 8e 15 ca 32 05 5a e9 a4 c8 |^4..1.L...2.Z...|00000020 af bd 4c 5f bf fb 41 37 af bd 4c 5f bf fb 41 37 |..L_..A7..L_..A7|00000030 00 03 01 00 91 16 d2 04 b4 81 00 00 ee 03 00 00 |................|00000040 ee 03 00 00 0a 00 00 00 bb 12 25 52 ab 7b 40 20 |..........%R.{@ |00000050 b5 f6 12 cc 3b bd d5 b4 3d 1f d3 a8 09 00 52 45 |....;...=.....RE|00000060 41 44 4d 45 2e 6d 64 00 |ADME.md.|00000068# 查看objects内容,sha1值从索引文件中获取$ cat-file bb122552ab7b4020b5f612cc3bbdd5b43d1fd3a8temp_git_file_61uTTP: blob$ cat ./temp_git_file_RwpU8bhello git
cat-file:查看objects文件内容
$ cat-file <sha1>
根据入参sha1值定位objects文件,比如.dircache/objects/46/4b392e2c8c7d2d13d90e6916e6d41defe8bb6a 读取该objects文件内容,解压得到真实数据。 写入到临时文件 temp_git_file_XXXXXX(随机不重复文件)。
# cat-file 会把内容读取到temp_git_file_rLcGKX$ cat-file 82f8604c3652fa5762899b5ff73eb37bef2da795temp_git_file_tBTXFM: blob# 查看 temp_git_file_tBTXFM 文件内容$ cat ./temp_git_file_tBTXFMhello git!
show-diff:查看diff内容
$ show-diff
读取并解析索引文件:.dircache/index。 循环遍历变更文件信息,比较工作区中的文件信息和索引文件中记录的文件信息差异。
无差异,显示 <file-name>: ok。 有差异,调用 diff 命令输出差异内容。
# 创建文件并提交到暂存区$ echo "hello git!" > README.md$ update-cache README.md# 当前无差异$ show-diffREADME.md: ok# 更改README.md$ echo "hello world!" > README.md# 查看diff$ show-diffREADME.md: 82f8604c3652fa5762899b5ff73eb37bef2da795--- - 2020-08-31 17:33:50.047881667 +0800+++ README.md 2020-08-31 17:33:47.827740680 +0800@@ -1 +1 @@-hello git!+hello world!
write-tree:写入到tree
$ write-tree
读取并解析索引文件:.dircache/index。 循环遍历变更文件信息,按照指定格式编排变更文件信息及内容。 压缩并存储到objects文件中,该object文件为tree对象。
# 提交$ write-treec771b3ab2fe3b7e43099290d3e99a3e8c414ec72# 查看objects内容$ cat-file c771b3ab2fe3b7e43099290d3e99a3e8c414ec72temp_git_file_r90ft5: tree$ cat ./temp_git_file_r90ft5100664 README.md��`L6R�Wb��_�>�{�-��
read-tree:读取tree
$ read-tree <sha1>
解析sha1值。 读取对应sha1值的object对象。 输出变更文件的属性、路径、sha1值。
# 提交$ write-treec771b3ab2fe3b7e43099290d3e99a3e8c414ec72# 读取tree对象$ read-tree c771b3ab2fe3b7e43099290d3e99a3e8c414ec72100664 README.md (82f8604c3652fa5762899b5ff73eb37bef2da795)
commit-tree:提交tree
$ commit-tree <sha1> [-p <sha1>]* < changelog
参数解析。 获取用户名称、用户邮件、提交日期。 写入tree信息。 写入parent信息。 写入author、commiter信息。 写入comments(注释)。 压缩并存储到objects文件中,该object文件为commit对象。
# 写入到tree$ write-treec771b3ab2fe3b7e43099290d3e99a3e8c414ec72# 提交tree$ echo "first commit" > changelog$ commit-tree c771b3ab2fe3b7e43099290d3e99a3e8c414ec72 < changelogCommitting initial tree c771b3ab2fe3b7e43099290d3e99a3e8c414ec727ea820bd363e24f5daa5de8028d77d88260503d9# 查看commit对象内容$ cat-file 7ea820bd363e24f5daa5de8028d77d88260503d9temp_git_file_CIfJsg: commit$ cat temp_git_file_CIfJsgtree c771b3ab2fe3b7e43099290d3e99a3e8c414ec72author Xiaowen Xia <chenan.xxw@aos-hw09> Tue Sep 1 10:56:16 2020committer Xiaowen Xia <chenan.xxw@aos-hw09> Tue Sep 1 10:56:16 2020first commit
设计原理
Write programs to work together. ——Unix philosophy
blob 对象:保存着文件快照。 tree 对象:记录着目录结构和 blob 对象索引。 commit 对象:包含着指向前述 tree 对象的指针和所有提交信息。
工作区(workspace):我们直接修改代码的地方。 暂存区(index):数据暂时存放的区域,用于在工作区和版本库之间进行数据交流。 版本库(commit history):存放已经提交的数据。
objects 文件
$ tree .git/objects.git/objects├── 02│ └── 77ec89d7ba8c46a16d86f219b21cfe09a611e1├── ...... # 省略├── be│ ├── adb5bac00c74c97da7f471905ab0da8b50229c│ └── ee7b5e8ab6ae1c0c1f3cfa2c4643aacdb30b9b├── ...... # 省略├── c9│ └── f6098f3ba06cf96e1248e9f39270883ba0e82e├── ...... # 省略├── cf│ ├── 631abbf3c4cec0911cb60cc307f3dce4f7a000│ └── 9e478ab3fc98680684cc7090e84644363a4054├── ...... # 省略└── ff
blob 对象
# 查看 blob 对象内容$ cat-file 82f8604c3652fa5762899b5ff73eb37bef2da795temp_git_file_tBTXFM: blob$ cat ./temp_git_file_tBTXFMhello git!
tree 对象
# 查看 tree 对象内容$ cat-file c771b3ab2fe3b7e43099290d3e99a3e8c414ec72temp_git_file_r90ft5: tree$ cat ./temp_git_file_r90ft5100664 README.md��`L6R�Wb��_�>�{�-��
commit 对象
# 查看 commit 对象内容$ cat-file 7ea820bd363e24f5daa5de8028d77d88260503d9temp_git_file_CIfJsg: commit$ cat temp_git_file_CIfJsgtree c771b3ab2fe3b7e43099290d3e99a3e8c414ec72author Xiaowen Xia <chenan.xxw@aos-hw09> Tue Sep 1 10:56:16 2020committer Xiaowen Xia <chenan.xxw@aos-hw09> Tue Sep 1 10:56:16 2020first commit
索引文件
索引文件默认路径为:.dircache/index。索引文件用来存储变更文件的相关信息,当运行 update-cache 时会添加变更文件的信息到索引文件中。
$ hexdump -C .dircache/index00000000 43 52 49 44 01 00 00 00 01 00 00 00 ae 73 c4 f2 |CRID.........s..|00000010 ce 32 c9 6f 13 20 0d 56 9c e8 cf 0d d3 75 10 c8 |.2.o. .V.....u..|00000020 94 ad 4c 5f f4 5c 42 06 94 ad 4c 5f f4 5c 42 06 |..L_.B...L_.B.|00000030 00 03 01 00 91 16 d2 04 b4 81 00 00 ee 03 00 00 |................|00000040 ee 03 00 00 0b 00 00 00 a3 f4 a0 66 c5 46 39 78 |...........f.F9x|00000050 1e 30 19 a3 20 42 e3 82 84 ee 31 54 09 00 52 45 |.0.. B....1T..RE|00000060 41 44 4d 45 2e 6d 64 00 |ADME.md.|
哈希算法
#include <openssl/sha.h>static int verify_hdr(struct cache_header *hdr, unsigned long size){SHA_CTX c;unsigned char sha1[20];/* 省略 *//* 计算索引文件头sha1值 */SHA1_Init(&c);SHA1_Update(&c, hdr, offsetof(struct cache_header, sha1));SHA1_Update(&c, hdr+1, size - sizeof(*hdr));SHA1_Final(sha1, &c);/* 省略 */return 0;}
总结与思考
Use software leverage to your advantage. ——Unix philosophy
好的代码不是写出来的,是改出来的
关于细节
关于代码质量
异常处理不完善,经常出现段错误(SegmentFault)。
存在几处内存泄漏的地方,比如 write-tree.c > main函数 > buffer内存块 。
从索引文件中读取到的变更文件信息使用数组存储,涉及到了比较多的申请释放操作,性能上是有损失的,可以优化成链表存储。
招聘
参考资料
Git官方网站:https://git-scm.com Git官方文档中心:https://git-scm.com/doc Git官网的Git底层原理介绍:Git Internals - Git Objects zlib 官方网站:http://zlib.net 浅析Git存储—对象、打包文件及打包文件索引 (https://www.jianshu.com/p/923bf0485995) 深入理解Git - 一切皆commit (https://www.cnblogs.com/jasongrass/p/10582449.html) 深入理解Git - Git底层对象(https://www.cnblogs.com/jasongrass/p/10582465.html)
阿里技术
分享阿里巴巴的技术创新、实战案例、经验总结,内容同步于微信公众号“阿里技术”。
发表评论
文章分类
联系我们
| 联系人: | 透明七彩巨人 |
|---|---|
| Email: | weok168@gmail.com |
| 网址: | ai.tmqcjr.com |