从 CVS 到 Git:三十年源代码管理变革,Git 为何至今无可替代?
版本控制系统(VCS)出现前的时代:正式版本控制出现前的做法
2010 年之后入行的从业者可能不清楚当时的情况有多糟糕。回顾那段历史很有必要,因为它能解释后续出现的系统为何是现在这个样子。CVS、Visual SourceSafe 以及最终的 Git,它们的每一项设计选择都是对之前失败模式的回应。当时,人们使用文件名包含日期的压缩文件,例如 `project-2003-04-15.zip`。备份目录里堆满了这样的文件,周一早上大家总会讨论“我觉得当前的工作版本在周二的压缩文件里”,因为周五的文件可能存在一个没人记得是如何引入的 bug。还有通过手动版本管理的网络共享文件夹,文件名诸如 `FINAL`、`FINAL_v2`、`FINAL_FINAL`、`FINAL_REALLY`、`FINAL_USE_THIS_ONE`,这成了大家都经历过的一个玩笑。早期的 VAX/VMS 系统会在文件系统层面为每次保存操作自动进行版本管理,例如 `MYFILE.TXT;3` 代表该文件的第三个版本,这种方式自动且透明,老一辈的 VMS 用户至今仍对此念念不忘,因为之后再没有如此无缝的版本管理方式了。此外,很多公司在 20 世纪 90 年代末还在采用通过白板或邮件锁定文件的做法(比如“我正在编辑 UTILS.BAS,请勿触碰”)。每个开发者都会随身携带一叠标有日期的 3.5 英寸软盘,里面存放着“工作版本”,这就相当于个人版的网络共享文件夹。关注这些情况的意义在于:在正式的版本控制出现之前,每个开发者都有自己的临时管理系统,但大多数都不太靠谱。那些“编写脚本每晚对源代码树进行压缩”的开发者,和后来使用 Git 的是同一批人,只是当时工具更差,焦虑感更强。
基于锁机制的时代:SCCS 和 RCS
最早的正式版本控制系统诞生于贝尔实验室和早期的 Unix 研究社区。1972 年,Marc Rochkind 开发了源代码控制系统(SCCS)。它基于文件,支持单用户操作,采用锁机制。如今,一些 Unix 系统仍会附带这个系统,不过通常只在手册页里作为脚注提及。1982 年,普渡大学的 Walter Tichy 开发了修订控制系统(RCS),它在增量存储和单文件版本跟踪方面表现更出色,并且是后来多个系统的基础。这两个系统都采用锁机制。在 RCS 中编辑文件时,需要运行 `co -l` 命令将文件标记为锁定状态,然后才能进行编辑,直到运行 `ci` 命令后,其他用户才能操作该文件,而且一次只能编辑一个文件,此时还没有“项目”的概念,版本控制的单位是单个文件。这个时代为后续的源代码管理引入了一些术语,如增量(即版本间差异的高效存储)、检出和检入,以及将历史记录作为一等公民的概念。RCS 的存储格式(`.v` 文件)在几年后成为了 CVS 的磁盘存储基础。基于锁机制的时代在当时的条件下(单工作站、无真正的网络)是合理的,但对于大型团队来说就不合适了。接下来三十年的版本控制历史,就是不断尝试突破这些限制的过程。
CVS:支持并发编辑的服务器端系统,但存在诸多问题
CVS 是第一个可扩展的系统,从 1990 年到 2005 年左右,它在开源领域占据主导地位。1986 年,Dick Grune 开发了最初的并发版本系统(CVS)。1989 年,Brian Berliner 接手并发布了大家熟知的版本。CVS 基于 RCS,使用 `.v` 文件作为单文件存储后端,但它增加了一个重要功能:并发编辑。多个开发者可以检出并编辑同一个文件,CVS 会在提交时合并他们的更改。这一改变使得基于锁机制的模式在严肃的开源工作中被淘汰,并引入了“仓库”这一概念,将其作为一等公民,人们可以克隆、贡献和标记仓库。然而,CVS 未能解决的问题几乎涵盖了所有方面,这最终导致它变得难以忍受。它不支持原子提交,涉及五个文件的提交会分别提交每个文件,如果网络在提交过程中中断,仓库就会处于不一致的状态,需要手动进行协调。虽然存在分支功能,但创建分支的速度很慢,对于大型仓库来说,有时需要几分钟,而且合并操作需要手动进行,并且很脆弱。重命名操作根本无法跟踪,移动文件意味着删除并重新添加,会丢失整个历史记录。二进制文件的支持较差,因为差异比较假定文件为文本格式。它没有目录版本控制功能,只能对文件进行版本控制,而不能对项目结构进行版本控制。此外,仓库本身只是服务器上一堆脆弱的 `.v` 文件,备份是 IT 部门的事情,如果服务器磁盘出现故障,历史记录就会丢失。在 1995 年到 2005 年期间使用 CVS 进行严肃开发的人都有类似的经历:提交时出现合并冲突,部分文件提交成功,部分失败,导致主干处于无法描述的状态;创建分支速度慢,团队因为创建一个分支需要五分钟,合并需要一个下午而避免使用分支;出现“有人删除目录,CVS 崩溃”这类问题。尽管存在这些问题,CVS 仍然占据主导地位,因为它是免费的,并且在所有 Unix 系统上都可用,直到 2000 年都没有真正的替代方案。开源项目依赖它,因为除此之外别无选择。
Visual SourceSafe:并行的商业版本控制系统
当 Unix 世界在使用 CVS 时,微软的开发环境有自己的一套体系,大多数 VB6 和早期 .NET 开发者实际上在使用 Visual SourceSafe。1992 年,位于山景城的 One Tree Software 公司开发了 SourceSafe。1994 年,微软收购了该公司,并将产品重新发布为 Visual SourceSafe,将其与 Visual Studio、Visual Basic 以及其他微软开发工具捆绑销售,这种情况一直持续到 21 世纪中期。该系统默认采用基于锁机制的悲观并发模式,即检出文件、编辑文件、再检入文件。在文件被锁定期间,其他开发者可以查看但不能编辑。虽然支持多检出,但这很危险,因为 VSS 的合并工具很粗糙,大多数团队都有不成文的规定禁止这样做。微软选择了看起来更安全的锁定规则,与 Unix 系统的做法不同,并使其成为整整一代 Windows 开发者的默认选择。VSS 最臭名昭著的问题是数据损坏。它将数据存储在自定义的文件数据库(共享网络目录中的 `.dat` 文件)中,这种格式很脆弱。并发访问,尤其是在缓慢或不稳定的网络共享环境下,可能会损坏数据库。“VSS 数据库损坏,运行分析工具,如果分析工具无法修复,则从昨晚的备份中恢复”是微软开发环境中有文档记录的 IT 操作流程,而非最坏情况下的应对措施。每个在生产环境中使用过 VSS 的人都至少有一个数据损坏的故事,那些没有遇到过的人只是在问题出现之前就放弃了使用。另一个问题是精神负担。锁定规则意味着开发者在处理重叠代码时,必须通过口头沟通来协调,无法并行工作。在我 20 世纪 90 年代末工作过的公司里,每天早上九点的站立会议上,大家都会问“你完成 `frmMain.frm` 的编辑了吗?”。虽然 VSS 有分支功能,但很少被使用,因为创建分支速度慢,合并操作很痛苦,所以典型的 VSS 开发环境通常只维护一个主干,并从该主干进行发布。尽管存在这些问题,VSS 仍然存在,因为它与 Visual Studio 捆绑销售。它与微软的工具链集成良好,对于本地网络上的小团队来说还能凑合使用,而替代方案要么是 CVS(Windows 开发环境大多不使用),要么是根本没有源代码管理,这在 20 世纪 90 年代末的微软开发项目中是很常见的情况。2005 年,微软最终用 Team Foundation Server 取代了 VSS,后来演变为 Azure DevOps。到 21 世纪 00 年代后期,VSS 被弃用。大多数公司迁移到了 TFS,或者在 Git 出现且表现良好后,直接转向了 Git。
Subversion:“改进版的 CVS”
在 Git 出现的两年前,Subversion 本应取得成功,而且几乎做到了。2000 年,CollabNet 启动了 Subversion 项目,明确提出要“改进 CVS”。后来它成为了 Apache 的顶级项目,并于 2004 年 2 月发布了第一个稳定版本 1.0。该系统由了解 CVS 缺陷的人设计,逐一解决了这些问题。SVN 解决了很多重要问题。它支持原子提交,即多文件提交要么全部成功,要么全部失败,仓库不会处于部分更新的状态。它实现了真正的分支功能,通过低成本的服务器端复制,使分支和标签成为一等操作,而不是需要花费数小时的操作。它支持目录版本控制,这意味着重命名操作可以被跟踪,项目结构和文件内容一样可以进行版本控制。二进制文件得到了很好的支持。客户端 - 服务器协议基于 WebDAV,与 HTTP 基础设施配合良好,这对于需要通过企业代理进行源代码管理的公司来说很重要。然而,SVN 未能解决根本的集中式模型问题。没有网络连接就无法提交代码,仓库仍然依赖于单个服务器。“离线工作”意味着“只读”,可以浏览历史记录,但无法记录新的工作,服务器宕机的那天,整个团队都只能等待。SVN 几乎取得了成功。从 2004 年到 2008 年,它显然是“比 CVS 更好”的选择,许多项目从 CVS 迁移到了 SVN,但他们没有意识到分布式版本控制系统(DVCS)即将带来巨大的变革。21 世纪 00 年代中期,我使用过 SVN,还记得原子提交带来的解脱感,就像长途飞行后享用一顿美餐一样。但这种情况并没有持续太久,因为下一个系统不仅实现了 SVN 的所有功能,还完全摆脱了服务器的限制。
2005 年 4 月:版本控制永远改变的一个月
这个故事非常精彩,但大多数 35 岁以下的开发者可能并不了解。故事的起因是 BitKeeper。Larry McVoy 开发的 BitKeeper 是一个专有的分布式版本控制系统,大约在 2000 年推出。2002 年到 2005 年期间,Linux 内核社区使用了该系统。Linus 选择它是因为它是唯一能够处理内核规模和大量合并操作的 DVCS,而且 McVoy 为开源项目提供了免费许可,但条件是不能尝试逆向工程该协议。2005 年 4 月,许可协议出现了问题。以 Samba 和 rsync 闻名的 Andrew Tridgell 开始编写一个与 BitKeeper 仓库交互的工具。McVoy 认为这违反了许可协议条款,于是撤销了对 Linux 内核社区的免费许可。当时,Linux 内核项目是全球最大的协作软件开发项目,突然失去了版本控制系统。2005 年 4 月 3 日,Linus 开始编写 Git。4 月 7 日,完成了第一个自托管提交。十天内,他就开发出了可用的版本;几个月后,Git 开始用于跟踪 Linux 内核。Git 的设计约束直接来源于他使用 BitKeeper 的经验以及对之前所有系统的不满。它采用分布式架构,而非集中式架构,每个克隆都是一个完整的仓库,包含完整的历史记录,没有单点故障。它的速度足够快,能够处理内核的规模,而其他系统在这方面都表现不佳。它采用内容寻址的对象数据库,每个数据块、树和提交都通过其 SHA - 1 哈希值进行标识,这使得仓库在结构上成为一个 Merkle 树,能够检测到数据损坏,而不是默默接受。它支持低成本的分支操作,分支只是指向提交的指针,因此创建分支的操作复杂度为 O(1),而不是像 CVS 那样需要几分钟。它默认采用三方合并算法,而不是手动协调步骤。2005 年,并非只有 Git 这一个分布式版本控制系统。2005 年 4 月,Matt Mackall 几乎与 Linus 同时开发了 Mercurial(Hg),它做出了不同的设计选择:内部模型更简单,命令行界面更友好,性能相当。2005 年晚些时候,在 Canonical 的支持下,Bazaar(bzr)问世,用于 Ubuntu 和 Launchpad 项目。早期的 DVCS 项目,如 Monotone、darcs 和 GNU arch,为这一领域提供了参考,但都没有得到广泛应用。2005 年到 2010 年期间,版本控制系统的发展使得这一领域的设计逐渐趋于统一。Linux 内核立即采用了 Git。2006 年到 2008 年期间,其他开源项目从 CVS 和 SVN 迁移到了 Git 或 Mercurial。社区在一段时间内分裂为 Git 和 Mercurial 两派,最终 Git 获胜,部分原因是 Linux 内核的光环效应,但主要还是因为 GitHub。Git 并非是对之前系统的渐进式改进,而是一次彻底的突破。它是由一位对之前所有模型都感到失望的开发者在两周内开发出来的。2005 年 DVCS 的变革将版本控制系统的设计空间压缩成了一种至今仍占主导地位的模式。
GitHub:让 Git 成为默认选择的平台
Git 工具是 Linus 开发的,但让 Git 成为文化默认选择的是 GitHub。2008 年 2 月,Tom Preston - Werner、Chris Wanstrath 和 PJ Hyett(后来 Scott Chacon 也加入了)推出了 GitHub。最初的定位很简单:一个带有 Web 界面的托管 Git 服务器。但 GitHub 真正带来的是拉取请求(pull - request)工作流程。分支、差异比较、审查、合并等协作模式,虽然 Git 在原理上支持,但 GitHub 让这些操作变得可视化、可点击且具有社交性。除非付费,否则仓库默认是公开的。
从业者的经历,每个系统的代价以及 Git 带来的回报
作为一名自 1990 年起使用过所有主流系统,且在多数系统中都有过代码丢失经历的从业者,我深刻体会到了每个系统的优缺点。从早期使用压缩文件和手动版本管理,到后来的 CVS、Visual SourceSafe、Subversion,再到如今的 Git,每一次的变革都带来了新的体验和挑战。CVS 虽然在开源领域占据主导地位多年,但它存在诸多问题,如不支持原子提交、分支创建速度慢、重命名操作无法跟踪等。Visual SourceSafe 则以数据损坏和精神负担大而闻名,尽管它与微软的工具链集成良好,但最终还是被淘汰。Subversion 解决了 CVS 的一些问题,但仍然未能摆脱集中式模型的限制。而 Git 的出现,彻底改变了源代码管理的格局。它的分布式架构、快速的分支操作、强大的合并算法等特点,使得开发者能够更加高效地协作和管理代码。同时,GitHub 的出现让 Git 的使用变得更加便捷和社交化,进一步推动了 Git 的普及。
结语
从 CVS 到 Git,三十年的源代码管理发展历程见证了技术的不断进步和变革。Git 的出现不仅解决了之前系统存在的诸多问题,还为软件开发带来了更高的效率和更好的协作体验。尽管未来可能会出现新的挑战和机遇,但 Git 作为目前最主流的版本控制系统,将继续在软件开发领域发挥重要作用。
