首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Git内幕——.git目录探秘

习惯于文件系统和基于目录的VCS(比如CVS)的人,可能以为git中版本的也是基于目录的,其实上git的版本管理基本上和项目目录(工作目录)没有什么关系,而整个git仓库的所有信息都是保存毫不起眼.git目录

今天虫虫就带领大家来通过探究.git目录来揭示git的底层原理。

概述

前面说了git所有的版本和变化都是保存在.git目录(默认是不可见)下的,工作目录只保存了你工作环境和未提交的文件变化,如果你的所有变化均已经提交了的话,那工作目录中除了.git目录下所有都是可以删掉的。

为了性能、空间、安全和完整性上的考虑,git对.git文件夹做了优化和算法处理,除了个别文件外,我都无法直接无法浏览其内容。目标文件均以guid命名,并且数据是zlib压缩的。但结构和组织是有记录的并且是可以理解的。

简单总结一下,.git文件夹可以分为五类:

objects-(蓝色)这些代表文件和更改。对象可以进一步分为提交、树和 blob类型。

refs引用文件-(红色)这些是组织对象的人类可读文件

logs日志-(绿色)这些用于快速生成显示给用户的日志。

config配置-(浅灰色)有用于配置git行为的文件,可以手动修改。

Temp-(灰色)git在命令行操作之间需要保存的信息的临时文件。

基本git操作

初始化

在任何git项目创建时候,首先要做的就是初始化,即git init:

执行该操作后,会开启git版本管理,即建立一个.git的目录,其结构如下:

可以看到初始化创建创建了一堆文件和文件夹。具体为:

config文件是一个文本文件,其中包含当前存储库的git配置。存储库的一些基本设置,如作者、文件模式等,后续添加的git add remote添加的要关联远程地址等都在该配置文件中,这是.git目录下可以直接手动修改的文件之一。

HEAD包含存储库的当前头。根据设置的“默认”分支指向,其值可能为 refs/heads/master或者refs/heads/main或其他。其中refs/heads可以在下面看到的文件夹,并进入一个名为的文件master目前还不存在。这个文件 master仅在第一次提交后才会显示。

hooks包含可以在git执行任何操作之前/之后运行的任何脚本的示例文件,可以根据需要修改,这是.git目录下唯二可以手动修改的文件(还有一个是config文件)。

objects包含git对象,即存储库中有关文件、提交等的数据,这是git版本管理的底层目录,也是其系统精妙设计的地方。

Refs为存储引用(指针)。refs/heads包含指向分支的指针和refs/tags包含指向标签的指针。

添加变化

为了展示.git系统的变化,我们给工作目录添加一个文件。

echo 'hello git!' >hello

可以看到.git目录并没有任何变化,然后让我们添加该文件到git

git add hellonew file mode 100644index 0000000..d1c6469--- /dev/null+++ b/hello@@ -0,0 +1 @@+hello git!

该操作下,.git目录下有如下变化:

index文件内容变更了,该文件主要存储有关当前暂存内容的信息,变化为将新add的hello文件添加到索引中。

另外再objects下添加了一个新文件夹objects/d1和一个名为

objects文件内容

objects目录下的文件是git存储内容的文件,我们用file工具查看其性质:

file ./.git/objects/d1/c64694584cf480b01273f2c729fd8b6b7c320c./.git/objects/d1/c64694584cf480b01273f2c729fd8b6b7c320c: zlib compressed data

File提示该文件是一个zlib 压缩数据,但是该数据是什么呢?

用zlib-flate工具试试

zlib-flate -uncompress blob 11hello git!

可以看到,其为一个包含名为的文件的类型、大小和内容信息,我们git add添加信息为一个blob类型,大小为11,其内容为尺寸的9内容是hello git!。

objects文件的名解析

实际上objects目录下git对象的文件名都是,对其内容的sha1哈希值。可以对其数据sha1sum,就得到了文件名

zlib-flate -uncompress d1c64694584cf480b01273f2c729fd8b6b7c320c -

git取要写入的内容的sha1哈希取前两个字符为名字创建一个文件夹,然后使用其余哈希部分作为文件名。Git之所以要取哈希德前两个字符创建文件夹,是为了确保单个objects文件夹下不会保存太多的文件。

git cat-file

事实上,git也提供了一个底层的工具来显示git对象的内容,而无需用zlib-flate那么麻烦的解压缩。我们可以使用git cat-file

其参数-t显示类型,-s求大小和-p显示内容。

git cat-file -t d1c64694584cf480b01273f2c729fd8b6b7c320cblobgit cat-file -s d1c64694584cf480b01273f2c729fd8b6b7c320c11git cat-file -p d1c64694584cf480b01273f2c729fd8b6b7c320chello git!

commit提交

add实际上只是将变化添加到了git暂存,要真正提交git版本,则需要commit提交。

git commit -m 'hello'[master (root-commit) 868d295] hello1 file changed, 1 insertion(+)create mode 100644 hello

该操作下.git目录发生的变化:

看来,变化不少,我们一个一个来看下。首先是,新增加了一个COMMIT_EDITMSG文件。和其文件名暗示的一样是,是它包含(最后的)提交消息。

还新增加了一个全新的文件夹logs。这是git记录存储库中所有提交更改的一种方式。所有refs和HEAD头的commit的变化都在此处显示。

objects目录也也增加了两个对象文件(及其对应目录)有变化。

来查看refs目录:

cat ./.git/refs/heads/master868d2956b01a865bd12ec9e3b0af12ae4decb446

