程序员必备技能之 Git 的体系结构与历史
作者 | Will Hay Jr.
译者 | 梁蕊
责编 | 屠敏
出品 | CSDN(ID:CSDNNews)
截至 2018 年,全球知名的 IT 技术问答网站 Stack Overflow 调查的 74000 开发人员中,将近 90% 的人表示更喜欢使用 Git 进行版本控制。根据调查,Git 在其他所有分布式版本控制系统中占主导地位,并且从 2017 年开始使用率几乎增长了 20%。然而,Git 并不总是像这样普及。让我们一起看看它上升到大众化的历程。
早期历史
Git 诞生于 Linux 内核社区对可用的 VCSs(版本控制系统)的挫败感。Linux 内核的发展在当时是相当不寻常的:项目中有大量的贡献者而且贡献者的参与程度和对代码知识库的了解有很大的差异。由于 Linux 内核不寻常的发展状况,开发人员很难找到适合他们需求的 VCSs(版本控制系统)。于是他们选择了 BitKeeper 和并发修订系统(CVS),每个系统有一组核心开发人员去负责管理内核的开发。BitKeeper 提供分布式版本控制,而 CVS 是一个客户端-服务端版本控制系统,它可以让开发人员“签出”项目的副本,进行更改,然后将他们的改变“签入”到服务端。
在 2005 年初期,BitKeeper 的版权持有人 Larry McVoy 宣布撤销允许免费使用 BitKeeper 软件的许可。他声称,正在创建与 BitKeeper 反向交互软件的澳大利亚程序设计师 Andrew Tridgell 反向设计了 BitKeeper 的源代码,这样违背了它的许可。许多依赖 BitKeeper 免费软件去开发 Linux 内核的 Linux 核心开发者现在已经无法继续使用它了。
Linux 社区与 BitKeeper 的关系已经开始有了冲突,但是他们希望在离开 BitKeeper 之前有一个可行的选择。Linux 内核主要的开发人员 Linus Torvalds 在看到没有其他免费的选择可以满足他们的需求之后就开始开发一个新的 VCS。在发送到内核邮件列表的电子邮件中,Linus 表示了他对 BitKeeper 有多么满意和 BitKeeper 为 Linux 内核开发做了什么,主要是它帮助整个团队更细粒度的保留了一些更改和更改追踪集的视图。值得注意的是,虽然 BitKeeper 没有成功,但它依然在改进内核开发方式上非常有帮助。
早期发展
为了向团队提供 BitKeeper 的替代品,Linus 概述了一些新版本控制系统的某些设计标准。他想要保持 BitKeeper 提供给团队的一些好处,同时进行一些改进。
他主要强调了三个主要特征:防止内容腐败的保障措施、高可用性和分布式开发工作流。Linus 还强调了补丁不应该超过 3 秒,引用源控制管理系统,该系统需要花费 30 秒来推送补丁并且更新相关的元数据。对于从事开发 Linux 内核的 250 名开发人员来说,这样的一个系统,显然不能很好的扩展。尽管 BitKeeper 对 Git 的创建有早期的影响,但 Git 比 BitKeeper 允许更多的分布式和本地工作流。项目协作者可以在存储库离线工作,增量提交,确定何时发布他们的工作,选择共享哪些更改,并将他们的更改推送到不同的分支。
架构概述
一个版本控制系统通常有三个核心功能,所有的这些功能都被 Linus 内置在 Git 中。它必须能够存储内容、追踪对内容所做的更改(所有的历史纪录,包括合并元数据)、与项目协作者选择分发内容和提交历史记录。
Git 使用了有向无环图(DAG)进行内容存储以及提交和合并历史记录。DAG 是一个有着有限数量的顶点和边(顶点之间的连接),没有包含循环的有向图(是非周期性的)。非循环意味着没有办法从A节点到B节点,并通过任意数量的边返回到 A 节点。DAG 也必须有拓扑排序,这意味着在一个序列中,所有的顶点都有直接从最开始的节点指向最后节点的边(如下图中箭头从左上角指向右下角所示)。
Git 还将这种有向无环图结构应用在存储内容上。Git 实质上是一个可寻址的文件系统,它由构成层次结构的对象组成,这些层次结构反映了内容的文件系统树。Git 有三种主要的原始内容,它用来表示存储库存储的内容:树、blob和提交。所有的内容实质上都作为树或 blob 对象存储。Blob 是存储在存储库中的文件,树对象引用了其他子树或者 blob。你可以认为 blob 是存储内容的文件,而树就像是目录。在另一方面,提交对象有三个主要的属性,它指向了在提交时代表项目顶级快照水平的文件系统树、它还包含对它之前提交的引用,提交作者的字段和可选的提交信息。
所有这些对象基元都有 40 位的 SHA 哈希。两个相同的对象将会有相同的哈希值,不同的对象将会有不同的哈希值。通过使用 SHA 哈希作为参考标识, Git 能够有效的计算差异。为了防止数据损坏,可以重新计算一个对象的哈希值,以便轻易的识别出损坏或丢失的数据。
Git 还使用了有向无环图来追踪内容更改的历史记录。如上所述,每一个提交对象都包含它祖先的元数据,也就是说一次提交可以有任意数量的父提交。Git 使用有向无环图的特性来存储内容、保留历史追踪记录和合并历史记录,这样允许它保留完整的分支功能,因为文件的历史记录将其目录结构一直链接到根目录和提交对象。
分支策略
当我们将“feature7”分支合并到 master 分支时,Git 会执行“fast-forward”合并策略,向前移动主分支指针。只有将当前“feature7”分支的提交历史记录要合并到 master 分支的最新提交时,才会使用“fast-forward”合并。
当你所在分支的提交并不是你正在合并的分支的直接祖先时,Git 使用不同的合并策略,这意味着你的开发历史不同。在这种情况下,Git 使用“递归”策略并执行三向合并。Git 创建了文件状态的新快照和指向新快照的分支提交对象。此时这个合并提交对象有两个父类,指向两个分支的头部的提交对象被合并在一起。Git 使用非线性内容存储策略和提交历史记录的系统,可以将项目的两个分支无缝的合并在一起。
分布和初始化
Git 使用分布式模型处理项目协作者之间的内容和历史分布,用户可以离线工作并在本地存储库上进行提交。每个协作者都有一个 Git 存储库的副本,他们可以离线工作,进行更改,提交更改,并且从远程存储库中提取新的更改以保持本地副本最新。当协作者准备好共享他们的更改时,他们可以将这些更改推送到可公开访问的存储库,供其他协作者访问。一旦公共存储库验证了这个提交可以应用于被推送到的分支,就会为公共存储库创建在本地存储库中创建和存储的相同对象,并更新该存储库以供所有协作者访问。
要初始化本地 Git 存储库,请运行 "Git init" 命令。这将在本地文件系统上创建一个新初始化的存储库,在当前工作目录中创建一个 .git 目录。.git 目录是根 "工作目录" 的子目录,并作为实际的本地存储库,包含各种配置文件,对象数据库,分支的引用指针以及可在项目生命周期的各个点运行的其他脚本。一旦你对文件进行了修改,就会创建另一个重要文件,Git index,位于 .git/index 下。Git index 文件是工作目录和本地存储库之间的暂存区域,在要提交的一个或多个文件中暂存特定的更改。
值得注意的缺点
Git 使用工具包编写的设计理念与 Linux 社区中使用和构建的命令行工具相同。虽然工具包设计为用户提供了 Git 大量功能更细粒度,更低级别的访问,但由于大量的命令可能对许多不熟悉命令行工具的人或其他 VCS 使用者不直观,所以新用户的学习曲线很陡峭。Git 还缺乏链接和构建到其他服务和应用的能力。许多在 Git 上构建或正在构建工具的应用程序开发者抱怨缺乏可连接的库。Git 的二进制文件是不可重入的,这意味着它不能在执行过程中被中断,之后安全地再次被调用。这会强制一些使用该二进制文件的应用程序或 Web 服务执行并调用该二进制文件后,在再次调用它之前等待其完全执行,影响应用程序的速度。有几个项目正在努力弥补这种缺乏可连接库的情况,其中最著名的是 libgit2,一个 Git 的跨平台可连接库实现。
Git 的另一些问题是它无法处理大文件或者大量的文件。如果你的项目包含很多非文本文件,比如图像,那么经常地更新 Git 将变得非常慢,使得最大的实际存储库大小只有几 GB。
最后
Git 的设计几乎完全符合了 Linus 和 Linux 团队所寻找的需求。它满足了 Linus 描述的 VCS 的每个核心需求,并且在使用时尽可能优雅且简单地做到尽可能高效。虽然 Git 存在一些小问题,但它的设计非常好,并且将在未来的许多年内继续成为 VCS 的首选。
原文:https://medium.com/@willhayjr/the-architecture-and-history-of-git-a-distributed-version-control-system-62b17dd37742本文为 CSDN 翻译,如需转载,请注明来源出处。