likes
comments
collection
share

Git究竟是如何工作的?

作者站长头像
站长
· 阅读数 52

深入探究 Git 的内部机制, 学习并精通 Git

Git究竟是如何工作的?

简介

Git无疑是现代软件开发的主要基石之一. 它是协调开发人员工作的必备工具箱, 多年来已成为开源运动的基本引擎. 一个简单的例子是, 截至2021年11月, 根据Git的主要仓库管理器GitHub报告可知, 已有超过7300万名开发人员和2亿多个仓库.

一些程序员每天都要与Git打交道, 并普遍应用其中的关键概念. 在本讲座中, 我们将通过深入Git内部, 探索Git的基本基础, 继续向前迈步. 什么是分支? 什么是分支头? 合并分支意味着什么? 今天, 我们将回答这些问题以及其他问题.

打地基

Blob, 树和提交是Git数据结构的主要组成部分. 就像房子由砖块砌成, 图由边和节点组成一样, 这些元素构成了Git的地基.

要理解这一切, 让我们从一个例子开始. 假设我们创建了一个空仓库. 当我们启动git init命令时, git会自动创建一个名为.git的隐藏文件夹, 用来存储内部信息.

Blob

现在, 假设我们创建了一个名为myfile.txt的文件, 并使用git add myfile.txt.命令将其添加到我们的版本库中.

当我们执行这个操作时, Git会创建一个blob, 这是个文件, 位于.git/objects子文件夹中, 存储了myfile.txt的内容, 但**不包括任何相关元数据(如创建时间戳、作者等). 因此, 创建 blob 就像存储图片文件的内容一样.

Blob 的名称与其内容的哈希值**相关. 内容散列后, 前两个字符用于在*.git/objects*中创建子文件夹, 散列的其余字符则构成 blob 的名称.

总之, 向 Git 添加文件的步骤如下:

  1. Git 获取文件内容并对其散列
  2. Git 在.git/objects文件夹下创建一个 blob. 哈希值的前两个字符用于在该路径下创建一个子文件夹. Git 会在其中创建一个blob, 其名称由哈希值的其余字符组成.
  3. Git 会将原始文件(压缩版)的内容保存在blob中.

Git究竟是如何工作的?

Git 创建blob的过程描述

请注意, 如果我们有一个名为myfile.txt的文件和另一个名为ourfile.txt的文件, 而这两个文件**共享相同的内容, 它们的哈希值也相同, **因此它们被存储在同一个blob中.

如果我们对myfile.txt稍作修改, 并将其重新添加到版本库中, Git 也会执行同样的过程.

假设我们在版本库中创建了一个名为subfolder的子文件夹, 也让我们在这个子文件夹中创建一个名为yourfile.txt的文件, 并将其添加到版本库中. 在此过程中, Git 会根据我们在上一段中定义的流程为yourfile.txt创建一个新的 blob.

Git究竟是如何工作的?

Git会对第二个名为yourfile.txt的文件进行散列, 该文件保存在.git/objects文件夹中

此时, 我们使用git commit命令提交myfile.txtyourfile.txt:

  • 创建仓库的根树
  • 创建提交

让我们集中讨论第一步. 那么, 什么是根树呢? 根树存储了整个版本库的文件和文件夹结构. 它是一个文件, 包含对版本库中每个 blob 或子文件夹的引用, 以递归方式建立.

根树的每一行都引用一个 blob 或其他子树, 这些子树又以同样的方式引用其他 blob 或其他子树. 因此, 树相当于目录: 就像我们可以从目录访问文件和子文件夹一样, 我们也可以从树访问 blob 和子树.

Git究竟是如何工作的?

根树和与mysubfolder相关的子树的内容

一旦 Git 创建了根树和所有相关的子树, 它就会执行上文所述的散列和存储操作. 更准确地说, 它会对每棵树进行散列, 并使用前两个字符在.git/objects中创建一个子文件夹, 而其余的散列字符则构成保存文件的名称. 因此, 在这个过程中, 我们得到的新文件数量与数据结构中树的数量相同.

Git究竟是如何工作的?

Git 会对根树和与mysubfolder相关的子树进行散列, 两者都存储在.git/objects文件夹中

提交

运行git commit命令后, 第二步是创建提交. 提交内容存储在一个文件中, 其中包含与根树, 父提交(如果有)相关的信息, 以及一些元数据, 如提交者的姓名, 电子邮件地址和提交信息.

Git究竟是如何工作的?

提交文件包含对根树的哈希值, 作者和提交者, 提交时间戳(本例中为 163267988), 父提交(本例中为空, 因为这是第一次提交)和提交消息的引用.

