想晋级高级工程师只知道表面是不够的!Git内部原理介绍
本文由云+社区发表
作者:腾讯工蜂用户:王二卫
从不一样的视角了解git,以便更好的使用git
一、git & git 版本库认识
git 是一个内容寻址的文件系统,其核心部分是一个简单的键值对数据库(key-value data store),可以向该数据库插入任意类型的内容,它会返回一个40位长的哈希键值。并在此基础上提供了一个版本控制系统的用户界面。
git 版本库其实只是一个简单的数据库,其中包含所有用来维护与管理项目的修订版本和历史信息。其不同于subversion,git版本库不仅提供版本库中所有文件的完整副本,还提供版本库本身的副本。在git版本库中,git维护两个主要数据结构:对象库(object store),索引(index)。
从整体来看,一个项目的git仓库,就如一张带节点的渔网(该渔网是一张有向网),随着项目的不断推进,该渔网也将不断的向四周扩散。
渔网上的节点就像一个个的提交,从某一个正常的节点都能漫游至项目最开始的起点。而分支就如该网上不同节点上的一个特殊标记,分支的演变就是该标记不断的移至其他节点。 分支的合并,根据合并方式的不同,使得这一张网的交叉紧密度越来越高。
1.1git对象类型
对象库是git版本库实现的心脏,包含四种类型:
块(blob,binary lare object),文件的每一个版本表示为一个块。一个blob被视为一个存储任意数据,且内部结构被程序忽略的变量或文件的黑盒。一个blob保存一个文件的数据,但不包含任何关于这个文件的元数据(Metadata,描述数据的数据)。
目录树(tree), 一个目录树对象代表一层目录信息。它记录blob标识符、路径名和在一个目录里所有文件的一的元数据。它也可以递归引用其他目录树或子树对象,从而建立一个包含文件和子目录的完整层次结构。
提交(commit),一个提交对象保存版本库中每一次变化的元数据,每一个提交对象指向一个目录树对象,这个树对象在一张完整的快照中补货提交时版本库的状态。
标签(tag) ,一个标签对象分配一个可读的名字给一个特定的对象,通常是一个提交对象。
为了有效的利用磁盘空间和网络带宽名,git把对象压缩并存储在打包文件(pack file)里,这些文件也在对象库里。
1.2索引
索引是一个临时的、动态的二进制文件,不包含任何文件内容,它仅仅追踪你想要提交的那些内容。使得开发的推进与提交的变更之间能够分离开来。
1.3引用
引用(ref)是一个保存SHA-1值的文件,该文件的名字指针来替代原始的SHA-1值,一般指向提交对象。本地分支名称、远程跟踪分支名称和标签名都是引用。
.git/refs
.git/refs/heads
.git/refs/tags
1.3.1 创建一个引用
$ echo “1a410efbd13591db07496601ebc7a059dd55cfe9” > .git/refs/heads/master
现在可以通过新建的引用来代替SHA-1的值: $ git log —pretty=oneline master 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
不提倡直接编辑引用文件,可以通过update-ref
更新某个引用 $ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
比如新建一个分支(git分支的本质:一个指向某一系列提交之首的指针或引用) $git update-ref refs/heads/feature-zhangsan cac0ca
1.3.2 符号引用
符号引用(symbolic reference),间接指向git对象,其实际也是一个引用,不像普通引用那样包含一个SHA-1值,它是一个指向其他引用的指针。 git自动维护几个用于特定目的的特殊符号引用,这些引用可以在使用提交的任何地方使用。
- HEAD 始终指向当前分支的最近提交,不像普通引用那样包含一个 如: $ cat .git/HEAD ref: refs/heads/master
若执行 $ git checkout test,git会这样更新HEAD文件 ref:refs/heads/test
- ORIG_HEAD 某些操作(如:merge、reset),会把调整为新值之前的先前版本的HEAD记录到OERG_HEAD中,只用其可以恢复或回滚之前的状态或做个比较
- FETCH_HEAD git fech命令将所有抓取分支的头记录到.git/FETCH_HEAD中
- MERGEHEAD 正在合并进HEAD的提交
1.3.3 远程引用
如果你添加了一个远程版本库并对其执行过推送操作,Git 会记录下最近一次推送操作时每一个分支所对应的值,并保存在 refs/remotes 目录下。 如:$cat .git/refs/remotes/origin/master ca82a6dff817ec66f44342007202690a93763949 发现添加的远程origin远程库的master分支锁对应的SHA-1值,就是最近一次与服务器通信时master分支所对应的SHA-1值。 远程引用和分支(位于 refs/heads 目录下的引用)之间最主要的区别在于,远程引用是只读的。 虽然可以git checkout
到某个远程引用,但是 Git 并不会将 HEAD 引用指向该远程引用。 因此,你永远不能通过commit
命令来更新远程引用。 Git 将这些远程引用作为记录远程服务器上各分支最后已知位置状态的书签来管理。
二、git底层命令
- cat-file 展示git仓库对象实体的类型、大小和内容
- ls-remote 显示远程库信息
- ls-files 显示由工作目录中添加到缓存中的文件的相关信息
- ls-tree 列出树对象内容
- read-tree 将给出的树写入索引但不写入缓存
- write-tree 按照索引区内容创建树对象
- symbolic-ref 同步引用信息
- update-index 更新树对象内容至索引
三、.git 结构说明
- HEAD 指示目前被检出的分支
- index 保存暂存区信息
- config* 包含项目特有的配置选项
- description 仅供gitweb程序使用,用户一般不需要关注。
- hooks 包含客户端和服务端的钩子
- info 包含全局排除(global excude)文件,存放那些不希望被记录在.gitignore中的忽略模式
- objects 存储所有数据内容
- refs 存储指向数据(分支)的提交对象的指针
四、git 版本演变
准备工作:创建一个没有任何文件的git初始库 $ git init test Initialized empty Git repository in /data/work/test/test/.git/
4.1 git数据存储演示
- hash-object 存储任意类型数据至数据库,并返回hash 键值
$ echo ‘test conten’ | git hash-object -w —stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
-w 执行写入数据库操作,若不指定该选项,只会返回hash,不会写入数据库。
--stdin 标准输入输出读取
默认存入是blob类型,通过-t 参数指定
$ find .git/objects/ -type f .git/objects//d6/870460b4b4aece5915caf5c68d12f560a9fe3e4
- 一个文件对应一条内容,这个内容的名称以该文件内容加上特定头部信息一起的sha-1校验和。
头部信息-对象类型(blob或tree或commit)+一个空格+数据内容长度+一个空字节 git 会通过zlib将文件内容和头部信息拼接一起的内容进行压缩写入磁盘某个对象,并用计算出的sha-1值的前两个字符串作为目录名称,后38个字符串作为子目录内文件的名称。
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
4.2 简单版本控制演示
4.2.1 创建初始版本
$ echo ‘version 1’ > test.txt
$ git hash-object -w ./test.txt 83baae61804e65cc73a7201a7252750c76066a30
4.2.2 更新版本
$ echo ‘version 2’ > test.txt
$ git hash-object -w ./test.txt 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
此时数据库已经存储了test.txt两个不同的版本,如下:
$ find .git/objects/ -type f .git/objects//1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a .git/objects//83/baae61804e65cc73a7201a7252750c76066a30
可以通过cat-file -p
查看内容,以上都是数据(blob)对象。可以使用 cat-file -t
查看。
4.3 树对象引入
树对像(tree object) 解决文件名和目录保存问题。一个树对象包含了一条或多条树对象记录,每条记录包含一个指向数据对象或子树对象的sha-1指针,以及相应的模式/类型/文件信息。
如下所示:
$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README 100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile 040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
master^{tree}指向master分支最新提交所指的树对象。 数据对象几种类型
- 100644: 表示一般文件
- 100755: 表示可执行文件
- 120000: 表示 指针
- —add: 将未跟踪文件加入缓存区
- —cacheinfo 将数据对象文件加入工作区
4.3.1 将文件加入暂存区
$ git update-index —add —cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
4.3.2 生成树对象
创建第一个树
$ git write-tree 将暂存区内容生成一个树对象,并输出树对象SHA-1 d8329fc1cc938780ffdd9f94e0d364e0ea74f579
4.3.3 演变一个复杂的树
$ echo ‘new file’ > new.txt
$echo ‘test file2’ > test.txt
$git update-index —cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index test.txt
$ git update-index —add new.txt
创建第二个树
$ git write-tree 0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
此时发现,第一个树丢了,并没有跟第一个树有关系,通过 read-tree进行链接 $ git read-tree —prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree 3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614 040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
4.3.4 查看我们生成的树
4.4 提交对象引入
通过commit对象将这些树对象串起来。 创建第一个提交
$ echo ‘first commit’ | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 fdf4fc3344e67ab068f836878b6c4951e3b15f3d
创建第二个提交
$ echo ‘second commit’ | git commit-tree 0155eb -p fdf4fc3 cac0cab538b970a37ea1e769cbbde608743bc96d
创建第三个提交
$ echo ‘third commit’ | git commit-tree 3c4e9c -p cac0cab 1a410efbd13591db07496601ebc7a059dd55cfe9
版本库目录变化` **$ find .git/objects -type f** .git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2 .git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3 .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2 .git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3 .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1 .git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2 .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # ‘test content’ .git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1 .git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt .git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1 `提交版本图
没有执行read-tree
$ git log --stat 92387
commit 923879712b02f980a2edbe1cee315d883ee72503
Author: erweiwang <erweiwang@tencent.com>
Date: Tue Jul 17 15:55:53 2018 +0800
second commit
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
commit e624badd39a25484a08ae74231be65ea50a0fe32
Author: erweiwang <erweiwang@tencent.com>
Date: Tue Jul 17 15:54:20 2018 +0800
first commit
test.txt | 1 +
1 file changed, 1 insertion(+)
五、包文件
Git 最初向磁盘中存储对象时所使用的格式被称为“松散(loose)”对象格式。 但是,Git 会时不时地将多个这些对象打包成一个称为“包文件(packfile)”的二进制文件,以节省空间和提高效率。 当版本库中有太多的松散对象,或者你手动执行 git gc 命令,或者你向远程服务器执行推送时,Git都会这样做。
git 打包对象时,会查找命名及大小相近的文件,并只保存文件不同版本之间的差异内容和文件最新版本的完整内容。
六、引用规格
引用规格的格式由一个可选的 + 号和紧随其后的 : 组成,其中 是一个模式(pattern),代表远程版本库中的引用; 是那些远程引用在本地所对应的位置。 + 号告诉 Git 即使在不能快进的情况下也要(强制)更新引用。
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*
如果想让git每次只拉取远程master分支,而不是所有分支,可以将引用规格那一行修改为: fetch = +refs/heads/master:refs/remotes/origin/master
七、git clone代码库过程
执行git clone后,
- 拉取info/refs文件 => GET info/refs ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
- 确定HEAD引用,明确检出至工作目录的内容 => GET HEAD ref: refs/heads/master 以上说明完成抓取后需要检出master分支
- 从info/refs文件中所提到的ca82a6提交对象开始 => GET objects/ca/82a6dff817ec66f44342007202690a93763949 (179 bytes of binary data)
- 根据ca82a6提取的的父提交对象和树对象开始遍历整个完整版本库。
在遍历过程中,若是未能直接找到(非松散对象)某些对象,会去替代版本库或某个包文件获取。
八、git推送远端库过程
为了上传数据至远端,Git 使用 send-pack 和 receive-pack 进程。 运行在客户端上的 send-pack 进程连接到远端运行的 receive-pack 进程。
九、扩展知识
9.1维护
git gc —auto //整理松散对象并放置包文件,将多个包文件合并为一个大的包文件,移除与任何提交不相关的陈旧对象
9.2数据恢复
- 确定需要恢复的版本 git reflog 查看git默默记录的每一次你改变的HEAD的值。 git log -g 可以详细的查看引用日志中各个版本的信息,风方便确定要恢复的提交。 如下所示 commit 1a410efbd13591db07496601ebc7a059dd55cfe9 Reflog: HEAD@{0} Reflog message: updating HEAD third commit commit ab1afef80fac8e34258ff41fc1b867c702daa24b Reflog: HEAD@{1} Reflog message: updating HEAD modified repo.rb a bit
- 创建用于恢复的临时分支
$ git branch recover-branch ab1afef
- 通过git fsck检查数据库的完整性(当reflog 也不存在需要恢复的版本)
当引用日志所在目录.git/logs/ 被不小心清空时
$ git fsck —full Checking object directories: 100% (256/256), done. Checking objects: 100% (18/18), done. dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4 dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9 dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293
9.3移除对象
该操作使用须谨慎,会导致提交历史不被重写。应用场景,必须对已上库的某些文件(因文件太大或保密信息)进行彻底移除可以使用。
- 定位出问题文件名 保密文件一般是已知的,若是误提交的文件较大需要删除,但又不知道是哪些文件,且又执行过git gc可以通过类似以下命令定位: $ git verify-pack -v .git/objects/pack-29…69 .idx | sort -k 3 -n | tail -3 dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 22044 5792 4977696 82c99a3e86bb1267b236a4b6eff7868d97489af1 blob 4975916 4976258 1438 $ git rev-list —objects —all | grep 82c99a3 82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz
- 从过去所有树中移除这个文件 查看哪些提交对这个文件做过改动 $ git log —oneline —branches — git.tgz dadf725 oops - removed large tarball 7b30847 add git tarball 从7b30847之后的所有提交历史中完全移除该文件 $ git filter-branch —index-fileter ‘git rm —ignore-unmatch —cached git.tgz’ — 7b30847^… Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm ‘git.tgz’ Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2) Ref ‘refs/heads/master’ was rewritten --index-filter 只修改暂存区或索引中的文件 --cached 需要从索引中移除,使得在运行过滤器是,并不会将每个修订版本检出到磁盘 --ignore-unmatch 如果尝试删除的模式不存在时,不提示错误 filter-branch 用于指定从那个提交以来的历史
- 重新打包日志 执行上面操作,本地历史不在包含那个文件的引用,但是,引用日志和 .git/refs/original 通过 filterbranch选项添加的新引用中还存有对这个文件的引用,必须移除它们后重新打包数据库。 $ rm -Rf .git/refs/original $ rm -Rf .git/logs/** $ git gc
- 彻底移除$ git prune --expire now $ git count-objects -v
此文已由腾讯云+社区在各渠道发布
获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号
想晋级高级工程师只知道表面是不够的!Git内部原理介绍的更多相关文章
- [think]关于个人发展值得记住的一些建议 听别人的话,即使你不想听 不要只做不想 成功不能被复制,但失败总在不停复制。看看别人是怎么倒下的,你可以更早地成功
[think]关于个人发展值得记住的一些建议 偶然看到一篇采访周爱民的文章,里面的一些建议虽然朴实无华,却感觉很有道理,特此记录: 记者:对于程序员的技术发展和职业规划能否给大家一些建议呢?----- ...
- 想成为Python高手,必须看这篇爬虫原理介绍!(附29个爬虫项目)
互联网是由一个个站点和网络设备组成的大网,我们通过浏览器访问站点,站点把HTML.JS.CSS代码返回给浏览器,这些代码经过浏览器解析.渲染,将丰富多彩的网页呈现我们眼前. 一.爬虫是什么? 如果我们 ...
- FullCalendar只可以从外部拖入,内部不能互相拖动
startDrag: function(ev) { if(ev.originalEvent.initEvent){ return; } if (!this.isListening) { // star ...
- 【NX二次开发】开发好几年,还只会用ufusr?其他用户出口函数介绍
用户出口(User Exit)是NX Open 中的一个重要概念.NX在运行过程中某些特定的位置存在规定的出口,当进程执行到这些出口时,NX会自动检查用户是否在此处已定义了指向内部程序位置的环境变量: ...
- git database 数据库 平面文件 Git 同其他系统的重要区别 Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异 Git 的设计哲学
小结: 1.如果要浏览项目的历史更新摘要,Git 不用跑到外面的服务器上去取数据回来 2.注意 git clone 应指定版本,它复制的这个版本的全部历史信息: 各个分支 git init 数据库 ...
- ABP理论之CSRF
返回总目录 本篇目录 介绍 ASP.NET MVC ASP.NET WEB API ASP.NET Core[以后补上] 客户端类库 内部原理 介绍 CSRF[Cross-Site Request F ...
- Docker知识-1
[编者的话]本文用图文并茂的方式介绍了容器.镜像的区别和Docker每个命令后面的技术细节,能够很好的帮助读者深入理解Docker. 这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(co ...
- 《Pro Git》阅读随想
之前做版本管理,我使用最多的是SVN,而且也只是在用一些最常用的操作.最近公司里很多项目都开始上Git,借这个机会,我计划好好学习一下Git的操作和原理,以及蕴含在其中的设计思想.同事推荐了一本< ...
- Git哲学与使用
-- 故国神游,多情应笑我,早生华发. Git是什么? Git是一个版本控制工具,代码管理工具,团队协作工具.它跟SVN等传统工具实现同样的目的:但从某种程度来说,它更快,更灵活.我想绝大多数读者都已 ...
随机推荐
- S0.4 二值图与阈值化
目录 二值图的定义 二值图的应用 阈值化 二值化/阈值化方法 1,无脑简单判断 opencv3函数threshold()实现 2,Otsu算法(大律法或最大类间方差法) OpenCV3 纯代码实现大津 ...
- Oracle、DB2、SQLSERVER、Mysql、Access分页SQL语句
最近把平时在项目中常用到的数据库分页sql总结了下.大家可以贴出分页更高效的sql语句.sqlserver分页 第一种分页方法 需用到的参数: pageSize 每页显示多少条数据 pageNu ...
- Hive中的Order by与关系型数据库中的order by语句的异同点
在Hive中,ORDER BY语句是对查询结果集进行整体的排序,最终将会产生一个reducer进行全局的排序,达到的最终结果是和传统的关系型数据库是一样的. 在数据量非常大的时候,全局排序的单个red ...
- TypeScript-封装
class People { private _name: string; age: number; print() { return this._name + ":" + thi ...
- hadoop2-HBase的Java API操作
Hbase提供了丰富的Java API,以及线程池操作,下面我用线程池来展示一下使用Java API操作Hbase. 项目结构如下: 我使用的Hbase的版本是 hbase-0.98.9-hadoop ...
- [LeetCode] Image Overlap 图像重叠
Two images A and B are given, represented as binary, square matrices of the same size. (A binary ma ...
- R语言S3类的理解与构建
R语言类 R语言的类有S3类和S4类,S3类用的比较广,创建简单粗糙但是灵活,而S4类比较精细,具有跟C++一样严格的结构.这里我们主要讲S3类. S3类的结构 S3类内部是一个list,append ...
- 接口自动化集成到jenkins(Java+testng+maven+git)
一jenkins启动命令:jenkins 查看端口号: 1.lsof -i:端口号 2.netstat -tunlp|grep 端口号 二: 登录:http://localhost:8080 输入:u ...
- windows 10 64位机器上 安装部署
mi这个博客写的不错 https://www.cnblogs.com/dingguofeng/p/8709476.html 安装redis 可视化工具后 ,新建连接 名称随意,注意端口号是否有误默认6 ...
- C# WPF 使用委托修改UI控件
近段时间在自学WPF,是一个完全不懂WPF的菜鸟,对于在线程中修改UI控件使用委托做一个记录,给自已以后查询也给需要的参考: 界面只放一个RichTextBox,在窗体启动时开起两个线程,调用两个函数 ...