版本控制
# 10.版本控制
本文简单介绍下什么是版本控制,以及常见的版本控制工具
在学习之前,希望读者有一点点编程经验,并且对一些常见的 Linux 命令有所了解,这样能更好理解。
本系列主要是参考廖雪峰老师的 Git 教程 (opens new window)和《Git 权威指南》,以及自己的使用经验来撰写。
# 在版本管理工具出现之前……
举个笔者的例子。笔者在写毕业论文的时候,由于篇幅非常多,经常需要修改;但有时候可能会用到之前的版本,因此笔者会先备份之前的 Word 文档,然后再修改。这就造成了文档非常非常多……(写过毕设或其他长篇大论的文案的人应该都能体会到)。
此时,我们是手工管理版本的。对于每个文件,你可能几天内都记得每个版本的区别;但如果过了一周、过了一个月呢?除非有特别记录,相信大家都记不清了,只能一个个文件打开来,认真比对 🧐,非常麻烦。
如果是多个人共同编写一个文档,更麻烦。例如,笔者所在的公司是内网,没有腾讯文档或者石墨文档之类的在线工具,如果要填表(并且是经常需要填表),都是各自填好后,发邮件交由专门的人来合并…… 特别麻烦。
为此,我们需要这样一个软件:
- 能帮我们记录每次文件的改动
- 能够方便的查看之前的改动
- 让同事或朋友协作编辑,而不是手工传来传去,手工合并
这样的软件就叫版本控制工具,有了它,我们就脱离了手工管理版本的“史前时代”,进入到了版本控制的 20 世纪。作为程序猿,我们主要是需要版本控制工具来管理我们的一个项目的代码(当然,你也可以用它来管理其他文件的版本),并且一个大型项目经常是需要多人协作的,纯靠手工来管理代码版本不太实际。
# 常见的版本控制工具和版本库
我们依次来简单介绍以下几个工具(大部分笔者都曾在工作中用过):
- diff 和 patch 命令
- CVS:全称 Concurrent Versions System,第一个被广泛使用的版本管理工具
- SVN:修正了 CVS 的一些稳定性问题,是目前用得最多的集中式版本库控制系统
- Git:目前世界上最先进的分布式版本控制系统(没有之一),必学
- 其他版本控制工具
存储版本的地方,我们称之为版本库。例如我们要在磁盘上存储东西,肯定是以文件的方式存储:
- Git 是用一个目录来存储各个版本和差异的文件,目录名字为
.git
; - SVN 同理,用
.svn
目录来存储的 - CVS 同理,用
.csv
目录来存储的
一般情况下这几个目录是隐藏的(防止被随意的删除和修改等),在 Windows 上可以通过显示隐藏文件夹来查看,Linux 下可以用 ls -ah
命令查看
# 集中式和分布式的概念
在介绍版本控制工具之前,我们还有必要介绍一下集中式版本控制系统,和分布式版本控制系统的概念。
集中式:版本库是放在一个中央服务器上的,干活的时候,需要联网才能从服务器下载最新版本到自己电脑上(因为大多数时候我们都是在自己电脑上开发),开发完后再上传到服务器上。如果网速慢……那就有的等了。好比一个图书馆,要看书得先从图书馆里借书,用完了后要还回去,需要来来回回跑图书馆。
而且集中式容易出现单点故障的问题。万一服务器坏了,那么就完了…… 因此得额外对服务器进行备份。
图片来自 集中式 vs 分布式 - 廖雪峰的官方网站 (opens new window)
分布式:每个人自己的电脑上就有一个完整的版本库,获取版本的时候不需要联网。等需要合并文件的时候,再将各自的修改上传到中央服务器上。就好比每个人都有一个完整的图书馆,如果要合并文件,每个人都将自己的修改放到中央图书馆里,相当于只用去一次图书馆;合并完后,每个人再从中央图书馆里更新。
图片来自 集中式 vs 分布式 - 廖雪峰的官方网站 (opens new window)
一句话,集中式和分布式的区别是:你的本地是否有完整的版本库历史。每个人都是本地版本库的主人,对版本库的操作,包括查看提交日志、提交、创建里程碑和分支、合并分支、回退等所有操作都直接在本地完成而不需要网络连接。
假设 SVN 服务器没了,那就丢掉了所有历史信息,因为你的本地只有当前版本以及部分历史信息。
假设 Git 的中央服务器没了,你不会丢掉任何 git 历史信息,因为你的本地有完整的版本库信息。你可以把本地的 git 库重新上传到另外的 git 服务器。
廖雪峰老师的评论:
比特币的区块链设计就类似 git,人手一份全账本,只是用 p2p 全网同步,而 git 通常搞个中心化服务来同步
svn 像银行,完整账本只有银行有,作为终端节点可以向银行查询账本,但如果某一天银行没了,整个完整账本就没了
分布式的核心设计是同步,而不是主从。软件架构,核心思想其实是非常简单的。
# diff 命令
diff 意指 difference。diff 是命令行工具,可以通过他们来比较两个文件的差异,后续我们学习 Git 的时候,也经常用 diff 命令来查看文件的差异,diff 可以说是版本控制的基础工具。
我们来看个例子(推荐读者一起来实践)。该命令需要在 Linux 下运行,如果没有 Linux 环境,读者可以先参考下一篇博客安装好 Git 后,然后在 Git Bash 里可以运行。
首先创建两个文件,并写入以下内容:
$echo "This is hello file" > hello.txt
$echo "This is world file" > world.txt
$echo "Welcome to diff" >> hello.txt
$echo "Welcome to diff" >> world.txt
-- 此时文件内容如下
$cat hello.txt
This is hello file
Welcome to diff
$cat world.txt
This is world file
Welcome to diff
2
3
4
5
6
7
8
9
10
11
12
13
然后我们就可以用 diff 命令,来比较两个文件的差异了。我们将差异结果输出到一个文件:
$diff -u hello.txt world.txt > diff.txt
-u 参数很重要,它使得差异带有上下文。
我们查看下 diff.txt 的内容:
$cat diff.txt
--- hello.txt 2023-01-10 21:56:47.050201742 +0800
+++ world.txt 2023-01-10 22:08:41.473521836 +0800
@@ -1,2 +1,2 @@
-This is hello file
+This is world file
Welcome to diff
2
3
4
5
6
7
我们来说明下这个文件的内容:
- 文件的第 1 行和第 2 行分别记录了原始文件和目标文件的文件名及时间戳。以三个减号(---)开始的行标识的是原始文件,以三个加号(+++)开始的行标识的是目标文件。
- 文件的第 5,6,7 行,以减号(-)开始的行是只出现在原始文件中的行,以加号(+)开始的行是只出现在目标文件中的行,以空格开始的行,是在原始文件和目标文件中都出现的行,
第 4~7 行,是一个差异小节(就是两个文件之间的一个差异):
@@ -1,2 +1,2 @@
-This is hello file
+This is world file
2
3
每个差异小节以一行差异定位语句开始,其前后分别用两个 @ 进行标识。其中:
- -1,2 的含义:本差异小节的内容相当于是原始文件从第 1 行开始的 2 行
- +1,2 的含义:本差异小节的内容相当于是目标文件从第 1 行开始的 2 行
因为命令 diff 是用于行比较的,所以即使一个字不同,也显示为一整行的修改(Git 对 diff 进行了扩展,并且还提供一种逐词比较的差异比较方法)。
除了-u,diff 还有以下选项,就不一一演示了:
- -b:忽略空格
- -B:忽略空行
- -i:忽略大小写
- -c:显示文件所有内容并标识不同
- -r:对比目录
# patch 命令
patch 相当于 diff 的反向操作。有了原始文件和 diff 文件,就可以还原出目标文件。我们来实践下:
首先,用 hello.txt 的内容,将 world.txt 覆盖,这样两个文件的内容都是原始文件的内容:
$cp hello.txt world.txt
$cat world.txt
This is hello file
Welcome to diff
2
3
4
然后用 patch 命令来还原目标文件:可以看到 world 文件回来了。
$patch world.txt < diff.txt
patching file world.txt
$cat world.txt
This is world file
Welcome to diff
2
3
4
5
6
7
注意:经测试,如果 diff 和 patch 的文件中的换行符不是 Linux 环境下的 LF 换行符,那么 patch 就会失败,报错如下:
$ patch world2.txt < diff.txt
patching file world2.txt
Hunk #1 FAILED at 1 (different line endings).
1 out of 1 hunk FAILED -- saving rejects to file world2.txt.rej
2
3
4
笔者推测是这两个命令没有考虑其他换行符的情况。
# diff 和 patch 小结
严格来说 diff 和 patch 命令不是版本控制工具,而是比较差异的工具。
diff 和 patch 命令存在一个局限,就是不能对二进制文件进行处理。对二进制文件的修改或添加会在差异文件中缺失,进而丢失对二进制文件的改动或添加。
实际上,大部分版本控制工具都不能很好的管理二进制文件,虽然也能由版本控制工具来管理 ,但是不能比较两者有什么差异(一堆 0 和 1 的比较,看不出来改了什么内容的)。Git 对差异文件格式提供了扩展支持,支持二进制文件的比较,解决了这个问题。
在没有版本控制系统的情况下,可以用这 diff 和 patch 记录并保存改动前后的差异,还可以将差异文件注入版本控制系统(如果有)。
Linus 在 1991~2002 年,就是用 diff 和 patch 维护 Linux 不同版本间差异的。感兴趣的读者可以看看 Linus Torvalds 于 2007 年 5 月 3 日在 Google 的演讲:Tech Talk: Linus Torvalds on git - YouTube (opens new window)。
在当时,已经有一些版本控制工具了(CVS 和 SVN),但不好用(是集中式版本控制系统);而一些商用软件,比起 CVS 和 SVN 好用一点,但那是收费的,与 Linux 的开源精神不符合…… 因此后面 Linus 开发了 Git。
# CVS
CVS 的历史:CVS(Concurrent Versions System)诞生于 1985 年,是由荷兰阿姆斯特丹 VU 大学的 Dick Grune 教授实现的。当时 Dick Grune 和两个学生共同开发一个项目,但是三个人的工作时间无法协调到一起,迫切需要一个记录和协同开发的工具软件。于是 Dick Grune 开发出有史以来第一个被大规模使用的版本控制工具。
可以看到,CVS 是被逼出来的,用人工管理版本太麻烦了,没办法才开发出来。
CVS 的特点:
- 最早被广泛使用,到现在也有不少人用;
- 开源且免费
- 集中式版本控制系统
使用 CVS 管理版本的时候,每个目录下面都有一个目录叫做 CVS(这样实现起来简单),单独拿一个目录出来就可以当作版本库。但 CVS 也有缺点:
- 不能对重命名进行版本控制
- 缺乏对原子提交的支持,如果有网络中断或其他不可控因素,会导致客户端向服务器端提交不完整的数据(这设计就很不合理了,最基本的功能有这样的缺陷)
- 随着文件的变多,效率会越来越慢
CVS 的成功导致了版本控制系统的大爆发,各式各样的版本控制系统如雨后春笋般诞生了,并且 CVS 的不少理念都成为了后续版本控制的标准。
新的版本控制系统或多或少地解决了 CVS 版本控制系统存在的问题。在这些版本控制系统中,最典型的就是 Subversion(SVN)。
# SVN
Subversion (opens new window),由于其命令行工具名为 svn,因此通常被简称为 SVN。SVN 由 CollabNet 公司于 2000 年资助并开发,目的是创建一个更好用的版本控制系统以取代 CVS。
SVN 的前期开发使用 CVS 做版本控制,到了 2001 年,SVN 已经可以用于自己的版本控制了
SVN 实现了原子提交的功能,不会像 CVS 那样出现文件的部分内容被提交而其余的内容没有被提交的情况。
SVN 还有一个创举,就是在工作区跟踪目录下(.svn 目录)为当前目录中的每一个文件都保存一份冗余的原始拷贝。这样做的好处是部分命令不再需要网络连接,例如文件修改的差异比较,以及错误更改的回退等。
SVN 还有不少闪亮的功能特性,使得 SVN 在 CVS 之后诞生的诸多版本控制系统中脱颖而出,成为开源社区一时的新宠,也成为当时各个企业进行版本控制的最佳选择之一。
但是,SVN 在本质上并没有突破,都属于集中式版本控制系统,在查看日志和提交的时候,如果网速慢,就很让人抓狂。
对于 CVS 和 SVN 来说,笔者在公司内局域网内用过,网速还是可以的。但是论起好用,还得是 Git😁
# Git
(https://image.peterjxl.com/blog/image-20230111201130-8wqt3b6.png)
书接上文,我们来继续介绍 Git。
在 Git 出现之前,Linux 之父 Linus 坚决反对使用集中式版本控制工具,在 1991-2002 这十余年间,Linus 宁可以手工修补文件的方式维护 Linux 的代码。
经过十余年的发展,Linux 的代码库已经变的非常大了,还用手工维护非常不方便,Linux 社区意见也很大。因此在 2002~2005 年期间,Linus 选择了一个商业版本控制系统 BitKeeper 作为 Linux 内核的代码管理工具,BitKeeper 的东家 BitMover 公司出于人道主义精神,授权 Linux 社区免费使用这个版本控制系统。
2005 年发生的一件事最终导致了 Git 的诞生。在 2005 年 4 月,Andrew Tridgell(即大名鼎鼎的 Samba 的作者)试图对 BitKeeper 进行反向工程(类似通过反编译等方式获得 BitKeeper 的源码),以开发一个能与 BitKeeper 交互的开源工具。这激怒了 BitKeeper 软件的所有者 BitMover 公司,要求收回对 Linux 社区免费使用 BitKeeper 的授权。
Linus 可以向 BitMover 公司道个歉,保证以后严格管教弟兄们,但实际上 Linus 会受这气?直接花了两周时间自己用 C 写了一个分布式版本控制系统,这就是 Git!几个月之内,Linux 系统的源码已经由 Git 管理了!大家可以体会下什么叫 🐂🍺
以下是 Git 诞生过程中的大事记(来自维基百科 (opens new window)):
- 2005 年 4 月 3 日,开始开发 Git。
- 2005 年 4 月 6 日,项目发布。
- 2005 年 4 月 7 日,Git 就可以作为自身的版本控制工具了。
- 2005 年 4 月 18 日,发生第一个多分支合并。
- 2005 年 4 月 29 日,Git 的性能就已经达到了 Linus 的预期。
- 2005 年 6 月 16 日,Linux 内核 2.6.12 发布,那时 Git 已经在维护 Linux 核心的源代码了。
# 其他版本控制工具
分布式版本控制系统除了 Git 以及促使 Git 诞生的 BitKeeper 外,还有类似 Git 的 Mercurial 和 Bazaar 等。这些分布式版本控制系统各有特点,但最快、最简单也最流行的依然是 Git!
除了之前介绍的版本控制工具,还有不少商用版本控制系统,占了一部分市场份额。但商用的一般有如下缺陷:
- 采用黑盒子式的版本库设计。大部分商用产品,是不会给使用者源码的(给了谁还买啊),因此让人捉摸不透其内部是怎么实现的,也加大了将代码迁移到其他版本库的成本(笔者推测这使得企业只能一直用这个产品,然后继续付费)
- 商业版本控制工具很难定制化需求,因为公司需要赚钱,总不能免费帮忙开发需求。
- 商业版本控制工具注定是小众软件,专门提供给企业使用,培训新员工也很麻烦。
简单说说笔者知道的一些商用工具。
# ClearCase
Rational 公司(该公司后面被 IBM 收购了)出品过 ClearCase 版本控制工具,但据说很难用,搜索到的评价如下:
- 为什么有些大公司技术弱爆了? - 知乎 (opens new window):毕业第一年在腾讯工作,做 QQ 游戏大厅,当时用的 IDE 是 VC2006,用的版本控制工具,叫 ClearCase(估计用过的人不多),IBM 开发的。特点是极其严谨、非常强大,但流程极为繁琐,用起来简直让人抓狂,这还是腾讯花了 3000 万找 IBM 买的。
- 集中式 vs 分布式 - 廖雪峰的官方网站 (opens new window):特点是安装比 Windows 还大,运行比蜗牛还慢,能用 ClearCase 的一般是世界 500 强,他们有个共同的特点是财大气粗,或者人傻钱多。
笔者也没用过,暂且不表。
# VSS
微软公司出品的,搜索到的相关平均如下:
集中式 vs 分布式 - 廖雪峰的官方网站 (opens new window):微软自己也有一个集中式版本控制系统叫 VSS,集成在 Visual Studio 中。由于其反人类的设计,连微软自己都不好意思用了。
维基百科:Microsoft Visual SourceSafe 是美国微软公司出品的版本控制系统,简称 VSS。
软件支持 Windows 系统所支持的所有文件格式,兼容 Check out-Modify-Check in(独占工作模式)与 Copy-Modify-Merge(并行工作模式)。VSS 通常与微软公司的 Visual Studio 产品同时发布,并且高度集成。VSS 使用文件系统作为存储方式,每次版本变更时就需要大量地读写硬盘。这也是 VSS 最广受垢弊的缺点。VSS 虽然是微软公司的产品,但微软内部却很少使用它。微软内部使用一个名为 SLM 的版本控制系统,直至 1999 年。之后,微软内部改以使用修改自 Perforce 的 SourceDepot。
# 小结
本文主要介绍了如下内容:
- 为什么我们需要版本控制
- 集中式和分布式的概念
- 常见的版本控制工具介绍
- 简单演示了下 diff 和 patch 命令
- 简单介绍了版本控制工具的历史
在可以选择的情况下,强烈推荐使用 Git 作为版本控制管理工具。