从.git文件夹探析git实现原理
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
从.git文件夹探析git实现原理的更多相关文章
- 修改apache2配置,禁止目录访问+禁止访问.git文件夹
通过url访问服务器,无论是本地服务器还是远程服务器 如果你的文件根目录里有 index.html,index.php,浏览器就会显示 index.html的内容,如果没有 index.html,浏览 ...
- .git文件夹太大问题及解决方法
最近我们做了自动化构建, 发现文件.git文件夹越来越大, 求后端小伙伴帮忙, 小伙伴指点了一下说周末弄了一下, 忘记命令的.大致的意思就是找到git 提交了哪些大文件. 然后重构git, 先分享给小 ...
- git文件夹下项目更改ip地址小结
在我们开发的过程中,经常切换项目IP地址是很正常的,之前弄过一次,没有记住,现在简单的总结下: 找到要切换IP地址的项目,点击鼠标右键,弹出下图: 打开该项目的路径后,双击打开该项目,具体参考自己项目 ...
- 将本地文件夹添加到git仓库
1.git init; 2.git add . 3.git commit -m "初始化" 4.git remote add origin https://github.com/g ...
- Windows 文件夹修改为exe的原理和解决办法
有关文件夹后缀改为exe的病毒 该病毒之前出现过,不过没多长时间便消失了,最新的这个应该是变种,下面解决一下该病毒在移动存储设备中的问题: 该病毒并不具备能够将文件夹改为文件的能力,只是将原有文件夹全 ...
- 整理自Git文件夹下资料及man手册(不包括书籍)
$ git commit -awhich will automatically notice any modified (but not new) files, add them to the ind ...
- git 无法添加文件夹下文件
最近做项目时,发现无法提交某个子文件夹下的文件. google后发现可能是该子文件夹下有.git文件夹导致无法上传. 删除子文件夹下.git后,依然无法提交子文件夹下的文件. 继续google, 尝试 ...
- git删除文件夹
git rm 要删除的文件夹 -r -f git commit -m 'del config' git push 使用场景,删除test文件夹,本来在码云上,正常的文件夹右击会出现 ...
- Git 忽略特定文件或文件夹
在代码编译过程中,可能会生成一些目标文件或其他我们不希望提交到服务器的文件或文件夹, 但是因为是生成出来的文件/文件夹,在每次使用git status 查看状态的时候git系统总会提示这些 文件或文件 ...
随机推荐
- 不使用数据结构反转栈 递归 CVTE实习 CVTE是一家什么公司
本文因为垃圾csdn标题字限制,标题写不好.本文想说一个算法,和我在CVTE的实习,我看到CVTE是一家什么公司.如果想要喷我的,可以留言,我不会理.如果想喷公司,可以在博客评论或发到我邮件linde ...
- linux系统下Python虚拟环境的安装和使用
前言:进行python项目开发的时候,由于不同的项目需要使用不同的资源包和相关的配置,因此创建多个python虚拟环境,在虚拟环境下开发就显得很有必要. 安装虚拟环境 步骤: 打开Linux终端(快捷 ...
- Java IO(IO流)-2
IO流 第一部分 (OutputStreamWriter BufferOutputStream) 转换流 超类为Reader和Writer 是字符流通向字节流的桥梁:可使用指定的字符编码表,将要写入流 ...
- 上海2017QCon个人分享总结
有幸作为讲师受邀参加InfoQ在上海举办的QCon2017,不得不说,不论是从讲师还是听众的角度衡量,QCon进一步扩大了技术视野.虽然前端专题只有四场,但每一场分享都是目前的热门话题.并且Qcon的 ...
- Linux入门(13)——Ubuntu16.04下将图片和pdf互转
Ubuntu16.04下将图片和pdf互转 将图片转为PDF: convert 图片 PDF convert pic.jpg pic.pdf 将PDF转为图片: convert PDF 图片 conv ...
- 本地idea调试spark2.x程序
1.构建使用idea 构建maven 项目 选择org.scala-tools.archetypes:scala-archetype-simple,然后一直点next,maven最好选中本地配置国内源 ...
- Java基础-运行原理及变量(01)
java运行原理 手动编写java文件由编译器编译成.class文件,再由解释器翻译class文件成机器语言运行. Java中注释分类 单行注释格式: //注释文字多行注释格式: /* 注释文字 */ ...
- 启动tomcat爆错 the JRE could not be found
启动报错,如下图: 之前更改了了一个较低的jdk的版本看了看一个项目的代码,不知所云,然后再改回来, 混乱之中只要启动Tomcat就出现这种错误,还是无法找到JRE,最后如此解决: 在Windows- ...
- ES6中的Promise用法
Node的产生,大大推动了Javascript这门语言在服务端的发展,使得前端人员可以以很低的门槛转向后端开发. 当然,这并不代表迸发成了全栈.全栈的技能很集中,绝不仅仅是前端会写一些HTML和一些交 ...
- AsposeCell特定格式表格
效果图: Workbook workbook = new Workbook(); Worksheet sheet = (Worksheet)workbook.Worksheets[0]; Cells ...