公众号关注 “GitHubDaily”

设为 “星标”,每天带你逛 GitHub!

本文作者:lzaneli,腾讯 TEG 前端开发工程师

本文以一个具体例子结合动图介绍了 Git 的内部原理,包括 Git 是什么储存我们的代码和变更历史的、更改一个文件时,Git 内部是怎么变化的、Git 这样实现的有什么好处等等。视频链接:https://www.bilibili.com/video/av77252063PPT 链接:https://www.lzane.com/slide/git-under-the-hood

前言

近几年技术发展十分迅猛,让部分同学养成了一种学习知识停留在表面,只会调用一些指令的习惯。我们时常有一种 “我会用这个技术、这个框架” 的错觉,等到真正遇到问题,才发现事情没有那么简单。而 Git 也是一个大部分人都知道如何去使用它,知道有哪些命令,却只有少部分人知道具体原理的东西。了解一些底层的东西,可以更好的帮你理清思路,知道你真正在操作什么,不会迷失在 Git 大量的指令和参数上面。

Git 是怎么储存信息的

这里会用一个简单的例子让大家直观感受一下 git 是怎么储存信息的。首先我们先创建两个文件

$ git init$ echo '111' > a.txt$ echo '222' > b.txt$ git add *.txt

Git 会将整个数据库储存在.git/目录下,如果你此时去查看.git/objects目录,你会发现仓库里面多了两个 object。

$ tree .git/objects.git/objects├── 58│   └── c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c├── c2│   └── 00906efd24ec5e783bee7f23b5d7c941b0c12c├── info└── pack

好奇的我们来看一下里面存的是什么东西

$ cat .git/objects/58/c9bdf9d017fcd178dc8c073cbfcbb7ff240d6cxKOR0a044K%

怎么是一串乱码?这是因为 Git 将信息压缩成二进制文件。但是不用担心,因为 Git 也提供了一个能够帮助你探索它的 api git cat-file [-t] [-p], -t可以查看 object 的类型,-p可以查看 object 储存的具体内容。

$ git cat-file -t 58c9blob$ git cat-file -p 58c9111

可以发现这个 object 是一个 blob 类型的节点,他的内容是 111,也就是说这个 object 储存着 a.txt 文件的内容。这里我们遇到第一种 Git object,blob 类型,它只储存的是一个文件的内容,不包括文件名等其他信息。然后将这些信息经过 SHA1 哈希算法得到对应的哈希值58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c,作为这个 object 在 Git 仓库中的唯一身份证。也就是说,我们此时的 Git 仓库是这样子的:


我们继续探索,我们创建一个 commit。

$ git commit -am '[+] init'$ tree .git/objects.git/objects├── 0c│   └── 96bfc59d0f02317d002ebbf8318f46c7e47ab2├── 4c│   └── aaa1a9ae0b274fba9e3675f9ef071616e5b209...

我们会发现当我们 commit 完成之后,Git 仓库里面多出来两个 object。同样使用 cat-file命令,我们看看它们分别是什么类型以及具体的内容是什么。

$ git cat-file -t 4caaa1tree$ git cat-file -p 4caaa1100644 blob 58c9bdf9d017fcd178dc8c0...     a.txt100644 blob c200906efd24ec5e783bee7...    b.txt

这里我们遇到了第二种 Git object 类型 ——tree,它将当前的目录结构打了一个快照。从它储存的内容来看可以发现它储存了一个目录结构(类似于文件夹),以及每一个文件(或者子文件夹)的权限、类型、对应的身份证(SHA1 值)、以及文件名。此时的 Git 仓库是这样的:


$ git cat-file -t 0c96bfcommit$ git cat-file -p 0c96bftree 4caaa1a9ae0b274fba9e3675f9ef071616e5b209author lzane 李泽帆  1573302343 +0800committer lzane 李泽帆  1573302343 +0800[+] init

接着我们发现了第三种 Git object 类型 ——commit,它储存的是一个提交的信息,包括对应目录结构的快照 tree 的哈希值,上一个提交的哈希值(这里由于是第一个提交,所以没有父节点。在一个 merge 提交中还会出现多个父节点),提交的作者以及提交的具体时间,最后是该提交的信息。此时我们去看 Git 仓库是这样的:到这里我们就知道 Git 是怎么储存一个提交的信息的了,那有同学就会问,我们平常接触的分支信息储存在哪里呢?

$ cat .git/HEADref: refs/heads/master$ cat .git/refs/heads/master0c96bfc59d0f02317d002ebbf8318f46c7e47ab2

