计算机科学家的Git
July 23, 2022
简介
一个简短的关于Git内部结构的介绍,使大家不被诸如有向无回环之类的词语吓到。
存储
简单来说,Git对象存储的“只是”一个对象的DAG,以及具有少数不同类型的对象。它们都在压缩后被存储,并且通过SHA-1 哈希进行标识(提一下,这里的哈希并不是文件内容决定的那个哈希,而是在Git中代表的哈希)。
blob:一堆字节组成的最简单的对象。通常是一个文件,但是也可以是符号链或者其他任何可能的东西。指向blob的对象决定其语义。
tree:目录都由tree对象表示,它们指向的是具有文件内容的blob(文件名,访问模式等都存储在tree中),和其他子目录的tree。
当一个节点在DAG中指向另一个节点时,它就依赖于另一个节点:它不能脱离这个连接而存在。一个节点若是没有任何指向其本身的所指,那它就会成为垃圾而被Git垃圾回收机制(GC)所回收,或者像文件系统inodes那样对于没有文件名指向的节点使用git fsck --lost-found进行拯救。
commit:一个commit指向一个代表文件在提交(commit)时的状态的tree。它也指向0到N个其他父提交。当有多个父级的时候意味着此次commit是合并操作(merge),没有父级意味着此次是初始提交,有趣的是可以存在多个初始提交,这通常意味着合并了两个独立的项目。commit对象的主体是commit message。
refs:引用、头(head)或者分支(branches),就像贴在DAG节点上的便利贴。由于DAG只获取添加操作,并且现存节点不能被改变,post-its可以随处自由移动。它们不会被存储在历史记录中,也不能直接在两个仓库间传输。它们就像一种书签,说着“我正在这工作”
(译注):我觉得HEAD是更加形象的说法,想象一下你那正面对电脑屏幕的头所锚定的屏幕内容。
git commit向DAG添加一个新节点,并且将当前分支的引用移动到新节点。
HEAD ref 的特殊之处在于它实际上指向另一个 ref。 它是指向当前活动分支的指针。 普通的 refs 实际上是在一个命名空间 head/XXX 中,但是你通常可以跳过 head/ 部分。
remote refs:远程引用是不同颜色的便利贴(post-its)。与普通refs的不同之处是地址的命名空间,事实上remote refs 本质上也是由远程服务器所控制。git fetch可以更新它们。
tag: 一个tag既是DAG 中的一个节点,也是一个便利贴(post-its)(另一种颜色)。一个tag指向一个commit,并包括可选消息和 GPG 签名。
post-it 只是一种访问标签的快速方法,如果丢失,可以使用git fsck --lost-found找回。
DAG 中的节点可以从一个仓库移动到另一个仓库,可以以更有效的形式(包)存储,并且可以对未使用的节点进行垃圾收集。但归根结底,git存储库始终只是 DAG 和便利贴(post-its)。
历史
因此,了解了关于git如何存储版本历史的知识,我们如何可视化合并之类的操作?以及git与尝试将版本号历史管理为每个分支的线性更改的工具有何不同。
这是最简单的存储库。我们克隆了一个远程仓库,其中包含一个提交。
在这里,我们已经fetch了远程仓库并从远程仓库接收了一个新的提交,但还没有合并它。
git merge remotes/MYSERVER/master 后的情况。 由于合并是快进的(也就是说,我们在本地分支中没有新的提交),唯一发生的事情就是移动我们的便利贴并分别更改我们工作目录中的文件。
译注:事实上在进行代码合并的时候,Git通常能智能地将代码合并好,但是如果遇到不能合并的情况,Git会提示冲突。
一个本地的git commit和一个晚点的git fetch。我们有一个新的本地提交和一个新的远程提交。显然,需要合并。
git merge remotes/MYSERVER/master 的结果。 因为我们有新的本地提交,所以这不是快速进行的,而是在 DAG 中创建了一个实际的新提交节点。 注意它有两个父提交。
这是tree在两个分支上的一些提交和另一个合并之后的样子。 看到“拼接”图案出现了吗? git DAG 准确记录了所采取行动的历史记录。
“拼接”模式读起来有些乏味。 如果你还没有发布你的分支,或者已经明确表示其他人不应该以此为基础进行工作,那么你有一个替代方案。 您可以重新调整(rebase)您的分支,而不是合并,您的提交被另一个具有不同父级的提交替换,并且您的分支被移到那里。
在垃圾收集之前,您的旧提交将保留在 DAG 中。 暂时忽略它们,但只要知道如果你完全搞砸了,还有一条出路。 如果您有额外的便利贴指向您的旧提交,它们将继续指向它,并使您的旧提交无限期地保持活跃。 不过,这可能相当令人困惑。
不要在其他人在其顶部创建新提交的分支上rebase。 有可能从中恢复过来,这并不难,但所需的额外工作可能会令人心烦。
垃圾收集(或只是忽略无法访问的提交)后的情况,并在重新设置的分支的顶部创建新的提交。
rebase 也知道如何使用一个命令来 rebase 多个提交。
对于那些不想被计算机科学吓倒的人,我们对 git 的简短介绍到此结束。 希望它有所帮助!