设为首页 收藏本站
查看: 1079|回复: 0

[经验分享] 从.git文件夹探析git实现原理

[复制链接]

尚未签到

发表于 2018-1-13 08:36:37 | 显示全部楼层 |阅读模式
  git是一款分布式代码版本管理工具,通过git能够更加高效地协同编程。了解git的工作原理将有助于我们使用git工具更好地管理项目。通过了解.git文件夹中的文件组成,我们可以从一个角度去窥探git的实现原理。我们知道,在开始开发一个项目或加入一个项目时,需要创建一个新的仓库git init [options],或从远端克隆一个已经存在的仓库git clone [uri],除使用git init --bare创建一个“裸”仓库以外,所有创建的本地仓库都包含有一个.git文件夹,需要了解的是,“裸”仓库的内容就是.git文件夹中的内容。讨论“裸”仓库与实际仓库作用的异同不是我们现在讨论的重点,如需了解可翻阅相关文档。由此,git系统当中的的所有数据都存在于.git文件夹之中。

.git文件夹中的内容及作用
  打开.git文件夹后,通常有五个文件夹:


  • hooks文件夹,用于存储shell脚本,当执行某些git指令后,会触发存储在该文件夹下指定的shell脚本
  • info文件夹,用于存储该项目仓库的相关信息
  • logs文件夹,用于记录分支提交记录
  • objects文件夹,“key-value数据库”
  • refs文件夹,用于记录每个分支的最新提交结点以及tags
  我们需要着重关注logs文件夹、objests文件夹以及refs文件夹,通过这三个文件夹所存储的内容来分析git。在.git文件夹中,同样存在有一些文件,譬如HEAD、config、index等文件,其中HEAD文件用以记录当前仓库指向的项目提交结点,config文件中记录着仓库的配置信息,这些文件内容不是我们要讨论的重点。

objects文件夹,git中的“key-value数据库”
  在讨论objects文件夹的内容之前,我们需要明确存在于git系统中的三个实体,即“提交结点”“节点内容”“文件内容”
  
objects可以认为是一种“key-value数据库”,之所以将数据库打引号,是因为这个“git的数据库”不具备数据库的基本功能,而仅仅具备可以通过key值能够找到与之对应的value。
  提交结点实体,是整个git中的核心实体,提交节点中描述了提交节点之间的继承关系,即本次提交的内容是基于哪个或哪几个之前的提交的内容,提交结点实体之间的关系形成了一个DAG图,通过这个DAG图可以清晰地理顺整个项目的发展脉络,提交节点的内容如下:
  

tree <SHA1-signature>  
[
  parent <SHA1-signature>
  ...
  
]
  
author <author name> <\<author email\>> <timestamp> <time zone>
  
committer <committer name> <\<committer email\>> <timestamp> <time zone>
  

  
<commit message>
  

  tree用于指向与该提交结点实体关联的节点内容实体。parent用于指向该提交节点实体所基于的之前的提交结点实体,可以看到,parent可以是多个。author用于记录本次提交的作者姓名、作者邮箱、作者所添加的内容时间以及时区。committer用于记录本次提交的提交者姓名、邮箱等内容。commit message用于记录当前提交的消息日志。
  节点内容实体,用于记录本次提交时,提交中所包含的所有文件名,以及文件名所对应的key值,值得注意的是,可能由于查询性能的缘故,并非是仅记录本次提交时修改的文件,而是记录本次提交时所有的文件。另有一点值得注意的是,即便项目仓库中的文件不变,仅改变某个或某几个文件内容的前后两次提交,生成的前后两次提交节点中的tree值是不同的,换句话说,节点内容与提交节点是逻辑上的一对一关系。随着之后的讨论我们会很自然地得出这样的结论,这种一对一关系也同样是必须的,尽管在实际情况中允许两个不同的提交节点实体指向相同的节点内容实体。
  文件内容实体,用于记录具体的文件内容。也就是说,在一个git仓库中,并非只有程序员们所能看到的当前项目文件夹下的代码版本,包括所有的历史代码都会在.git文件夹中有一个备份。
  在objects文件夹中,三种数据实体无差别的以key-value的形式进行存储。因此一次提交操作,在objects文件夹中至少生成两个文件。存储时采用deflate算法对原始文件内容进行压缩,而key值是根据原始文件内容、文件大小等数据生成的消息摘要,在当前版本的git中,消息摘要生成算法采用SHA1算法,生成过程是将文件格式与文件长度组成头部,将文件内容作为尾部,由头部和尾部拼接后作为原文,经过SHA1算法计算之后得到该文件的160位长的SHA1签名。为防止一个文件夹内的文件数量过多,将签名每四位用字符表示十六进制数,于是得到一个长度为40的字符串,将字符串的前两个字符作为文件夹,后38个字符作为文件名进行存储。
  观察仔细的同学可以发现,在三个实体的内容里,没有任何一个字段提供分支概念的信息。

logs文件夹,用于记录分支提交记录
  该文件夹下的内容是一条分支下的所有提交节点实体序列。在该文件夹下,文件内容格式是单一的,即形如这样:
  