在 Git 仓库里面,HEAD、分支、普通的 Tag 可以简单的理解成是一个指针,指向对应 commit 的 SHA1 值。其实还有第四种 Git object,类型是 tag,在添加含附注的 tag(git tag -a)的时候会新建,这里不详细介绍,有兴趣的朋友按照上文中的方法可以深入探究。至此我们知道了 Git 是什么储存一个文件的内容、目录结构、commit 信息和分支的。其本质上是一个 key-value 的数据库加上默克尔树形成的有向无环图(DAG)。这里可以蹭一下区块链的热度,区块链的数据结构也使用了默克尔树。

Git 的三个分区

接下来我们来看一下 Git 的三个分区(工作目录、Index 索引区域、Git 仓库),以及 Git 变更记录是怎么形成的。了解这三个分区和 Git 链的内部原理之后可以对 Git 的众多指令有一个 “可视化” 的理解,不会再经常搞混。接着上面的例子,目前的仓库状态如下:这里有三个区域,他们所储存的信息分别是:

  • 工作目录 ( working directory ):操作系统上的文件,所有代码开发编辑都在这上面完成。

  • 索引( index or staging area ):可以理解为一个暂存区域,这里面的代码会在下一次 commit 被提交到 Git 仓库。

  • Git 仓库( git repository ):由 Git object 记录着每一次提交的快照,以及链式结构记录的提交变更历史。

我们来看一下更新一个文件的内容这个过程会发生什么事。运行 echo "333" > a.txt将 a.txt 的内容从 111 修改成 333,此时如上图可以看到,此时索引区域和 git 仓库没有任何变化。运行 git add a.txt将 a.txt 加入到索引区域,此时如上图所示,git 在仓库里面新建了一个 blob object,储存了新的文件内容。并且更新了索引将 a.txt 指向了新建的 blob object。运行 git commit -m 'update'提交这次修改。如上图所示

  1. Git 首先根据当前的索引生产一个 tree object,充当新提交的一个快照。

  2. 创建一个新的 commit object,将这次 commit 的信息储存起来,并且 parent 指向上一个 commit,组成一条链记录变更历史。

  3. 将 master 分支的指针移到新的 commit 结点。

至此我们知道了 Git 的三个分区分别是什么以及他们的作用,以及历史链是怎么被建立起来的。基本上 Git 的大部分指令就是在操作这三个分区以及这条链。可以尝试的思考一下 git 的各种命令,试一下你能不能够在上图将它们 “可视化” 出来,这个很重要,建议尝试一下。如果不能很好的将日常使用的指令 “可视化” 出来,推荐阅读《图解 Git》。

一些有趣的问题

有兴趣的同学可以继续阅读,这部分不是文章的主要内容

问题 1:为什么要把文件的权限和文件名储存在 tree object 里面而不是 blob object 呢?

想象一下修改一个文件的命名。如果将文件名保存在 blob 里面,那么 Git 只能多复制一份原始内容形成一个新的 blob object。而 Git 的实现方法只需要创建一个新的 tree object 将对应的文件名更改成新的即可,原本的 blob object 可以复用,节约了空间。

问题 2:每次 commit,Git 储存的是全新的文件快照还是储存文件的变更部分?

由上面的例子我们可以看到,Git 储存的是全新的文件快照,而不是文件的变更记录。也就是说,就算你只是在文件中添加一行,Git 也会新建一个全新的 blob object。那这样子是不是很浪费空间呢?这其实是 Git 在空间和时间上的一个取舍,思考一下你要 checkout 一个 commit,或对比两个 commit 之间的差异。如果 Git 储存的是问卷的变更部分,那么为了拿到一个 commit 的内容,Git 都只能从第一个 commit 开始,然后一直计算变更,直到目标 commit,这会花费很长时间。而相反,Git 采用的储存全新文件快照的方法能使这个操作变得很快,直接从快照里面拿取内容就行了。当然,在涉及网络传输或者 Git 仓库真的体积很大的时候,Git 会有垃圾回收机制 gc,不仅会清除无用的 object,还会把已有的相似 object 打包压缩。

问题 3:Git 怎么保证历史记录不可篡改?

通过 SHA1 哈希算法和哈系树来保证。假设你偷偷修改了历史变更记录上一个文件的内容,那么这个问卷的 blob object 的 SHA1 哈希值就变了,与之相关的 tree object 的 SHA1 也需要改变,commit 的 SHA1 也要变,这个 commit 之后的所有 commit SHA1 值也要跟着改变。又由于 Git 是分布式系统,即所有人都有一份完整历史的 Git 仓库,所以所有人都能很轻松的发现存在问题。推荐阅读:强烈推荐下 GitHub 官方的这个教程GitHub 上有个沙雕开发者,做了款斗图工具后火了...955 不加班的公司名单:955.WLBGitHub 要进入中国啦!GitHub 上有哪些适合新手跟进的优质项目?

