您的当前位置:首页正文

【Git教程】(十二)工作流之项目设置 — 何时使用工作流,工作流的结构,项目设置概述、执行过程及其实现 ~

2024-12-02 来源:个人技术集锦

在前面的章节中,我们学习了 Git 的基本概念。其中只涉及到了一些最重要的命令及其最重要的一些参数。
使我们选择了这样的话题,你或许还是会问:那么,我们究竟应该多久进行一次重新合并或变基呢?
而且,你也肯定已经通过互联网搜索以及亲身遇到的各种应用接触到了更多的命令及其参数。这种灵活性既是Git 强项也是它的缺点。

我们接下来要谈的工作流是 Git 在项目开发过程中的一个典型用途。在这里,我们所要介绍的重点是如何完成任务,而不是介绍更多参数。每个工作流的都只有一个解决方案来说明。这说明在细节上要做到尽可能地详尽,这样才能让工作流满足我们的工作,并且不用频繁地查看帮助文档。

即使你有多年的 Git 使用经验,也会遇到一些平常不太需要做的任务,例如分割某个版本库。在这种情况下,工作流可为相关的命令做一个简要说明。甚至在任务需要的情况下,我们还会在工作流中还会用到一些更鲜为人知的命令和参数。因此,你也确实需要学习更多用Git 做事的方式。


1️⃣ 何时使用工作流

谈到工作流选择这个话题,我就得来观察一个典型的项目开发过程。

  • 项目开始阶段
    如果你是从头开始了一个新项目,并已决定了用 Git 来进行版本控制。那么, 你的第一个任务就是为版本化的工作选择并设置一个基础架构。而如果我们面对的是一个需要被迁移到现有Git项目中的项目,那需要用到第13章()中所介绍的工作流了。

  • 项目开发阶段
    一旦我们定义好了基础架构,对于分支的处理就应当有明确的团队分工了。我们要么就让所有的开发者都在同一分支上工作,要么我们就得为每个任务创建一个独立的特性分支,我们将会在第14章中()介绍该工作流。

    如果在开发过程中突然出现了一些之前版本中不存在的错误,第15章中()所介绍的工作流可能会有助于我们找到问题的根源。

    为了确保修改的质量,我们还应该设置一些自动化构建和测试。而如何让 Git 在一台构建服务器上工作,就请参考第16章中()所介绍的工作流,如果你目前还没有使用Git 来对项目进行版本控制,但仍希望在工作中使用Git, 那么你可能会对第22章中()所介绍的工作流感兴趣。

  • 项目交付阶段
    在当前版本开发完成之后,我们应该要确保交付给客户是一个经过良好测试的产品。与 此同时,要为下一次的发布以及解决最新发行版中尚未解决的问题做好准备。我们将会在第17章中()讨论该工作流。

  • 项目重构阶段
    项目的需求会随着时间和新的理解而产生变化。因此,我们必须要重新考虑到自己会将 一个项目分割到多个 Git 版本库中的可能性。

    铁板一块的项目即使最初规模很小,也是会增长的,它必须被模块化。对此,你可以参考第19章中()的工作流,我们将会为你介绍如何将一个大型版本库分割成几个较小的版本库。

    当然,也有可能出现相反的情况。处理一个被分割到多个版本库中的项目可能过于复杂了。对此,你可以参考第20章中()的工作流,看看如何将这些版本库重新合并起来。

    当某个项目已经存在了很长一段时间,经历过多次修改,相关版本库会增加到非常大的规模。其所有文件的历史记录中包括了每个开发者的本地工作区中的文件。特别地,当早期版本中有大型二进制文件被版本化的话,就很可能会带来不必要的资源消耗。对此,我们将 会在第21章中()介绍如何将一个项目的历史记录分割,并且让每个开发者只在本地存储较新的版本。


2️⃣ 工作流的结构

下面来看一下上述这些章节在介绍工作流时的结构安排,这里对每一节都做一个简短的介绍。

  • 条目
    每个工作流开头都会有一个简短的动机说明,用来解释我们会基于什么原因以及在什么情况下使用这个工作流,描述的是其中心任务中可能的决定和特定的功能。通常在结尾处还会有一段关于该工作流的要点总结。看完了这一节,你就能判断自己的项目是否与该工作流相关了。

  • 概述
    在这一节中,我们将会以具体实例的方式来为你描述该工作流的基本过程,并介绍完成该工作流所需要的基本术语、概念和Git 命令。看完了这一节,我们将会了解该工作流的工作原则以及其中会用到什么Git 资源。

  • 使用要求
    每个工作流都只能在一定条件下运作。看完了这一节,我们将学会判断是否可在项目中使用该工作流°。你会认识到使用该工作流需要怎样的前提条件,或者它不适用的原因。

  • 工作流简述
    这部分会用几句话对工作流做一个简要概述,以提出它的主要概念和思路。如果你把本教程当作参考书来用,这一页就是其提示部分。

  • 执行过程及其实现
    一个工作流通常可以由一个或多个过程组成。这里的过程所描述的是我们在完成一个任 务的过程中所需要执行的独立子任务。我们将在这一节详细介绍每一个过程,包括其中一定会用到哪些 Git 命令、这些命令要配合哪些参数、以及按什么顺序来运行。我们还会特意给出具体的实现。看完了这一节,我们就会知道如何用Git来实现相关功能了。

  • 替代解决方案
    Git是一个非常强大的工具箱,它的各种工具都可以用来解决多种任务。我们会在这一节讨论一下替代方案,解释为什么可以这样做。
    当然,也有可能替代策略全都不符合我们的视角或本身就不可行,但只要它们在某一不同环境中能成为可行的解决方案也是可以的。在这部分,我们也会讨论这些只对任务中某种特殊情况有用的解决方案。
    总之,这一节将会更彻底地为你解释我们挑选 Git 工具的缘由,同时也会展示问题的替代解决方案。