提交文件创建后, Git 会对其内容进行散列, 并使用散列名将内容存储到一个新文件中, 与上述操作完全相同(前两个字符构成.git/objects中的子文件夹名称, 而散列名的剩余部分构成实际名称).

Git究竟是如何工作的?

到目前为止所有树, 提交和 Blob 的结构

就是这样! 恭喜你, 你刚刚了解了 Git 的结构. 现在, 有了这些概念, 要定义分支, 标记, 头部和合并等概念就非常简单了!

垒砖墙

分支

分支是对提交的命名引用. 例如, 当创建一个名为mybranch的新分支时(使用git checkout -b mybranch命令), Git 会在.git/refs/heads路径下生成一个名为mybranch的新文件, 该文件的内容是创建分支的提交的哈希值.

Git究竟是如何工作的?

最初, master 和mybranch都指向同一个提交

然后, 当我们在mybranch上提交时, Git 会执行之前定义的操作(创建根树和提交文件), 然后用新的提交哈希值更新分支的文件.

Git究竟是如何工作的?

执行新提交后, mybranch文件的内容将被更新. 现在, mybranch文件指向新的提交

因此, 分支是文件, 用于跟踪提交, 我们每次提交都会更新这些文件的内容.

标签

标签是对特定提交的永久引用. 例如, 当我们创建一个名为mytag的新标签时(使用git tag mytag命令), Git会在.git/refs/tags路径下生成一个名为mytag的新文件.

不过, 当我们继续工作并在同一(或其他)分支上提交时, 标签文件不会更新, 而是继续指向其创建时的特定提交. 与分支文件不同, 标签在执行新提交时不会移动.

Git究竟是如何工作的?

执行了新提交, 但文件mytag并未更新

HEAD

HEAD 在 Git 中执行一些任务:

  • 因此, 当我们执行git branch时, Git 会通过HEAD来了解我们所在的分支.
  • 它引用下一次提交的父提交, 因此HEAD指向的提交将是下一次提交的父提交. 回想一下, 当我们执行提交时, 的父提交会保存在提交文件中.

如果我们在分支 master 上, HEAD就会引用该分支. 如果我们打开HEAD文件, 就会看到"ref: refs/heads/master". 相反, 如果我们切换到mybranch分支, 并打开.git文件夹中的HEAD文件, 我们会看到"ref: refs/heads/mybranch". 因此, HEAD并不直接指向某个提交, 而是指向某个分支, 而该分支又指向该分支上的最新提交. 通过这种方式, Git 可以追踪当前已签出的提交.

Git究竟是如何工作的?

我们在分支mybranch上. HEAD指向文件mybranch, 而文件mybranch又指向一个特定的提交. 与分支 master 相关的文件master指向另一个提交

当我们在分支上执行提交时, Git 会读取HEAD文件的内容, 并写入被引用为父提交的提交. 从这个意义上说, HEAD(间接)提供了下一次提交的父提交.

Git究竟是如何工作的?

提交文件的内容. HEAD(间接)提供了父提交

现在, 在 Git 中, 我们可以签出到前一个提交, 然后从那里开始修改. 这种模式被称为分离模式. 在这种情况下, HEAD直接指向一个提交, 而不是分支. 请注意, 这样做可能会有危险, 因为我们有可能丢失新的提交. 事实上, 在执行一次提交后, 如果我们签出到一个分支, 我们就无法再返回到这个新提交, 因为它没有被任何分支引用! 这就是为什么我们在分离模式下提交任何改动之前, 总是要创建一个新分支的原因!

Merge

Merge允许连接两个或多个提交. Merge有两种类型:

  • 第一种是当两个分支发生分歧时. Git 会创建一个有两个父分支的新子分支. 第一个父分支是我们所在的分支, 第二个父分支是将要Merge的分支. 提交文件将有两个父节点, HEAD被移动到新的子节点上.
  • 第二种情况是, 两个分支没有分叉, 但其中一个分支是另一个分支的延续. 在这种情况下, 合并被称为快进合并(fast-forward merge), 它不是真正意义上的合并, 因为没有冲突. 在这种情况下, Git 只是把HEAD和当前分支移到要合并分支的同一个提交点上.

就到这了. 恭喜你看到了这里! 希望你喜欢这篇文章! 现在, 你应该对 Git 的工作原理有所了解了. 如有任何疑问, 欢迎随时发表评论!

后会有期了, 要持续发光哦! :)

转载自:https://juejin.cn/post/7289692200160526394
评论
请登录