应该,是一个对象名称(哈希),用来指向一个git对象(objects目录新建对象之一)。用git cat-file看看他的属性:

git cat-file -t 868d2956b01a865bd12ec9e3b0af12ae4decb446

commit

git cat-file -p 868d2956b01a865bd12ec9e3b0af12ae4decb446tree f0856ed7bdf85cdfae83207f5d18b3435f4e720cauthor chongchong 1696776332 +0800committer chongchong 1696776332 +0800hello

实际上可以直接,这样查看:

git cat-file -t refs/heads/master

信息显示,这是一个新的git对象,其类型为了commit,其内容指向一个tree对象f0856ed7bdf85cdfae83207f5d18b3435f4e720c,恰恰是此次objects目录创建的另一个git对象。

然后是作者,提交时间,提交的消息(hello)等信息。

查看一下这个tree对象:

git cat-file -t f0856ed7bdf85cdfae83207f5d18b3435f4e720ctreegit cat-file -p f0856ed7bdf85cdfae83207f5d18b3435f4e720c100644 blob d1c64694584cf480b01273f2c729fd8b6b7c320c hello

对象类型为tree,其内容为指向我们之前创建的hello文件的blob对象。

这是更成熟的仓库的树的样子。

继续变更

下面来修改下hello文件内容,看看.git目录如何变更。

echo 'by chongchong' > hellogit commit -am '第二次提交'1 file changed, 1 insertion(+), 1 deletion(-)

-am在commit中直接包含了add操作,将两个操作并为一个。该操作后.git目录的变化如下:

新添加了3个git对象。其中之一是blob文件包新内容的对象,其中一个是tree对象,和一个commit对象。来查看refs/heads/master

git cat-file -p refs/heads/mastertree 94667246335fd2296936c4b1c0a53c53dee0cebfparent 868d2956b01a865bd12ec9e3b0af12ae4decb446author chongchong 1696777676 +0800committer chongchong 1696777676 +0800第二次提交git cat-file -p 94667246335fd2296936c4b1c0a53c53dee0cebf100644 blob 37dabb2d48b7c7144b48d97a2a7b231660b01d62 hellogit cat-file -p 37dabb2d48b7c7144b48d97a2a7b231660b01d62by chongchong

另外,commit对象中,多了一行parent键,指向第一次的commit对象的链接,表示,这个commit是在上一个提交之上创建的。

创建分支

我们再来创建一个分支,使用git branch new

该操作在refs/heads文件夹下添加一个以分支为文件名的文件(new),内容作为最新commit的ID值。

cat ./.git/refs/heads/new88a163162bd276257c2a20cff25c017274871ca1

所以,git创建分支在git下是一个非常轻量级的东西,仅仅是一个指向commit ID的指针,这是git和SVN最大区别之一,以及git为啥如此高效的原因。

创建标签(tag)行为和创建分支一样,也是在refs/tags创建一个指向commit的文件指针。

最后,还在logs目录下新添加了一个文件存储提交历史数据的目录类似于master分支。

checkout分支

tree提交的对象并更新工作树中的文件以匹配其中记录的状态。在这种情况下,由于要从 master到new,两者都指向同一个commit和底层tree目录,git在工作树基本无任何变化。

git checkout new

在checkout发生的唯一变化的是.git/HEAD文件现在将指向new。

cat .git/HEADref: refs/heads/new

我们在这提交一个变化,以用来后续的合并操作。

echo 'the third addition'>>newgit add newgit commit -m "第三次提交-分支提交"

合并操作

主要有3种合并方式。

最简单也是最容易的就是快进合并。在这种情况下,只需更新一个分支指向另一个分支指向的提交。只需将哈希复制到refs/heads/new到 refs/heads/master。

第二种是变基合并。在这种情况下,首先将变化应用到main当前一次指向一个提交的内容之上,然后执行类似于快进合并的操作。

第三种使用单独的合并提交来合并两个分支。这有点不同,因为它有两个parent其提交对象中的条目。

首先让看看合并之前的图示:

git log --graph --oneline -all

现在执行合并:

git merge newUpdating 88a1631..88e5367Fast-forwardnew | 1 +1 file changed, 1 insertion(+)create mode 100644 new

截止目前我们都是在本地进行的操作,实际git最常用的操作是push推送到远程仓库中和其他人协作。

为了展示这一点,首先我们在gitee平台上创建一个新的gitee项目作为远程库。

回到仓库目录,添加远程库地址

git remote add origin git@giteeDOTcom:ijz/hellogit.git

添加后,会在仓库的.git/config目录添加如下信息和远程库进行关联。

顺便说一句,和前面提到一样,我们手动修改.git/config文件进行地址和远程库名称修改(比如修改origin为gitee)。

git push origin master

由于我们还没有添加ssh的私钥,所以此处会报如下错误:

ssh-keygen生成密钥对,将公钥~/.ssh/id_rsa.pub,添加到gitee的个人公钥中

然后,通过

ssh -T git@giteeDOTcom

测试,显示如下说明密钥添加成功。

git push origin master

在该步骤中,git目录变化如下:

在refs和log目录下分辨添加了remotes目录用来保存指向远程库的commitID,用来本地和网络间的同步。

总结

本文我们通过探究git项目.git目录下的各种git操作后导致的变化追踪git底层的运作方式,可以更深入的了解git底层的架构和运行原理。

git底层最重要就是文件内容的sha1加密快照和以其哈希为文件名,以及各种指针引用原理,希望一点带面本文能帮助大家了解git基本的原理。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O88WxgLhR1owae7SmH4Psv7Q0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券