3️⃣ 概述

另外,我们还必须要决定团队中所有开发者能以什么方式访问中央版本库。Git 支持各种访问方式,例如通过共享网络设备、Web 服务器,以及某种专有网络协议和安全壳架构协议 (即 SSH) 来访问等。
至于我们究竟要选择哪一种协议,这取决于我们自身现有的基础架构、本地布局情况、以及所拥有的管理员权限。

上述工作流应具体包含如下内容。

  • 如何将项目目录转换成一个版本库。
  • 如何版本化空目录。
  • 如何处理行尾终止问题。
  • 中央版本库有哪些服访问选项可用,它们应该如何设置。
  • 团队成员应该如何访问中央版本库。

下面进入第二步,我们现在要将新建的版本库提供给其他开发者使用。到目前为止, Git 支持了几种不同协议的变体。

  • file: 经由共享网络设备访问的协议。
  • git: 专用服务器服务的网络通信协议。
  • http: 经由 Web 服务器访问的协议。
  • ssh: 经由安全壳架构访问的协议。

在 Git 中,对于同一版本库的多点访问是可以并行部署的。在实际例子中,我们常常会 为匿名读取访问配置 HTTP 协议,而为写访问配置 SSH 协议。


4️⃣ 使用要求

  • 共享服务器:对于Git 的协同环境,我们可以选择某种共享网络设备,也可以选择一台启动相关服务的服务器,或者选择一台支持CGI 或 SSH 基架构的 Web 服务器,总之这三者必选其一。


5️⃣ 执行过程及其实现

下面,我们就以第三节的图中这个简单的 projecta 项目为例来做一个过程演示。

5.1 基于项目目录创建一个新的版本库

> cd projecta/EmptyDir
> touch .gitignore
 > echo "*"  > ·gitignore

#Content of .gitignore
/TempDir
*.bak

第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

> git status
> git add .

然后用commit 命令来完成一次提交。

> git commit -m "init"

> git clone  --bare projecta projecta.git

在这里, --bare 选项主要用于确保整个克隆过程不包括工作区,它只涉及版本库中的那些对象。projecta 参数则是我们之前所准备的版本库名称。最后的 projecta.git 参数是我们所要创建的裸版本库的名称。


5.2 以文件访问的方式共享版本库

在这一节中,我们就来看看如何通过网络共享设备来共享一个裸版本库。

第1步:复制裸版本库
在基于相关项目文件创建裸版本库之后,它们就非常容易存储在网络设备中,所有人都可以对其进行访问。

> cp -R projecta.git /shared/gitrepos/.

第2步:克隆中央版本库
如果我们要克隆一个位于网络设备上的版本库,只需要该中央裸版本库的路径即可。

> git clone /shared/gitrepos/projecta.git

当然,这里的路径还可以加一个 file://前缀。

> git clone file:///shared/gitrepos/projecta.git

优缺点分析:
这种共享方式的优点在于如今许多企业环境都已经部署有现成的网络共享设备,以作为其文件共享系统的一部分,这对于部署一个中央版本库来说,无疑是最方便的选项了。
而缺点则在于当我们的工作地点与中央版本库不在同一位置上时,这个选项就很难设置 了。而且在Git 中,数据访问并不是最有效的一种方式,因为这类远程 Git 命令(即 pushfetchpull) 操作的始终都是远程版本库上的数据。但在接下来的3个服务器版的共享方式中,Git 可以在服务器上运行这些远程命令,它只需要将其结果发送到本地计算机上即可。

5.3 用 Git daemon 来共享版本库

标准的 Git 安装包中都会内置有一个服务器程序,该程序可以让我们用一种简单的网络协议来访问版本库。
请注意,在 Windows 上, Git daemon 只有在Git1.7.4 版本(以上)中才被支持。

> 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 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_aliasmod_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

<Directory "/usr/local/git/libexec/git-core">
	AllowOverride  None
	options  None
	Order  allow,deny
	Allow  from  all
</Directory>

请注意! 这样做有一个重要前提,就是我们要确保用户是在运行了Apache2的服务器上, 并且拥有读取和执行该CGI 脚本的权限。

> cd /shared/gitrepos/projecta.git
> touch git-daemon-export-ok
SetEnv GIT_PROJECT_R0OT /shared/gitrepos

最后,我们还必须为该CGI 脚本设置一个别名。在这里,我们将其设置成了/git。

ScriptAlias /git/ /usr/local/git/libexec/git-core/git-http-backend/
> 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

第2步:克隆中央版本库
如果我们要经由SSH 共享协议来克隆某个版本库,就只需要给出一个普通的 SSH 路径,以指出该中央版本库的位置即可。

> git clone ssh://server-42:/shared/gitrepos/projecta.git

当然,我们也可以省略这里的 ssh:// 前缀。

> git clone server-42:/shared/gitrepos/projecta.git

优缺点分析:
这种共享方式的优点在于,经由一个现成的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/) 这个开源环境就是为此而已存在的。而且,这样做也确保了集成负责人随时能访问到开发者版本库。


显示全文