版本控制
# 00.版本控制
本文简单介绍下什么是版本控制,以及常见的版本控制工具
在学习之前,读者应具有一定的编程经验,并且对一些常见的Linux命令有所了解。
本系列主要是参考廖雪峰老师的Git教程 (opens new window)和《Git权威指南》,以及自己的使用经验来撰写。
# 在版本管理工具出现之前……
举个笔者的例子。笔者在写毕业论文的时候,由于篇幅非常多,经常需要修改;但有时候可能会用到之前的版本,因此笔者会先备份之前的Word文档,然后再修改。这就造成了文档非常非常多……(写过毕设或其他长篇大论的文案的人应该都能体会到)。
此时,我们是手工管理版本的。对于每个文件,你可能几天内都记得每个版本的区别;但如果过了一周、过了一个月呢?除非有特别记录,相信大家都记不清了,只能一个个文件打开来,认真比对🧐,非常麻烦。
如果是多个人共同编写一个文档,更麻烦。例如,笔者所在的公司是内网,没有腾讯文档或者石墨文档之类的在线工具,如果要填表(并且是经常需要填表),都是各自填好后,发邮件交由专门的人来合并…… 特别麻烦。
为此,我们需要这样一个软件:
- 能帮我们记录每次文件的改动
- 能够方便的查看之前的改动
- 让同事或朋友协作编辑,而不是手工传来传去,手工合并
这样的软件就叫版本控制工具,有了它,我们就脱离了手工管理版本的“史前时代”,进入到了版本控制的20世纪。作为程序猿,我们主要是需要版本控制工具来管理我们的一个项目的代码(当然,你也可以用它来管理其他文件的版本),并且一个大型项目经常是需要多人协作的,纯靠手工来管理代码版本不太实际。
# 常见的版本控制工具和版本库
我们依次来简单介绍以下几个工具(大部分笔者都曾在工作中用过):
- diff和patch命令
- CVS:全称Concurrent Versions System,第一个被广泛使用的版本管理工具
- SVN:修正了CVS的一些稳定性问题,是目前用得最多的集中式版本库控制系统
- Git:目前世界上最先进的分布式版本控制系统(没有之一),必学
- 其他版本控制工具
存储版本的地方,我们称之为版本库。例如我们要在磁盘上存储东西,肯定是以文件的方式存储,例如Git是用一个目录来存储各个版本和差异的文件,目录名字为.git
;而SVN同理,用.svn
目录来存储的,CVS同理。一般情况下这几个目录是隐藏的(防止被随意的删除和修改等),在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
然后我们就可以用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命令来还原目标文件
$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
可以看到world文件回来了。
注意:经测试,如果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),大概1小时左右,英文字幕
在当时,已经有一些版本控制工具了(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
书接上文,我们来继续介绍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 **是美国微软 (opens new window)公司出品的版本控制系统 (opens new window),简称VSS。
软件支持Windows系统所支持的所有文件格式,兼容Check out-Modify-Check in(独占工作模式)与Copy-Modify-Merge(并行工作模式)。VSS通常与微软公司的Visual Studio (opens new window)产品同时发布,并且高度集成。VSS使用文件系统作为存储方式,每次版本变更时就需要大量地读写硬盘。这也是VSS最广受垢弊的缺点。VSS虽然是微软公司的产品,但微软内部却很少使用它。微软内部使用一个名为SLM的版本控制系统,直至1999年。之后,微软内部改以使用修改自Perforce的SourceDepot。
# 小结
本文主要介绍了如下内容:
- 为什么我们需要版本控制
- 集中式和分布式的概念
- 常见的版本控制工具介绍
- 简单演示了下diff和patch命令
在可以选择的情况下,强烈推荐使用Git作为版本控制管理工具