浅议Git部分底层命令与上层命令之间的关系

深入理解Git的实现原理 - 马鸣谦 - 博客园 (cnblogs.com)

Git:改变世界的一次代码提交 - 知乎 (zhihu.com)

        首先谈谈本文面向的读者身份:如果您对于粗暴的使用git add、git commit和其他高级命令而完全不理解git的任何工作原理感到不满,我非常推荐您先阅读上面的两篇文章,它们分别从历史的角度和实践的角度讲述了git一些有趣的底层命令到底是如何运作的,让你得以一窥.git文件夹中那些40位的SHA-1代表的文件到底是怎么让git成为一个简明的数据库。如果这两篇文章的内容您已读完,是否产生了下面的认识:

1:Git底层命令主要有:

        git init(init-db这是当年linus的初版命令)、

        git hash-object(早期没有这个)、

        git cat-file(继承至今)、

        git update-index(update-cache初版)、

        git ls-files(早期没有)、

        git write-tree(继承至今)、

        git read-tree(继承至今)、

        git commit-tree(继承至今)、

        show-diff(不知道现在用哪个名字代替了,但现在是弃用了)

2:Git需要的数据库都放在.git的objects文件夹中,这个文件夹中的文件无论内容如何它们的共同点就是以一个SHA-1值命名,它们的内容都是原有内容经过zlib压缩后的结果(如下图所示,亲测tree类型文件只保存目录相关信息的内容也是zlib压缩后存放的,而不是直接存放),这也是git cat-file的作用——可以临时解压出文件内容给用户看,否则就是乱码。 

3:Git其实就是模仿UNIX做了一个简单的文件系统,git只抽象出了三种数据结构:blob、tree和commit,如果你想看git数据库文件的类型,git cat-file -t可以直接看。

        首先请明确:blob只记录文件的内容,所以blob类型的文件它的名字是由blob头加内容一起hash得到的,而它的内容就是文件的原内容zlib压缩得到的。blob不记录文件名字,不记录文件属性。

        然后:tree只记录属于自己文件夹下的文件和文件夹的属性,所以你会看到一个tree文件用cat-file能看到如下图所示的内容,如果当前目录下有子文件夹,那么子文件夹的内容由另一个tree文件来记录,形成嵌套结构。

        最后,commit是一个顶层结构,它会记录一个提交的作者和时间以及一些提交说明。 

4:现在我们终于可以讨论主题:底层命令与上层命令的一些关系

        我和朋友一起做了一系列实验,首先我们讨论了git add与git hash-object命令的关系,我们先用git add与hash-object操作了一个文件,然后发现二者得到的hash文件是完全一样的。但是以我个人分析,git add至少还有两种额外功能:首先add可以添加文件夹,而hash-object是无法对文件夹做hash的。然后add毕竟是一种暂存区操作,如果在此之前没有创建过暂存区,那么git add至少还集成了update-index的功能,先创建暂存区。有了暂存区之后add还需要更新暂存区的目录结构。

        现在我们要讨论commit与commit-tree命令的关系吗?也许你会发现:在add和commit命令之间似乎没有和tree有关的高级命令?那么生成tree类型文件的命令到底是在add时做还是在commit命令时做呢?我朋友做实验告诉我,tree的生成是发生在commit命令时,我觉得这也是一个更符合思考的结果——index区域告知用户当前目录结构的能力完全不是依赖于tree类型hash文件,而是只依靠.index文件,所以没必要在add时就建立tree文件。另外,无论怎样add,git并不保证你通过正常的git命令能够在多次没有commit的add之间进行文件恢复(虽然每次add确实会生成对应的hash文件,这让我们的确可以在多次add之间手动恢复文件),所以暂存区只需要记录和显示当前目录结构,而不管理几次add之间的目录结构,从这个角度也没必要建立tree文件。

        现在我们可以讨论commit与commit-tree命令之间的关系了,上面知乎专栏的文章说commit可以理解为write-tree与commit-tree的集成,我的理解也类似:commit-tree允许用户指定我要以哪一个tree文件作为这一次commit的根目录,也就是说我可以不提交工作区根目录,我还可以去提交它的子目录,而write-tree命令的作用就是把工作区根目录打包一个tree文件,而高级命令commit的作用是:打包当前工作区根目录并提交,所以当然就是commit-tree一下write-tree的运行结果喽。

        最后两段猜想成分多一些,仅供参考,如果想了解真实细节可以去读Git Pro,我没读过,恳请大佬们随时指正!

你可能感兴趣的:(git)