人肉版本管理
所以我们需要专门的软件来对代码做版本管理。
版本管理的意义
要想能随时回到某个版本,我们肯定要将这些版本对应的数据存储起来才行。
两种存储方案
全量存储
- 将每个版本数据都完整保存。
- 使用时直接取用即可。
- 优点是简单快速。
- 缺点是存储了大量重复内容,占用了磁盘空间。
增量存储
- 存储一个基础版本,然后就只存储新的版本和它之间的补丁数据。
- 使用时,通过拼合来获得对应版本。
- 优点是存储小了,因为大部分的版本都只有增量数据。
- 缺点是使用时需要运算,降低了系统的性能、增加了系统的复杂性。
在了解了这两种方案以后,我们就需依场景取舍。对于编程场景,我们绝大部分时间都是在处理纯文本。这些文件本身很小,即使存储多份也不占太多空间。如果遇到特别大的文件,可以专门对其进行特殊处理。相反,版本管理工具要被团队里每一个程序员使用,保持它的简单性可能比少占一点磁盘更重要。
当然,并不是每一个工具都这么看。不知道是否和以前磁盘比较贵有关,SVN选择了增量存储;而Git选择了全量存储。正因为Git采用了全量存储,所以它本质上就变成了一个「Content-addressable 的文件系统」。
这是Git在设计上和其他那些版本管理系统最大的不同。它直接导致了其他系统的概念在Git这里是找不到对应的。这也是我们强调要回到需求层去理解Git的原因。
不同版本的文件的存放处
答案是,它们都在我们项目的根目录下的一个叫做.git的目录里边。
这和SVN喜欢把追踪文件放得每个目录都有不同,使得Git管理的项目很干净。
当我们不想要版本管理数据时,直接把这个目录删除就好了。
版本库里的文件命名
因为是在自己的版本库里边,所以这里的文件名不一定非要和原始文件一致,当然也不可能一致,因为我们要存放多份。
Git采用了SHA1函数对文件内容进行计算,然后将值作为文件名。
所谓哈希函数,是为数据创建「指纹」的方法。
比如SHA1,它能将所有传入的内容都转换为一个40位长的数字串。
- 当传入同一份数据时,每次会得到相同的输出;
- 而传入不同的数据,则会得到不同的结果。
严格的讲,也会存在多份数据对应的SHA1值相同的情况,但由于概率非常小,所以我们一般简单认为它是唯一的。而采用这种唯一哈希值作为文件名的好处就是,它保证了相同内容的文件在系统中天然就只会存在一份,不用再费心思去做排重。
然后我们需要有一种机制来将代码目录下的文件和版本库中的文件对应起来。
虽然当前文件的SHA值可以计算出来,但如果文件发生过修改,那我们只能计算出修改后的值,修改之前的SHA值我们是不知道的。
虽然当前文件的SHA值可以计算出来,但如果文件发生过修改,那我们只能计算出修改后的值,修改之前的SHA值我们是不知道的。这就需要额外的文件来描述了。
目录的支持
以上只是文件的情况,而通常我们的代码目录下除了文件还有子目录。所以这个结构还必须支持目录。
Git如何设计目录
它采用了树状结构来描述这种关系。
树结构里边只有两种节点
- 一种是叶子节点、里边直接是二进制内容。
- 一种是子树节点,它表示下边还有其他的树或者叶子节点。
这种结构其实我们非常熟悉,你可以把「树节点」想成是文件夹;把「叶子节点」想成是文件。
树结构在Git中非常的常用。
由于它同时包含有「目录结构」、「文件名称」和「它们对应的SHA值」,所以本质上来讲,它其实完整描述了一个目录或文件在某一个时间上的全部信息,如同一个「快照」一般。
在Git中我们一般通过Commit(提交)来创建这种「快照」。
它除了会记录前边提到的、关于目录和文件的tree,还会记录提交人信息和上一次提交的SHA1。
而通过在「提交」之间的切换,我们就实现了复杂结构的目录在任何存储过的版本上的「回溯」。
这种切换,在Git中是由HEAD(头)文件的指向来实现的。
暂存区
为了进一步提升使用上的便利性,Git在「代码目录」和「版本库」之间加入了「暂存区」。
假设我们要添加一个小功能,它需要修改10个文件。
我们并不希望每修改一个文件就在「版本库」中进行一次「提交」。
所以需要一个缓冲区域,可以将这些文件加入到里边,等修改完了,再一次性的放到「版本库」里边,并把缓冲清空,以便下次使用。这个缓冲区域就是「暂存区」。
在实现上,「暂存区」其实并不是一个文件夹,只是一个名为index的文件。
它甚至是放到.git目录下的。
「暂存区」还可以帮助Git识别文件状态,从而更好的生成「快照」。
- 通过对比「代码目录」和「暂存区」,我们可以知道一个文件是否是新增、删除或者被修改。
- 而通过对比「暂存区」和「版本库」,我们可以知道一个文件是否丢失、修改或者未被追踪。
有人有时理解不了Git的命令,主要就是由于无视了「暂存区」的存在而导致的。
版本管理·协同
到这里「回溯」的功能已被实现,我们可以很好的管理单独工作时的版本了。但绝大部分的工作,光靠一个人做不完。当多人参与时,会出现很多文件被不同人同时修改的问题。这时,我们就需要维护一份公有代码库了。
集中化的版本控制正是按照这个思路来做的:
- 它将版本库给挪到一台共用的版本管理服务器上去。
- 每个人开始工作前,先从服务器获取最新代码,进行自己需要的修改,然后再将修改后的代码提交回去。
- 这时候服务器会检测提交的代码是否有冲突,如果其他人先提交了修改,它就会尝试自动合并冲突。
- 但这个合并不太智能,大部分情况下都搞不定。所以它会把两个版本的修改都写入,然后要求提交人手工处理冲突。
而Git采用的是分布式的版本控制。
虽然也放了一个版本库在服务器端,但它依然保留了本地的版本库。
这样在没有网络的情况下,我们仍然可以通过本地版本库来工作。
等到有了网络以后,再来处理本地版本库和服务器端版本库的同步问题。