图解git原理
用了这么久git,你有没有想过git的原理是什么?为什么git做回退这么快?创建、切换分支也这么快?git的工作区、暂存区、本地仓库、远程仓库都分别对应怎样的实体?打开一个git仓,看看目录下的.git文件夹,所有秘密都藏在这个.git里面。
下面举个栗子说明。我先git init了一个空仓,然后创建了两个txt文件,目录结构如下:
--dir1
└ a.txt 内容是"hello"
--dir2
└ b.txt 内容是"world"
然后再git add;此时发生了如下几件事:
- git分别把各个文件经过计算得到SHA1值,并生成blob object(这种object的内容是文件经过无损压缩后的字节流),并存储到.git/objects目录下。
- 此外还会更新.git/index文件(即暂存区的索引文件),我们可以用git ls-files --stage来看看它当前的内容:
其中b6fc4c、04fea0这两串数字分别是a.txt和b.txt的SHA1值。
我们去.git/objects里找到这个b6fc4c,打开看看:
是串乱码。因为是被压缩过的。我们可以用git cat-file -t [sha1]来查看它的类型,用git cat-file -p [sha1]来查看它被压缩之前的原始内容:
现在我们执行git commit做一次提交。此时发生了如下几件事:
- git从此次commit修改的每个文件向上遍历,将其父目录、祖父目录...等等都重新生成tree object(这种object用于记录某个目录的文件结构,即目录树)
- git生成一个commit object(即一个提交,记录了根目录树的SHA1、父提交的SHA1、以及提交的commit message)
- git将分支索引文件中的内容替换为这个最新的commit的SHA1
我们从上往下看。先打开.git/HEAD文件(HEAD用来表示当前分支):
当前分支叫refs/heads/master。
我们再打开.git/refs/heads/master:
意思是master分支当前指向37de56这个提交。
那再用git cat-file来看看这个提交的内容:
这个提交指向tree 83c4ae,继续往下看:
tree 83c4ae记录了根目录的结构,它有两个子目录,dir1和dir2,分别对应tree 658293和tree
098076:
这两个tree又分别指向一个blob object。
此时来张图就一目了然了:
上图第1列(绿色)是第一次提交,
修改a.txt的内容,做第二次提交(蓝色),可以看到更新后的a.txt产生了一个新的blob 620ffd,而b.txt我没做修改,所以复用上次的blob 04fea0;并且a.txt的更新影响了其父目录dir1和其祖父目录(即根目录)的tree object的刷新。
将b.txt从dir2移动到dir1,做第三次提交(橙色),由于只是移动,所以没有新的blob产生,但由于我更新了目录结构,所以dir1目录以及根目录需要重新生成tree object。
综上:git使用.git中的各式各样的文件、以及文件之间的指向关系来管理整个仓库。
需要注意的几点:
- commit object会通过tree object来管理目录结构,但index文件不会指向任何tree object,它直接在自身的内容中记录每个文件的名字和所在的目录。
- 一旦执行了git add,只要你别删除.git目录,你被add进去的代码就不会丢失。之前我还傻傻以为git reset --hard后文件都没了,实际上.git/objects里对应的blob全都在。
参考资料:这才是真正的Git——Git内部原理揭秘! - 知乎 (zhihu.com)