git tag怎么使用_这才是真正的 Git:Git 内部原理揭秘!相关推荐

  1. git tag怎么使用_这才是真正的Git——Git内部原理揭秘

    本文作者:lzaneli,腾讯 TEG 前端开发工程师 本文以一个具体例子结合动图介绍了Git的内部原理,包括Git是什么储存我们的代码和变更历史的.更改一个文件时,Git内部是怎么变化的.Git这样 ...

  2. git tag怎么使用_这才是真正的Git——Git内部原理揭秘!

    本文以一个具体例子结合动图介绍了Git的内部原理,包括Git是什么储存我们的代码和变更历史的.更改一个文件时,Git内部是怎么变化的.Git这样实现的有什么好处等等. 通过例子解释清楚上面这张动图,让 ...

  3. git把另一个盘的代码上传_Git内部原理揭秘!从文件更改到代码储存,Git究竟是怎么实现的...

    原标题:Git内部原理揭秘!从文件更改到代码储存,Git究竟是怎么实现的 新智元推荐 来源:腾讯技术工程 作者:lzaneli [新智元导读]本文以一个具体实例结合动图介绍了Git的内部原理,包括Gi ...

  4. git object 很大_这才是真正的Git——Git内部原理

    本文以一个具体例子结合动图介绍了Git的内部原理,包括Git是什么储存我们的代码和变更历史的.更改一个文件时,Git内部是怎么变化的.Git这样实现的好处等等. TL;DR 本文以一个具体例子结合动图 ...

  5. 这才是真正的Git——Git内部原理揭秘!

    本文作者:lzaneli,腾讯 TEG 前端开发工程师 本文以一个具体例子结合动图介绍了Git的内部原理,包括Git是什么储存我们的代码和变更历史的.更改一个文件时,Git内部是怎么变化的.Git这样 ...

  6. git stash 强制恢复_开发中必须要掌握的 Git 技巧

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:你喜欢吃青椒么 juejin.im/post/5d157bf3f265da1bcc1 ...

  7. git 修改分支名字_开发中必须要掌握的 Git 技巧

    前言 本文是参考杜老师的Git资料再加上我自己对Git的理解,记录我的Git学习历程,作下此文是为以后学习,工作,开发中如果遇到问题可以回过头来参考参考.因为水平有限,难免会有出错的地方,欢迎指正. ...

  8. git reset --hanrd撤回_一篇文章,全面掌握Git

    版本控制 版本控制就是记录项目文件的历史变化.它为我们查阅日志,回退,协作等方面提供了有力的帮助. 版本控制一般分为集中化版本控制和分布式版本控制. 集中化主要的版本数据都保存服务端. 分布式版本数据 ...

  9. eclipse git 取远程代码_【JAVA】Eclipse中使用git进行pull远程代码

    当使用eclipse或者MyEclipse进行pull远程代码的时候,或者github的代码的时候报如下错误代码: 代表我们没有配置我们的Git地址,这里我教大家配置一下.首先下面是错误代码: The ...

最新文章

  1. 暗时间:开发效率为何如此低下
  2. 设置应用图标badge(徽章)
  3. 拒绝conda, 用virtualenv构建多版本的python开发环境
  4. Android(三)——将程序打包成APK文件,进行APK签名以及反编译APK
  5. 基于python3写的源码剖析_Python 极简插件系统 pluggy 源码剖析
  6. Strut学习-web.xml
  7. 动态规划训练17 [Super Jumping! Jumping! Jumping! HDU - 1087 ]
  8. mysql 多表混全_mysql--浅谈多表查询1
  9. 幅度响应怎么计算_四电平脉冲幅度调制(PAM4)信号的误码分析
  10. Linux-kernel网桥代码分析(二)
  11. 4月15日见?疑似一加8 Pro真机谍照曝光:开孔双曲面屏加持
  12. fastjson list转json字符串_从fastjson转jackson的血泪史
  13. Firebug方便分解网页的小工具
  14. 阶段3 2.Spring_07.银行转账案例_6 测试转账并分析案例中的问题
  15. C#使用ITextSharp操作pdf
  16. Java中Collections.singleton方法起什么作用呢?
  17. !function(){}() 什么意思
  18. Unity5.1 新的网络引擎UNET(十五) Networking 引用--中
  19. git reset soft、mixed和hard的区别和用途详讲
  20. img图片不失真,进行比例缩放

热门文章

  1. clojure实战——宏
  2. Spring Cloud架构教程 (三)服务网关(基础)
  3. Seastar源码阅读(三)future
  4. X Spring File Storage 0.6.0 发布,新增支持 FTP、SFTP、WebDAV
  5. 论大数据的泡沫、价值与应用陷阱
  6. 如何识别骗子广告联盟,骗子广告联盟常用手段
  7. 陶瓷天线的选型(二)
  8. 内网渗透----常见后门
  9. 智慧水务信息化管理平台解决方案
  10. ASP 、PHP 代码加密的安全性逻辑思考