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的值为开始的。我们需要关注这样几个问题:

  1. master在初始化时是否会创建一个起始地提交结点?
  2. 分支创建时的是否会创建一个新的提交节点?

通过 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的日志是如何表现,事先需要明确的是,合并分支等价于一次提交(合并分支会生成一个提交结点实体)。我们需要关注这样几个问题:

  1. 在子分支上已经提交过若干次,在父分支上不提交代码,当在父分支上合并子分支时,父分支的logs记录序列是怎样的
  2. 在子分支上提交若干次,在父分支上同样提交若干次,当在父分支上合并子分支时,父分支的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实现原理的更多相关文章

  1. 修改apache2配置,禁止目录访问+禁止访问.git文件夹

    通过url访问服务器,无论是本地服务器还是远程服务器 如果你的文件根目录里有 index.html,index.php,浏览器就会显示 index.html的内容,如果没有 index.html,浏览 ...

  2. .git文件夹太大问题及解决方法

    最近我们做了自动化构建, 发现文件.git文件夹越来越大, 求后端小伙伴帮忙, 小伙伴指点了一下说周末弄了一下, 忘记命令的.大致的意思就是找到git 提交了哪些大文件. 然后重构git, 先分享给小 ...

  3. git文件夹下项目更改ip地址小结

    在我们开发的过程中,经常切换项目IP地址是很正常的,之前弄过一次,没有记住,现在简单的总结下: 找到要切换IP地址的项目,点击鼠标右键,弹出下图: 打开该项目的路径后,双击打开该项目,具体参考自己项目 ...

  4. 将本地文件夹添加到git仓库

    1.git init; 2.git add . 3.git commit -m "初始化" 4.git remote add origin https://github.com/g ...

  5. Windows 文件夹修改为exe的原理和解决办法

    有关文件夹后缀改为exe的病毒 该病毒之前出现过,不过没多长时间便消失了,最新的这个应该是变种,下面解决一下该病毒在移动存储设备中的问题: 该病毒并不具备能够将文件夹改为文件的能力,只是将原有文件夹全 ...

  6. 整理自Git文件夹下资料及man手册(不包括书籍)

    $ git commit -awhich will automatically notice any modified (but not new) files, add them to the ind ...

  7. git 无法添加文件夹下文件

    最近做项目时,发现无法提交某个子文件夹下的文件. google后发现可能是该子文件夹下有.git文件夹导致无法上传. 删除子文件夹下.git后,依然无法提交子文件夹下的文件. 继续google, 尝试 ...

  8. git删除文件夹

    git  rm  要删除的文件夹  -r -f   git  commit  -m  'del  config' git  push 使用场景,删除test文件夹,本来在码云上,正常的文件夹右击会出现 ...

  9. Git 忽略特定文件或文件夹

    在代码编译过程中,可能会生成一些目标文件或其他我们不希望提交到服务器的文件或文件夹, 但是因为是生成出来的文件/文件夹,在每次使用git status 查看状态的时候git系统总会提示这些 文件或文件 ...

随机推荐

  1. win10 uwp 右击选择 GridViewItem

    有时候我们需要选择一个 GridView 的一项,通过我们右击. 于是我们需要在 GridView 的 SelectionMode 为 Single ,IsRightTapEnabled 为True ...

  2. LeetCode 48. Rotate Image(旋转图像)

    You are given an n x n 2D matrix representing an image. Rotate the image by 90 degrees (clockwise). ...

  3. SSH整合的详细步骤

    SSH整合 新建一个动态web工程-->加入Spring-->加入Hibernate-->加入Struts2 1.在 web中应用Spring 目的:在web应用程序加载成功之后,就 ...

  4. Tomcat 服务器及使用Eclipse绑定Tomcat并发布应用

    一.简介 Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache.Sun 和其他一些公司及个人共同开发而成 ...

  5. Android Studio 3.0 使用问题解决方案总结

    问题:创建新项目非常慢 问题描述: 更新到3.0版本后,出现创建新项目一直停留在如下图的界面: 选择等待?不知道要等到什么时候,这时候怎么办呢?显然,不能一直等待下去呀,需要想办法让他能尽快的加载好才 ...

  6. [译]ASP.NET Core 2.0 路由引擎

    问题 ASP.NET Core 2.0的路由引擎是如何工作的? 答案 创建一个空项目,为Startup类添加MVC服务和请求中间件: public void ConfigureServices(ISe ...

  7. 数据处理不等式:Data Processing Inequality

    我是在差分隐私下看到的,新解决方案的可用性肯定小于原有解决方案的可用性,也就是说信息的后续处理只会降低所拥有的信息量. 那么如果这么说的话为什么还要做特征工程呢,这是因为该不等式有一个巨大的前提就是数 ...

  8. 【JAVA零基础入门系列】Day12 Java类的简单应用

    俗话说的好,实践出真知,所以除了理论知识掌握扎实以外,更重要的是要多加操练,这样才能掌握核心科技. 今天我们就用刚学会的类来实践一下,目标便是完成上一篇中的剁手任务. 我们的商品类已经准备好了,代码重 ...

  9. Android Weekly Notes Issue #281

    October 29th, 2017 Android Weekly Issue #281 本期内容不多,包含了小众DI库牙签帮助测试的文章,Kotlin中Delegate的强大之介绍,以及基于Goog ...

  10. A Simple Math Problem(矩阵快速幂)(寒假闭关第一题,有点曲折啊)

    A Simple Math Problem Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Other ...