0000000000000000000000000000000000000000 6a0fa53d78f03abea3439b9213123d1f260f5beb author <mail> 1511776312 +0800    commit (initial): master 1  
6a0fa53d78f03abea3439b9213123d1f260f5beb 75642040a2da5b324befde7ca8531b3426b32ba7 author <mail> 1511776323 +0800    commit: master 2
  

  
...
  

  

  在一个分支创建时,无论这个分支是master还是基于某个提交结点创建的子分支,在logs文件中关于分支的时间线总是以全0的值为开始的。我们需要关注这样几个问题:


  • master在初始化时是否会创建一个起始地提交结点?
  • 分支创建时的是否会创建一个新的提交节点?
  通过 git init创建一个初始化的git仓库时,master是默认创建的,在初始化的git仓库中,.git文件夹中是不存在logs文件夹的,且在objects文件夹中不包含任何key-value键值对,甚至不存在一个实际存在的master主分支,因此所谓的master初始化并非是在git仓库初始化时进行的,而是在首次提交时进行的。在测试项目中,我在以75为开头的提交结点时创建了一个子分支,查看子分支的logs文件内容:
  

0000000000000000000000000000000000000000 75642040a2da5b324befde7ca8531b3426b32ba7 author <mail> 1511776334 +0800    branch: Created from HEAD  

  
...
  

  

  可以看到,子分支创建时并非将主分支上分叉节点复制一下,而是从这个节点起即为一个子分支。
  当合并分支时,logs的日志是如何表现,事先需要明确的是,合并分支等价于一次提交(合并分支会生成一个提交结点实体)。我们需要关注这样几个问题:


  • 在子分支上已经提交过若干次,在父分支上不提交代码,当在父分支上合并子分支时,父分支的logs记录序列是怎样的
  • 在子分支上提交若干次,在父分支上同样提交若干次,当在父分支上合并子分支时,父分支的logs记录序列是怎样的
  关于这两个问题,我们需要观察相关的文件。第一个实验是,首先创建了一个仓库,并在master分支上提交了一次代码,之后在master分支的最新提交结点上创建了一个子分支branch,再在branch分支上连续提交了两次代码,而后切换到mster分支后,合并branch分支,logs文档的记录如下:
  该文件是master分支的logs文件:
  

0000000000000000000000000000000000000000 79f586c23a8a169f1651411c879657406757ef92 author <mail> 1511853763 +0800    commit (initial): master 1  
79f586c23a8a169f1651411c879657406757ef92 ea4fbfc8600b90555a8a4eb410a176cfbdfa48d7 author <mail> 1511853908 +0800    merge branch: Fast-forward
  

  该文件是branch分支的logs文件
  

0000000000000000000000000000000000000000 79f586c23a8a169f1651411c879657406757ef92 author <mail> 1511853776 +0800    branch: Created from master  
79f586c23a8a169f1651411c879657406757ef92 2dbe03d87733bbcf5b760ba3beacd61ab3f54b58 author <mail> 1511853808 +0800    commit: branch 1
  
2dbe03d87733bbcf5b760ba3beacd61ab3f54b58 ea4fbfc8600b90555a8a4eb410a176cfbdfa48d7 author <mail> 1511853870 +0800    commit: branch 2
  

  可以看到,在父分支没有做改动且子分支做改动的情况下,由父分支进行合并时,是直接将父分支的最新分支节点定义为子分支上的最新分支节点,ea开头的提交节点实体的内容如下:
  

tree 1247c7d74e9c28fb83e8e394910346dee104fcae  
parent 2dbe03d87733bbcf5b760ba3beacd61ab3f54b58
  
author author <mail> 1511853870 +0800
  
committer author <mail> 1511853870 +0800
  

  
...
  

  

  第二个实验是在父分支上提交若干次切在子分支上提交若干次,在父分支上合并子分支时,logs的数据内容。首先创建一个仓库,且在master分支上提交一次代码。在该提交的代码基础上创建一个分支branch。分别在branch分支和在master分支上各自提交两次代码(无冲突),再在master分支上合并branch分支,观察两个分支下的文件内容:
  该文件是master分支的logs文件:
  

0000000000000000000000000000000000000000 8bf95277d6c020f0ade434896355c448fd0cac00 author <mail> 1511854786 +0800    commit (initial): 1  
8bf95277d6c020f0ade434896355c448fd0cac00 368ddb7d615e5eedfe5813ff2f88547dcb66b02b author <mail> 1511854819 +0800    commit: change
  
368ddb7d615e5eedfe5813ff2f88547dcb66b02b 3ba031189d37d816421a019a6240e9d79a683fdd author <mail> 1511854837 +0800    commit: master add 2
  
3ba031189d37d816421a019a6240e9d79a683fdd 12c6263013cf467f348549337813db01044046b9 author <mail> 1511854896 +0800    merge branch: Merge made by the 'recursive' strategy.
  

  该文件是branch分支的logs文件
  

0000000000000000000000000000000000000000 8bf95277d6c020f0ade434896355c448fd0cac00 author <mail> 1511854795 +0800    branch: Created from master  
8bf95277d6c020f0ade434896355c448fd0cac00 ba65e4cc73953f522f14a47088acf814e26ebc29 author <mail> 1511854859 +0800    commit: add 3
  
ba65e4cc73953f522f14a47088acf814e26ebc29 eccc7d8176edb5064cde7649ad651bb2e4fce0e3 author <mail> 1511854873 +0800    commit: add 4
  

  可以看出,在执行merge之后,master分支的最后一次提交结点实体是两个logs文件中都从未出现过的,并且,这个以12为开头的最新提交结点实体的前一个结点实体,是父节点的次新提交结点实体。
  额外关注一下以12为开头的最新提交结点实体的内容:
  

tree 659231fc28ddcd98c8d557e07a3fb5de4efc55b6  
parent 3ba031189d37d816421a019a6240e9d79a683fdd
  
parent eccc7d8176edb5064cde7649ad651bb2e4fce0e3
  
author author <mail> 1511854896 +0800
  
committer author <mail> 1511854896 +0800
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-434558-1-1.html 上篇帖子: 落花无意溪自流 下篇帖子: Git使用总结(廖雪峰git)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表