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 创建blob的过程描述

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

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

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

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

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

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

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

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

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

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

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

提交

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

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

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

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

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

垒砖墙

分支

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

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

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

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

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

标签

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

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

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

HEAD 在 Git 中执行一些任务:

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

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

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

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

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

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

Merge

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

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

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

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

相关推荐
一条闲鱼_mytube3 小时前
git自动压缩提交的脚本
大数据·git·elasticsearch
油泼辣子多加7 小时前
2024年12月29日Github流行趋势
github
sin220110 小时前
Git--tag标签远程管理
git
Mocode10 小时前
【git】将项目上传到github、gitee
git·gitee·github
码农研究僧13 小时前
如何安全删除 GitHub 提交记录及其操作步骤
git·github·提交记录
迃幵chen14 小时前
密钥登录服务器
运维·服务器·github
极客先躯17 小时前
问题记录: git clone的时候遇到的问题
git
記億揺晃着的那天18 小时前
Github优质项目推荐(第十期)
github
ZERO-A-ONE18 小时前
Github Copilot 插件更新失败
github·copilot
xianwu54318 小时前
cpp编译链接与命名空间
linux·开发语言·网络·c++·git