版本控制系统的设计
为了更好的理解开发Git所需要面临的体系结构选择,我们应该了解下的不同。
一个版本控制系统(Version Control System, VCS)通常需要满足三个核心需求:
- 保存内容
- 跟踪内容的变化(历史数据,包括合并的元数据)
- 内容和历史记录分布在各个协作者
注意:第三个需求并不是所有版本控制系统的核心需求。
保存数据
通常,VCS保存内容的方式有两种,基于delta变化集和有向无环图内容展示。基于delta变化集是将不同版本的扁平化内容的差异和一些元数据封装在一起,而有向无环图则是用对象来构建一个层级,作为每个提交的快照,来反映内容的文件系统树(尽可能地重复使用树中没有发生变化的对象)。
Git采用了第二种方式来存储内容,并使用了多种不同类型的对象。这篇博文接下来的“对象数据库”一节,会描述那些在Git仓库中形成有向无环图的不同类型对象。
提交和合并历史记录
历史记录和变化跟踪,大多数的版本控制系统都使用了一下方法中的一种:
- 线性历史记录
- 有向无环图历史记录
同样的,Git采用了有向无环图方式来保存历史记录。每一次的提交都会包含关于祖先的元数据:一个提交都可以有零个或者多个(理论上是无限个)父提交。比如说,Git仓库的第一次的提交有零个父提交,而三路合并就会有三个父提交。
Git和Subversion以及它的线性历史祖先的另一个主要区别是它能够直接支持分支,来记录大多数的合并历史数据。
由于采用有向无环图的方式来存储数据,因此Git可以完全地支持分支。一个文件的历史记录从它的目录结构可以一直往上(通过表示目录结构的节点)连接到根目录,而这个根目录又会连接到一个提交节点。同样的,反过来,这个提交节点也可以有一个或者多个父亲节点。因此,相比较于从RCS衍生而来的VCS,这就让我们可以合理地用更加准确的方式来看待历史记录和数据:
- 当图中的一个内容(比如文件或者目录)节点,与另一个提交中某一个节点有着相同的引用标记(Git使用了SHA),那么这两个节点就肯定是包含相同的内容,从而让Git可以有效地分辨内容的区别。
- 当合并两个分支的时候,我们是合并在有向无环图中的两个节点的内容。相对于RCS系列的VCS,有向无环图让Git更有效地确定公共祖先。
分布
有以下几种方式:
- 仅限本地:对于那些不需要满足上面所描述的第三个功能需求的版本控制系统。
- 中央服务器:所有对仓库的变化都必须提交到某个特定的仓库,来记录变化。
- 分布式模型:协作者可以往多个公开的仓库来提交变化,同时提交可以是发送在本地,然后过后再提交到这些公开的节点,允许离线工作。
为了现在这些设计的优缺点,我们将会比较两个有着相同内容的Subversion仓库和Git仓库(比如说,Git的缺省分支的HEAD节点的内容跟Subversion仓库主干上的最新版本相同。)一个叫做Alex的开发者,有一个Subversion仓库的本地,和一个Git仓库的本地拷贝。
首先,我们需要在本地创建一个新的Git仓库:
$ mkdir testgit
$ cd testgit
$ git init
现在,我们就有了一个空的但已经初始化好的Git仓库。我们可以创建分支,提交,打标签甚至跟其他本地或者远程Git仓库通讯。通过使用一些git
命令,我们甚至可以跟其他类型的VCS仓库通讯。
git init
命令在testgit
目录中创建了一个.git子目录。我们先来观察下它的结构:
tree .git/ .git/
|-- HEAD
|-- config
|-- description |-- hooks
| |-- applypatch-msg.sample | |-- commit-msg.sample
| |-- post-commit.sample
| |-- post-receive.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-rebase.sample
| |-- prepare-commit-msg.sample | |-- update.sample
|-- info
| |-- exclude
|-- objects
| |-- info
| |-- pack
|-- refs
|-- heads |-- tags
如上面显示的,.git
目录初始是由根工作目录(testgit
)的一个子目录。.git
目录包含了一些其他的文件和目录:
- Configuration:
.git/config
,.git/description
和.git/info/exclude
文件主要是用来配置本地仓库。 - Hooks:
.git/hooks
目录包括了可以在仓库的不同生命周期事件运行的脚本。 - Staging Area:
.git/index
文件(上面并没说显示)可以为我们的工作目录提供一个集结地。 - Object Database:
.git/objects
目录是缺省的Git对象数据库,包含了所以的内容或者指向本地内容的指针。所有的对象在创建之后都不可以被改变。 - References:
.git/refs
目录是保存本地和远程的分支、标签和head的引用指针的缺省目录。一个引用是一个指向一个对象的指针,通常是tag
或者commit
类型
.git
目录是实际的仓库。工作目录包含着所有的工作文件集,并且通常是.git
目的的父目录。如果你一个没有工作目录的Git远程仓库,那么你可以使用git init --base
命令来初始化这个仓库。这个命令会直接在根目录,而不是
另外一个很重要的文件是Git index:.git/index
。这个文件本地工作目录和本地仓库之间提供一个缓存区域。这个文件是用来一个或者多个文件的特定变化,然后再一起提交。就算你