第1章 Git的版本控制之道
版本控制系统(Version Control System,VCS)可以帮助我们记录和跟踪项目中各文件内容的修改变化。
1.1 版本库
版本库(Repository)是版本控制系统用来存储所有历史数据的地方。
集中式版本库(Centralized Repository)——所有的程序员都会把他们的改动提交到服务器上的一个公共版本库中。具体来说,每一个程序员在本地有一个工作目录树,其内容是该版本库中最新的代码。
集中式版本库的局限性:
- 首先,在本地工作目录树中,只能看到代码的最新版本。如果想查询历史修改记录,就必须与服务器上的版本库打交道。
- 其次,同远程的版本库连接,通常须要使用网络。
使用分布式版本控制系统(Distributed version control system,DVCS),每个人都会在本地有自己的版本库,而不是连接到服务器上的一个公共的版本库。所有的历史记录都存储在本地的版本库中。向版本库提交代码无须连接远程版本库,而是记录在本地的版本库中。
1.2 版本库中存储什么
项目开发所必需的所有内容:
- 项目源代码
- 构建文件(Makefile、Rakefile及Ant的build.xml等)
- 配置文件样例
- 各类文档
- 程序使用的图片
- 单元测试脚本等
例外:
项目中使用的开发工具,通常不用放到版本控制中去。
1.3 工作目录树
工作目录树(Working Tree),也就是程序员进行程序开发的地方。
工作目录树是版本库的一个"断面视图"。它包括了开发该项目所需要的全部文件,包括源代码文件、构建文件、单元测试文件等。一些版本控制系统把工作目录树称为工作拷贝(Working Copy)。
在Git中,版本库不在服务器上,而存储在本地工作目录树的".git"目录中。这意味着,要想知道历史信息,只和本地的版本库打交道即可,无须与服务器上的版本库通信。
工作目录树的创建
- 第一个方法是用Git相关命令初始化版本库,也就是生成".git"目录,于是".git"目录的父目录就成了工作目录树。
- 第二个方法是克隆(Clone)一个已有的版本库,也就连带创建了相应的工作目录树。克隆一个已有的版本库,就是创建该版本库的一个拷贝,并把版本库中主分支(Master Branch)的内容检出(Check out)到工作目录树。
在Git中,检出是指把工作目录树更新,使其内容与版本库中某个特定的历史版本相同。
1.4 代码修改与文件同步
在修改了文件内容之后,须要进行单元测试以保证这次修改不会有任何负面影响,然后提交(Commit)这些改动。
每次提交操作都使得版本库中新增一个版本(Revision)。除了记录改动内容本身外,版本库还记录改动的日志信息(Log Message)或称提交留言(Commit Message),以便将来能够很方便地查询为什么要做这个改动,并能够很方便地查找某个缺陷是何时引入的。
使用像Git这样的分布式版本控制系统时,除了把改动提交到本地版本库之外,还要通过某种方式将改动共享,以便其他程序员能够得到。因此,须要把改动推入(Push)到上游版本库(upstream repository)。
上游版本库是一个公共版本库。一般来说,程序员们都把自己的改动推入到这里。
这里所说的推入,是指把自己锁在版本库中的内容,推入带另一个版本库中。把改动推入公共版本库后,其他程序员就能"看到"了。
把远程版本库中的改动拿到本地版本库中,需要两步操作。
- 第一步,把改动取来(Fetch),把远程版本库中的版本和分支复制到本地版本库中。这有点像是推入操作的反操作:推入操作时把本地版本库中的改动发送给另一个版本库,而取来操作是把远程版本库中的改动取到本地版本库中。
- 第二步,在本地版本库中,把从远程版本库里取来的改动与自己本地的改动合并(Merge)。Git工具包提供了这样的合并工具。
一般来说,取来操作额合并操作总是先后执行的。因此,在Git中可以用一个命令完成这两步操作:拖入(Pull)。
1.5 跟踪项目、目录和文件
Git记录和跟踪版本库中组成文件的各部分内容。Git并不把整个文件作为不可分的整体来记录和跟踪,而是记录和跟踪组成该文件的各部分内容,也就是若干字符和代码行(它们构成了变量和函数)。Git为这些内容添加一系列元数据,比如所在文件的文件名、文件属性,以及该文件是否为符号链接等。
这样做具有许多优势:
- 首先,它能显著降低版本库存储全部历史版本所需的磁盘空间。
- 其次,它使得"判断某个函数或类在文件间移动和拷贝的情况"之类的操作变得快捷容易。
而对这些文件的修改操作,则与寻常无异。所有的修改都在工作目录树中完成。工作目录树由目录和文件组成,它们是版本库中的一个"断面视图"。
版本库中的文件和目录结构的组织方式是对应于实际项目的。大部分项目遵循特定的目录组织结构。Git本身并不规定如何在版本库中组织这些模块。Git支持任意的组织方式。
1.6 使用标签跟踪里程碑
使用标签能够记录下版本库在特定历史时刻的"断面视图",以便于日后查找和恢复。
标签以一个简单的名称(即标签名)来标记版本库历史中某个特定的点。
本质上,标签是一个对于使用者来说易于理解易于记忆的名字,用来标识版本库中一个难读难记的内部版本号,以此帮助使用者跟踪版本历史。
1.7 使用分支来跟踪并行演进
上图展示了分支原理。主分支(Master Branch)是研发的主线。
一些版本控制工具也把主分支称作主干(trunk)。
分支可以长期存在,也可以仅存在数小时。分支可以合并到别的分支,但并非所有的分支都必须合并。
有些情况下,分支不应该合并,譬如用分支来记录项目的不同发布版本的开发时;用分支来记录试验性的工作时也是这样,也许试验结束后分支就删除了。
分支也可以在本地创建,并留作私用。创建本地分支并留作私用是有意义的。在完成试验性的工作后,如果有价值,再让大家拿到也不迟,而如果没价值,那就把它消无声息地删除。
1.8 合并
合并操作把两条或两条以上的分支合并到一起。
Git会自动处理分支合并,当Git不能自动合并时,就会提示冲突(conflict)。Git有好几种解决冲突的方法。
Git也提供自动记录和跟踪合并的功能,称为合并跟踪(merge tracking)。
1.9 锁机制
当程序员从版本库检出某个文件时,版本库禁止任何其他人修改这个文件,直到该程序员捡入(check in)为止。这种锁机制称为严格锁(strict locking)。
另一种锁机制,乐观锁(optimistic locking),允许多个程序员同时修改同一文件。乐观锁机制基于一个假定:大多数时候,这种并发修改不会引起冲突。