Git教程 · 工作流之项目设置
在前面的章节中,我们学习了 Git 的基本概念。其中只涉及到了一些最重要的命令及其最重要的一些参数。
使我们选择了这样的话题,你或许还是会问:那么,我们究竟应该多久进行一次重新合并或变基呢?
而且,你也肯定已经通过互联网搜索以及亲身遇到的各种应用接触到了更多的命令及其参数。这种灵活性既是Git 强项也是它的缺点。
我们接下来要谈的工作流是 Git 在项目开发过程中的一个典型用途。在这里,我们所要介绍的重点是如何完成任务,而不是介绍更多参数。每个工作流的都只有一个解决方案来说明。这说明在细节上要做到尽可能地详尽,这样才能让工作流满足我们的工作,并且不用频繁地查看帮助文档。
即使你有多年的 Git 使用经验,也会遇到一些平常不太需要做的任务,例如分割某个版本库。在这种情况下,工作流可为相关的命令做一个简要说明。甚至在任务需要的情况下,我们还会在工作流中还会用到一些更鲜为人知的命令和参数。因此,你也确实需要学习更多用Git 做事的方式。
1️⃣ 何时使用工作流
谈到工作流选择这个话题,我就得来观察一个典型的项目开发过程。
-
项目开始阶段
如果你是从头开始了一个新项目,并已决定了用 Git 来进行版本控制。那么, 你的第一个任务就是为版本化的工作选择并设置一个基础架构。而如果我们面对的是一个需要被迁移到现有Git项目中的项目,那需要用到第14章(【Git教程】(十四)相同分支上的开发— …)中所介绍的工作流了。 -
项目开发阶段
一旦我们定义好了基础架构,对于分支的处理就应当有明确的团队分工了。我们要么就让所有的开发者都在同一分支上工作,要么我们就得为每个任务创建一个独立的特性分支,我们将会在第15章中(【Git教程】(十五)特性分支上的开发— …)介绍该工作流。如果在开发过程中突然出现了一些之前版本中不存在的错误,第16章中(【Git教程】(十六)二分法排错— …)所介绍的工作流可能会有助于我们找到问题的根源。
为了确保修改的质量,我们还应该设置一些自动化构建和测试。而如何让 Git 在一台构建服务器上工作,就请参考第17章中(【Git教程】(十七)基于构建服务器的工作— …)所介绍的工作流,如果你目前还没有使用Git 来对项目进行版本控制,但仍希望在工作中使用Git, 那么你可能会对第22章中(【Git教程】(二十二)与其他版本控制系统并行使用— …)所介绍的工作流感兴趣。
-
项目交付阶段
在当前版本开发完成之后,我们应该要确保交付给客户是一个经过良好测试的产品。与 此同时,要为下一次的发布以及解决最新发行版中尚未解决的问题做好准备。我们将会在第18章中(【Git教程】(十八)发行版交付— …)讨论该工作流。 -
项目重构阶段
项目的需求会随着时间和新的理解而产生变化。因此,我们必须要重新考虑到自己会将 一个项目分割到多个 Git 版本库中的可能性。铁板一块的项目即使最初规模很小,也是会增长的,它必须被模块化。对此,你可以参考第19章中(【Git教程】(十九)拆分大项目— …)的工作流,我们将会为你介绍如何将一个大型版本库分割成几个较小的版本库。
当然,也有可能出现相反的情况。处理一个被分割到多个版本库中的项目可能过于复杂了。对此,你可以参考第20章中(【Git教程】(二十)合并小型项目— …)的工作流,看看如何将这些版本库重新合并起来。
当某个项目已经存在了很长一段时间,经历过多次修改,相关版本库会增加到非常大的规模。其所有文件的历史记录中包括了每个开发者的本地工作区中的文件。特别地,当早期版本中有大型二进制文件被版本化的话,就很可能会带来不必要的资源消耗。对此,我们将 会在第21章中(【Git教程】(二十一)外包长历史记录— …)介绍如何将一个项目的历史记录分割,并且让每个开发者只在本地存储较新的版本。
2️⃣ 工作流的结构
下面来看一下上述这些章节在介绍工作流时的结构安排,这里对每一节都做一个简短的介绍。
-
条目
每个工作流开头都会有一个简短的动机说明,用来解释我们会基于什么原因以及在什么情况下使用这个工作流,描述的是其中心任务中可能的决定和特定的功能。通常在结尾处还会有一段关于该工作流的要点总结。看完了这一节,你就能判断自己的项目是否与该工作流相关了。 -
概述
在这一节中,我们将会以具体实例的方式来为你描述该工作流的基本过程,并介绍完成该工作流所需要的基本术语、概念和Git 命令。看完了这一节,我们将会了解该工作流的工作原则以及其中会用到什么Git 资源。 -
使用要求
每个工作流都只能在一定条件下运作。看完了这一节,我们将学会判断是否可在项目中使用该工作流°。你会认识到使用该工作流需要怎样的前提条件,或者它不适用的原因。 -
工作流简述
这部分会用几句话对工作流做一个简要概述,以提出它的主要概念和思路。如果你把本教程当作参考书来用,这一页就是其提示部分。 -
执行过程及其实现
一个工作流通常可以由一个或多个过程组成。这里的过程所描述的是我们在完成一个任 务的过程中所需要执行的独立子任务。我们将在这一节详细介绍每一个过程,包括其中一定会用到哪些 Git 命令、这些命令要配合哪些参数、以及按什么顺序来运行。我们还会特意给出具体的实现。看完了这一节,我们就会知道如何用Git来实现相关功能了。 -
替代解决方案
Git是一个非常强大的工具箱,它的各种工具都可以用来解决多种任务。我们会在这一节讨论一下替代方案,解释为什么可以这样做。
当然,也有可能替代策略全都不符合我们的视角或本身就不可行,但只要它们在某一不同环境中能成为可行的解决方案也是可以的。在这部分,我们也会讨论这些只对任务中某种特殊情况有用的解决方案。
总之,这一节将会更彻底地为你解释我们挑选 Git 工具的缘由,同时也会展示问题的替代解决方案。
3️⃣ 概述
一旦我们决定了要在项目中使用 Git, 首先要做的第一步就是为其 Git 版本库分配相应 的文件与目录。这一步的重点是我们要决定被版本化的项目是要用单一版本库还是多个版本库。由于Git 版本库中只能创建分支和标签,这在很大程度上也等于是在决定该项目的发布单元。
待划分完项目后,我们还得要为每个模块建立相应的版本库并对其进行填充。当然,空目录与尚未被版本化的文件会被特殊对待。
在进行团队协作时,我们还必须要为各模块定义一个中央版本库。团队中的所有开发者都将优先从该中央版本库里获取当前的状态并记录下他们的修改。
另外,我们还必须要决定团队中所有开发者能以什么方式访问中央版本库。Git 支持各种访问方式,例如通过共享网络设备、Web 服务器,以及某种专有网络协议和安全壳架构协议 (即 SSH) 来访问等。
至于我们究竟要选择哪一种协议,这取决于我们自身现有的基础架构、本地布局情况、以及所拥有的管理员权限。
上述工作流应具体包含如下内容。
- 如何将项目目录转换成一个版本库。
- 如何版本化空目录。
- 如何处理行尾终止问题。
- 中央版本库有哪些服访问选项可用,它们应该如何设置。
- 团队成员应该如何访问中央版本库。
该工作流将由两个步骤组成。在第一步中,我们要为自己的项目目录创建相应的版本库。而在第二步中,我们要为所有的开发者提供一个可用的中央版本库。
在下图中,我们将会看到一个名为 projecta 的项目是如何被转换成版本库的。这其中, 特别要注意一个名为 EmptyDir
的空目录,因为 Git 通常是不会将空目录纳入版本控制的。你可以通过在该空目录中创建一个文件(例如.gitignore
文件)的方式强制 Git 将该目录纳入版本控制。
同样地,我们也应该要确保自己在第一次提交时没有将一些不该被版本化的文件放入版本库,例如一些构建结果和临时文件。在这个例子中,我们可以看到这些文件都被存放在 TempDir
目录中。另外,备份文件所在的存储目录也不应该被纳入版本控制。为了在未来的 提交中排除这个目录,我们应该在项目的根目录中创建一个.gitignore
文件,并在该文件中指定要忽略的目录。
下面进入第二步,我们现在要将新建的版本库提供给其他开发者使用。到目前为止, Git 支持了几种不同协议的变体。
file
: 经由共享网络设备访问的协议。git
: 专用服务器服务的网络通信协议。http
: 经由 Web 服务器访问的协议。ssh
: 经由安全壳架构访问的协议。
在 Git 中,对于同一版本库的多点访问是可以并行部署的。在实际例子中,我们常常会 为匿名读取访问配置 HTTP 协议,而为写访问配置 SSH 协议。
4️⃣ 使用要求
-
共享服务器:对于Git 的协同环境,我们可以选择某种共享网络设备,也可以选择一台启动相关服务的服务器,或者选择一台支持CGI 或 SSH 基架构的 Web 服务器,总之这三者必选其一。
-
分配项目级权限:Git 只能识别版本库整体的读写权限,也就是说,它不能将权限细化到单个目录中。
在该工作流中,我们会将项目目录导入到一个新的版本库中,并让该版本库来充当整个开发团队的中央版本库。
5️⃣ 执行过程及其实现
下面,我们就以第三节的图中这个简单的 projecta 项目为例来做一个过程演示。
5.1 基于项目目录创建一个新的版本库
在这一节中,我们将演示如何基于现有的项目来创建一个纯版本库。这个纯版本库是我们后期为团队创建共享版本库的一个先决条件。
整个过程的起点是文件系统中的某一个目录,现在我们要逐步将其转换成一个纯版本库。
第1步:准备空目录
Git 基本上是一个内容追踪器,它能对不同类型的文件进行有效的版本化管理。相比之下,目录只能被视为结构单元,并与文件一道被纳入版本控制。
但空目录在 Git 中是个无关对象,它不能通过 add 命令被填加到提交中。
通常情况下,只要我们的开发环境不依赖于这个空目录,你可以先忽略它们。但在某些情况下,由于我们的开发环境和相关工具会假定这些目录是存在的,因此我们不能删除这些空目录, 一旦缺失了这些目录,它们就会报错。
我们可以通过添加一个文件到该空目录的方式来强制 Git 对其进行版本化。从理论上来说,我们可以添加任何对开发环境没有影响的文件。所以添加.gitignore
或.gitkeep
文件应该都是合适的。
下面来实例演示一下,我们先应该像在图中那样创建一个 EmptyDir
目录。然后,在该目录下调用 UNIX 系统的 touch
命令,就在 EmptyDir
目录下创建了一个空文件。
> cd projecta/EmptyDir
> touch .gitignore
这种以点开头命名的文件在 Unix 中属于隐藏文件,多数开发环境会忽略掉它们。
而且,某些临时文件(如编译结果)也经常会被添加到空目录中。为了防止这些文件被错误地纳入提交,我们可以在该目录中创建一个.gitignore
文件,并在其中插入一个带有星号(“*
”) 的行,以便告诉Git该目录中所有文件都将被忽略,这样它们在 status
命令被调用时就不会显示在“未跟踪”列表中。
下面我们用Unix 系统的 echo
命令创建一个新的.gitignore
文件,并在其中插入“*
”。
> echo "*" > ·gitignore
第2步:忽略不需要的文件和目录
一些开发与构建工具也经常创建出一些临时文件(例如 Java 中的类文件),这些文件也 不应该被纳入版本控制。为了防止产生临时文件被版本化,我们也需要创建这个名为.gitignore
的文件,列出所有无需被版本话的文件和目录。
·gitignore
文件可以被创建在各个目录中,其中的条目通常会影响到当前这一级目录机器所有子目录。
下面,我们来看看在图这个例子中, .gitignore
文件的内容应该是怎样的。该文件的每一行都是一个文件名模式,用来指定应被忽略的文件。具体来说,就是 TempDir
目录下的以及所有扩展名为.bak
的文件都将会被忽略。
#Content of .gitignore
/TempDir
*.bak
为了便于跟踪这些被忽略的文件,我们应该只在项目的根目录上创建.gitignore
文件。然后用该文件去设定更深层的子目录。当然,只有在我们需要通过创建.gitignore
文件来强制 Git 将空目录纳入版本控制时才是个例外。
第3步:创建一个版本库
在之前的步骤中,我们导入了相关的项目文件,做好了准备工作。以下是创建版本库的步骤。
> cd projecta
> git init
第4步:定义行尾终止符
对于之前实际导入的文件,我们还需要对它们的行尾终止形式做一个统一的处理。
当我们同时在不同的操作系统上开发或处理文本文件时,行尾终止是一个经常会出现的问题。
正如我们所知, Windows 上所用的换行符是 CRLF (即回车符加换行符),而在 Unix 系统和 Mac 机上,我们则是用LF (换行符)来换行的。如果不同平台上的文本编辑器都能采用其他平台上的换行符来处理,其实这个问题很大程度上就可以得到解决。
但这样一来, 一个文本编辑器的用户有没有将其换行形式调整到相应的平台,就需要Git 在内容没有变化的情况下识别出相关行所发生的变化。我们可以想像这其中会产生多少合并冲突。
对此, Git 提供了一套用LF 来标准化换行符的问题解决方案。 一旦这套标准化方案被启 用 ,Git 就会在每次执行 commit
命令时将所有行尾结束符转换成LF, 并在必要时根据各相关平台的默认值进行来回切换。
Git对于行尾结束符主要有3种不同的处理方式。
core.autocrlf false
: 该方案会忽略行尾结束符。Git 在版本库中会原样存储这些文件的 行尾结束符。另外,当这文件被检索时其行尾结束符也将保持不变。core.autocrlf true
: 该方案会(用 LF) 执行标准化,但也会根据相应的平台来回切换。core.autocrl input
: 该方案在引入标准化 (LF) 时不会调整行尾结束符,但会将其来回切换。
由于在通常情况下, 一个版本库不可避免地在将来会被其他平台所使用,所以我们也理应要对行尾结束符做些适当的标准化处理。
综合上述理由,我们可以知道在Windows 系统上, core.autocrlf
应被设置为 true, 而在 Unix 系统上,则应该在首次导入之前将其设置为input。请注意,core.autocrlf
是被设置为 true 还是input 这件事,会直接影响到Git 是将一个文件识别成文本文件还是二进制文件。当然,我们可以用 .gitattribute
文件覆盖掉这种自动检测。
下面我们来将 core-autocrlf 设置成 input。
> git config --global core.autocrlf input
第5步:导入文件
接下来,我们要用 add
命令将所有文件纳入到我们的首次提交中。所有的现有文件,包括已加入的·gitignore
文件,然后完成提交并忽略不需要的文件。
在执行 add
命令之前,我们会建议重新调用一下status
命令,检查被列为“未跟踪”的究竟是哪些文件。因为有时候,我们会无意中将某个临时文件或目录加入到版本库中。
> git status
> git add .
然后用commit
命令来完成一次提交。
> git commit -m "init"
第6步:创建一个裸版本库
到目前为止,我们已经为新项目创建好了一个带工作区的普通版本库。为了让团队中能 有一个用于执行 pull
和 push
命令的中央版本库,还需要将该版本库转换成一个不带工作区的 裸版本库。裸版本库中将只包含.git
目录中的内容。
该转换动作可以通过clone
命令加-bare
参数来完成。裸版本库的名称通常会以.git
结尾,用于区分普通版本库。
> git clone --bare projecta projecta.git
在这里, --bare
选项主要用于确保整个克隆过程不包括工作区,它只涉及版本库中的那些对象。projecta 参数则是我们之前所准备的版本库名称。最后的 projecta.git 参数是我们所要创建的裸版本库的名称。
5.2 以文件访问的方式共享版本库
在这一节中,我们就来看看如何通过网络共享设备来共享一个裸版本库。
第1步:复制裸版本库
在基于相关项目文件创建裸版本库之后,它们就非常容易存储在网络设备中,所有人都可以对其进行访问。
> cp -R projecta.git /shared/gitrepos/.
在上述例子中,我们假设将其存放在了某一网络设备的/shared/gitrepos 目录中。
第2步:克隆中央版本库
如果我们要克隆一个位于网络设备上的版本库,只需要该中央裸版本库的路径即可。
> git clone /shared/gitrepos/projecta.git
当然,这里的路径还可以加一个 file://前缀。
> git clone file:///shared/gitrepos/projecta.git
第3步:管理读写权限
在这里,对版本库的读写访问是由文件系统来管理的。我们团队中的每个成员都将拥有版本库读权限,因为我们需要该版本库目录的读权限。当然,对于其写权限也同样如此。
优缺点分析:
这种共享方式的优点在于如今许多企业环境都已经部署有现成的网络共享设备,以作为其文件共享系统的一部分,这对于部署一个中央版本库来说,无疑是最方便的选项了。
而缺点则在于当我们的工作地点与中央版本库不在同一位置上时,这个选项就很难设置 了。而且在Git 中,数据访问并不是最有效的一种方式,因为这类远程 Git 命令(即 push
、fetch
与 pull
) 操作的始终都是远程版本库上的数据。但在接下来的3个服务器版的共享方式中,Git 可以在服务器上运行这些远程命令,它只需要将其结果发送到本地计算机上即可。
5.3 用 Git daemon 来共享版本库
标准的 Git 安装包中都会内置有一个服务器程序,该程序可以让我们用一种简单的网络协议来访问版本库。
请注意,在 Windows 上, Git daemon 只有在Git1.7.4 版本(以上)中才被支持。
第1步:为 git daemon 准备好相应的裸版本库
当 git daemon
导出某个版本库时,就会在该裸版本库的根目录中创建一个 gitdaemon-export-ok
文件。后者可以是一个空文件,它只需告诉 Git, 这是一个无需验证的合格服务项目即可。
> cd projecta.git
> touch git-daemon-export-ok
第2步:启动 git daemon
我们可以用 daemon 命令来启动 git daemon。
> git daemon
启动完成之后,我们就可以访问到当前计算机上所有被允许导出的版本库了。当然,为了真正实现访问,我们还需要在 Git 的 URL 中指定版本库的完整路径。
下面来看一个具体的 URL:
git://server-42/shared/gitrepos/projecta.git
其中, git: 前缀表示的是使用 Git daemon 所必须的协议。紧随其后的就是计算机名称 (server-42) 和版本库所在目录的路径(/shared/gitrepos/projecta.git) 。
为了使该URL 不那么依赖于特定的目录,我们通常也会设置一个基本路径。这需要通过 -base-path
参数来完成。
> git daemon --base-path=/shared/gitrepos
这样一来,我们就可以用 git://server42/projecta.git
来访问该版本库了。
默认情况下,daemon 命令所导出的版本库往往只有读取权限。如果你想打开版本库的写权限,就要用到 -enable=receive-pack
参数。
> git daemon --base-path=/shared/gitrepos --enable=receive-pack
git daemon
也可以被配置成操作系统的服务。关于这部分的细节,读者可以自行参考 daemon 命令的文档。
第3步:克隆中央版本库
当我们要从 daemon 服务中克隆某个已经发布的版本库时,只需要输入该中央裸版本库所在的URL 即可。
> git clone git://server-42/projecta.git
第4步:管理读写访问权限
版本库的读写访问权限不能由开发者各自来单独定义。也就是说,每个已发布的版本库应该可以被所有访问这台计算机的人所读取。而如果 git daemon要开启写访问权限的话,也应该是所有人都可以修改被由其导出的所有版本库。
优缺点分析:
这种共享方式的优点在于,git daemon 提供了一种最有效、最快速的从中央版本库中传输数据的方式。
而其缺点则是,缺少用户验证的功能。这意味着在这种环境下,我们必须要要读写访问权限在版本库中,git daemon 对此无能为力。
除此之外,这种共享方式还有一个缺点:即在分布式团队中,由于 Git 需要设置一个共享端口,所以防火墙仍然会是一个问题。
5.4 用 HTTP 协议来共享版本库
标准的 Git 安装包中都会自带一个 CGI 脚本,该脚本可以让我们经由 Web 服务器来访问版本库。当然,只有版本在1.6.6以上的 Git 才支持该CGI 脚本。在此之前,虽然我们也能用 HTTP 协议来访问版本库,但“老版”协议效率很低,速度也很慢。
下面我们通过一个例子来说明一下该CGI 脚本是如何被集成到 Apache2 架构中去的。
通常情况下, Apache2 是通过一个名为 httpd.conf
的文件来配置的。下面我们就来介绍一下如何通过修改 Apache2 的配置文件来完成我们的事情。至于关于该配置文件的其他详细信息和背景信息,请读者自行参阅 Apache2 文档。
第1步:启用 Apache2 中的相关模块
我们的CGI 脚本只能在 mod_cgi 模块被启用的情况下被集成到 Apache2 中去。而且要在其中集成 Git, 我们还得需要 mod_alias
和 mod_env
这两个模块的支持。如果这两个模块还没有被启用,我们就必须要先启用它们。
请注意,下面例子中的确切路径要取决于Apache2 的具体安装与其所在的操作系统。
LoadModule cgi_module libexec/apache2/mod_cgi.so
LoadModule alias_module libexec/apache2/mod_alias.so
LoadModule env module libexec/apache2/mod env.so
第2步:允许对 CGl 脚本进行访问
典型的 Apache2 安装设定通常会限制访问 Web 服务器上文件系统中的某些目录。因此, 如果我们想直接使用Git 安装目录下的CGI 脚本,就得要先开启这些目录的访问权。
在这个例子中,我们的CGI 脚本位于/usr/local/git/libexec/git-core
目录中。我们可以用下面这段代码来赋予 Apache2 调用该CGI脚本的权限:
<Directory "/usr/local/git/libexec/git-core">
AllowOverride None
options None
Order allow,deny
Allow from all
</Directory>
请注意! 这样做有一个重要前提,就是我们要确保用户是在运行了Apache2的服务器上, 并且拥有读取和执行该CGI 脚本的权限。
第3步:赋予 HTTP 协议访问版本库的权限
为了让CGI 脚本能导出相关的版本库,我们必须要在相关裸版本库的根目录中创建一个 名为 git-daemon-export-ok
的文件。它可以是一个空文件,只需要能告知Git, 这是一个无需验证的合格服务项目即可。
> cd /shared/gitrepos/projecta.git
> touch git-daemon-export-ok
请注意! 这样做有个重要前提,就是要确保 Apache2 服务器对版本库目录及其所有的文件与子目录都有读写访问权限。
现在,我们需要在 httpd.conf
文件中指定带导出版本库所在的根目录了,具体到这个例子,就是/shared/gitrepos/
目录。
SetEnv GIT_PROJECT_R0OT /shared/gitrepos
最后,我们还必须为该CGI 脚本设置一个别名。在这里,我们将其设置成了/git。
ScriptAlias /git/ /usr/local/git/libexec/git-core/git-http-backend/
待重启 Apache2 之后,我们就可以访问/shared/gitrepos/
目录下所有的版本库了。
第4步:克隆中央版本库
如果我们要克隆某个版本库,只需要指定该中央版本库的 URL 即可。具体来说,即该 URL 应该包括计算机名、之前所设置的 CGI 脚本的别名以及版本库的目录名。
> git clone http://server-42/git/projecta.git
具体到这个例子,即版本库 projecta.git
位于服务器server-42 中,其脚本别名是 git。
第5步:管理读写访问权限
在这种共享方式中,读写权限可以用一般 Web 服务器的访问权限来定义。
例如,如果我们想对版本库的写操作(即 push
命令)设置一个密码,就只需在 Apache2 配置文件中添加下面以下配置项即可:
<LocationMatch "^/git/.*/git-receive-pack$">
AuthType Basic
AuthName "Git Access"
AuthUserFile /shared/gitrepos/git-auth-file
Require valid-user
</LocationMatch>
有了这个配置项,每次 push
命令所发出的请求全都会交由 git-receive-pack
来处理,并且只有在用户通过验证的情况下才会响应。另一方面,读取访问则仍然无需输入密码。
如果我们想将某个版本库的读写访问都纳入密码保护,就得在 Apache2 配置文件中设置 以下配置项。
<LocationMatch /git/projecta.git>
AuthType Basic
AuthName "Git Access"
AuthUserFile /shared/gitrepos/git-auth-file
Require valid-user
</LocationMatch>
关于更多 Web 服务器的配置实例,读者可以参考 http-backend
命令所返回的文档。
优缺点分析
这种共享方式的优点在于,HTTP 这种访问形式在 Web 环境中访问版本库是非常方便的。 防火墙对于 HTTP 协议来说不再是一个典型问题了。另外,用户认证可通过Web 服务器来完成。如果你还想让它更安全一点,还可以使用 HTTPS 协议。
而其缺点在于,我们需要一个 Web 服务器,并且要经由它来进行操作和管理。
5.5 用 SSH 协议来共享版本库
为了能使用安全壳协议 (SSH) 来共享版本库,我们必须要设置相应的基础架构。也就 是说,我们至少必须要有一台安装了SSH 服务的计算机。并且所有项目参与者必须要有这台服务器上的 SSH 帐户。
第1步:复制裸版本库
我们只需要将包含项目文件的裸版本复制到某台 SSH 主机上,所有的相关开发者就都可以对其进行访问了。在 SSH 协议下,我们可以用scp
命令来将复制一个或多个文件。
> scp -r projecta.git server-42:/shared/gitrepos/projecta.git
在这个例子中,我们会假设自己的计算机 (server-42 ) 允许进行 SSH 访问,并且其 /shared/gitrepos
目录也被分配用于该版本库的存储。
第2步:克隆中央版本库
如果我们要经由SSH 共享协议来克隆某个版本库,就只需要给出一个普通的 SSH 路径,以指出该中央版本库的位置即可。
> git clone ssh://server-42:/shared/gitrepos/projecta.git
当然,我们也可以省略这里的 ssh:// 前缀。
> git clone server-42:/shared/gitrepos/projecta.git
第3步:管理读写访问权限
在这种共享方式中,版本库的读写访问将由掌管SSH 服务和文件系统权限的管理员来管理的。也就是说,团队中的每个成员都得要有版本库的读权限,以及该版本库目录的 SSH 访 问权限和读权限才行。当然,对于写权限也同样如此。
优缺点分析:
这种共享方式的优点在于,经由一个现成的SSH 协议架构来对版本库进行访问,设置起来非常方便。而且由于大多数 Git 命令都是在 SSH 服务器上执行的,只是通过网络向终端返回结果而已,因此其网络访问也非常高效。此外,该访问还是经过加密的。
而其缺点在于,如果我们没有现成的 SSH 协议架构,那要从头构建其这样一个协议架构, 代价是比较昂贵的。而且,即使你已经有了现成的协议架构,其用户帐户的管理也相当复杂,因为每个用户都需要设置一个单独的帐户,即使对只要读权限的用户也是如此。请注意,我们也可以用Gitolite(https://github.com/sitaramc/gitolite)
和 Gitosis(https://github.com/tv42/gitosis)
这样的软件,以简化 Git条件下的 SSH 协议架构管理。其中, Gitolite 甚至 还可以管理分支这一级的读写权限。另外,我们还有Gerrit(http://code.google.com/p/gerrit/)
, 该软件也可以用来充当 SSH 服务器,并且它还额外提供了审查功能。
6️⃣ 替代解决方案
本章所介绍的工作流都会假定每一个开发者都拥有中央版本库的写权限,因此一定可以用push
命令来发布自己的提交。
而通常在一个开源项目中,我们往往使用的是一个纯拉取的动作序列。在这种情况下,所有的开发者只在自己的版本库中完成相关的工作,并且只有负责整合的人员(集成负责人,integrator
)才有更新中央软件版本的权限。下面,我们通过图来看一下这个纯拉取操作的工作流。
如你所见,开发者们会将中央版本库克隆到本地,并生成新的提交。接下来,他们会向 负责集成的人发送一个拉取请求,以要求将自己的某个分支或某一提交导入并合并到中央版本库的集成分支上去。
这时候,集成负责人则需要及时负责通过pull 命令将所有开发者所做的修改合并到中央版本库中。当然,该集成负责人也必须要保证这些修改的质量。而一旦集成负责人完成中央版本库中的所有修改,开发者们就可以再次通过pull 命令将官方版本从中央版本库中导入到自己的版本库中。
在正常项目工作和产品开发中,这个过程可以在无需任何制约的情况下快速完成。 一个 团队中总是存在着几位高频率的更新者,他们需要快速看到来自其他各方的修改,例如在重构过程中很多文件都会发生变化。另外,敏捷项目的发布周期往往较短。在这种情况下,集成负责人可能会成为中央版本库更新不够快的一个瓶颈。
在大多数项目中,我们在修改控制上做得更多所得到的好处并不会掩盖我们的高成本。
当然,相关修改的备份则是另一个问题。只有在我们的拉取请求被处理之后,其相关数据才会被存储到中央版本库中。通常情况下, 一个企业中大概只有中央版本库才会有备份系统。开发者计算机中的数据在其被拉取之前遭到了破坏,该开发者所做的工作就会被丢失。
请注意:我们当然可以将这些修改备份到开发者版本库中。GitHub(https://github.com/)
这个开源环境就是为此而已存在的。而且,这样做也确保了集成负责人随时能访问到开发者版本库。