Git——协作开发

介绍多种协作工作流,以及它们各自的优缺点;同时还会了解信任链的概念,签名标签、签名合并、签名提交的使用方法。

主要内容包括以下几个部分:

  • 中心式和分布式工作流,裸版本库。
  • 远程版本库和一次性单点协作管理。
  • 推送、拉取请求以及交换补丁。
  • 版本的编址——信任链。
  • 使用bundle进行离线传输(sneakernet)。
  • 标签化,轻量级标签和签名标签。
  • 签名标签、签名合并和签名提交。

1、协作工作流

版本控制系统的主要用途之一就是帮助项目团队成员进行协作开发。版本控制机制是高效同步开发某个软件的解决方案,它使得团队成员能够有效避免软件变更冲突,并帮助人们高效合并程序变更。

在进行软件项目开发时,项目或多或少都存在其他开发人员。例如其他开发成员或者维护项目人员;又或者项目非常庞大,需要子项目维护人员。其中的成员有可能和软件开发团队关系密切,也有可能只是外部辅助人员,希望能够方便地提交合适的变更(例如bug修复,或者帮助修正文档中的排版错误等)。现在有多种工作流可以应对上述情况:

  • 中心式工作流。
  • 对等网络工作流。
  • 维护者工作流。
  • 分层式工作流。

1.1、空版本库

有两种版本库:一种是普通的带工作目录和暂存区的非裸库,另外一种是没有工作目录的裸版本库。前一种适合个人独立开发创建修订记录,后一种适合团队协作和同步开发。

一般来说,裸版本库使用.git扩展名,例如project.git;而非裸库则没有使用上述扩展名,例如project(管理区和本地版本库中的project/.git文件),用户在远程版本库上执行克隆、推送和拉取操作时可以忽略该扩展名。使用http://git.example.com/project.git或者http://git.example.com/project等版本库,URL地址的效果是一样的。

为了创建一个裸版本库,用户在执行初始化或者克隆操作时需要添加–bare选项:

$ git init --bare project.git
Initialized empty Git repository in /home/user/project.git/

1.2、和其他版本库交互

在创建了一组修订和项目的历史记录后,用户通常需要把它们和团队其他成员共享。用户需要和其他开发人员的版本库实例同步,发布自己的变更,并且从其他人员那里获取变更。从本地版本库实例的角度来说,即用户自己的远程版本库克隆,用户需要向其他版本库推送自己的变更(其中包括远程版本库或者用户自己的公共版本库),同时从其他版本库(通常是用户克隆的源版本库)中获取变更记录。在获得其他人的变更之后,用户有时需要将它们与自己的工作集成到一起,即将两个工作分支合并(或者变基),用户还可以在pull操作中一并完成上述工作。

通常用户并不希望公开自己的本地版本库,因为这类版本库中包含不少私人信息(其中还包括一些不成熟的想法或者工作成果)。这意味着用户完成工作后还必须添加一个额外的步骤才能使用git push命令发布自己的变更。下图演示了创建和发布提交的主要步骤。箭头显示了Git执行内容拷贝的过程,其中还包括远程版本库的操作。
Git——协作开发_第1张图片

1.3、中心式工作流

通过分布式版本控制系统,用户采用的协作模型或多或少地都带有部分分布式特性。如下图所示,在中心式工作流中有一个中心集线器,通常是裸版本库,方便团队成员同步他们的工作成果:

Git——协作开发_第2张图片
中心式工作流。共享版本库是裸版本库。其中连接的线代表版本库的数据流向,例如最左侧的线表示该命令是由左一版本库的所有者发送的。

每个开发人员都有其自己的中心版本库的非裸版本库克隆,它们主要用来为软件添加新的修订。当准备好变更之后,开发人员将它们推送到中心版本库上,同时从中心共享版本库上获取其他开发人员提交的变更,因此集成工作是分布式的。该工作流可以参见下图。中心式工作流的优点和缺点如下:

  • 优点是配置简单,对于熟悉分布式版本控制系统和集中管理的人来说上手容易,而且支持中心式访问控制和备份功能。其对于私人的小型项目不失为一种很好的配置方案。
  • 缺点是共享版本库存在单点故障问题(如果中心版本库出现问题,那么团队所有成员就无法同步变更),而且每个开发人员在发布自己的变更(将它共享给其他人)之前都必须先更新自己的版本库,并且需要合并其他人提交的变更。在这一配置中,用户还需要信任其他开发人员访问共享版本库。

1.4、对等网络或者分支工作流

和中心式工作流相反的是对等网络或者分支工作流。它没有使用单一的共享版本库,每个开发人员在私人的工作版本库(带工作目录)之外都拥有一个公共版本库,如下图所示:
Git——协作开发_第3张图片
对等网络(分支)工作流,每个开发人员都有其自己的非裸版本库和公共裸版本库。连接的线代表发送请求的主体(发送指令的用户),向上的箭头代表推送,向下的箭头代表拉取。

当准备好变更记录后,开发者将它们推送到自己的公共版本库上。为了从其他开发人员那里集成变更,用户需要从其他开发者的公共版本库上拉取变更记录。对等网络(分支)工作流的优缺点如下:

  • 对等网络工作流的优点之一是集成变更记录时不需要依赖中心版本库,它是一种非常纯粹的分布式工作流。另外一个优点是,如果用户希望发布自己的变更,不必强制集成他人的变更记录,可以稍后根据自己的时间安排进行集成。这对于不需要太多配置的团队来说是一种良好的解决方案。
  • 它的缺点是缺乏一个规范的版本,没有集中管理机制,而且事实上,基于这种形式的工作流,用户需要和多个版本库打交道(当然这里使用git remote update命令就可以搞定)。环境配置需要开发者的公共版本库可以被团队其他成员的工作站访问,这可能并不会像某人使用自己的机器作为他自己的公共版本库那样简单,如下图所示,协作随着团队成员数量的增加会变得愈发复杂。

1.5、维护者和集成管理工作流

对等网络工作流的缺点之一就是项目中没有规范的版本可供非开发人员使用,此外每个开发人员都必须亲自处理集成变更的工作。如果我们将上图中的某个公共版本库指定为规范的(官方的)版本库,让某个开发人员负责对它进行集成,那么我们就实现了集成管理工作流了(维护者工作流)。下图演示了这种工作流模型,其中裸版本库在上面,非裸版本库在下面。
Git——协作开发_第4张图片
集成管理(维护者)工作流。其中的某个开发者扮演了集成管理的角色,其公共版本库被“推举”为项目的官方版本。指向该版本库相同颜色的箭头代表拉取变更,从它指向外部的箭头代表推送。虚线代表从一个非官方的版本库中拉取变更的操作(例如一个小型的开发团队)。

在这种工作流中,当准备好变更记录后,开发者将它们推送到其自己的公共版本库上,然后告知负责维护集成的人(例如一个pull请求)。维护集成管理者从其他开发人员的公共版本库中拉取变更,然后将它们集成到自己的版本库中。集成管理者将上述变更合并之后,将它们推送到大家“推举”的公共版本库中。该工作流的优缺点如下:

  • 优点是项目有了一个官方版本,开发人员可以不必为集成变更等待,能够安心工作,因为维护者能够随时将他们提交的变更集成。对于大型的开源项目团队来说,这是一种非常好的工作流。实际上被推荐的版本库都是通过社交互动确定的,因此可以很方便地指定其他的维护人员,这些人可以是临时性(例如时间过期)的,也可以是永久性的(例如项目分支)。
  • 缺点是对于大型项目和大型团队来说,维护者集成变更的效率是个短板。为此,对于大型团队,例如Linux内核开发团队,最好使用层级式工作流。

1.6、层级式(主从式)工作流

层级式工作流是对等网络工作流的变体,通常被包含几百人的共同协作的大型项目采用。在这种工作流中,项目维护者(有时也叫总集成师)通常会领导若干集成经理,集成经理会管理版本库的某个特定的部分,有时也称为集成助理。总集成师的公共版本库可以通过所有集成助理的推送作为包含引用的“推举”版本库。集成助理从开发者那里获取变更,总集成师从集成助理那里获取变更,如下图所示:
Git——协作开发_第5张图片
层级式工作流。这种项目中有一名总集成师(他的公共版本库是大家“推举”的项目官方版本),项目子系统集成管理人员叫集成助理。虚线连接的版本库实际上是开发人员和集成助手组成的配对。它们之间发起会话的人是由连接的线表示的。

在层级式工作流中还存在着层级式的版本库网络。在开始工作之前,无论是开发还是合并变更,用户首先需要从项目的官方版本库拉取更新。开发者在他们的私人版本库中准备好变更记录后,会将这些变更发送给相关的子系统变更集成助手。变更记录既可以是通过电子邮件发送的补丁,也可以将它们推送到开发者的公共版本库上,然后集成助手发送一个拉取请求。

变更集成助手会将上述变更合并到其负责的区域。总集成师从变更集成助手那里拉取上述变更(有时也直接从开发人员那里获取变更),同时也会兼顾软件预览发布的职责(例如为预览版软件添加标签)。该工作流的优缺点如下:

  • 该工作流的优点是允许项目负责人(总集成师)代理大量的变更集成工作。这对于超大型项目或者高度分层的环境来说是非常有用的(从开发者和变更数量方面来看)。这类工作流常用来开发Linux内核。
  • 该工作流的缺点是配置复杂。其复杂度往往超过一般的普通项目。

2、远程版本库管理

当使用Git进行项目协作管理时,用户经常需要访问若干其他版本库,例如在集成管理工作流中,用户至少需要访问项目成员一致“推举”的官方版本库。大多数情况下,用户需要访问多个远程版本库。

Git允许用户将远程版本库的信息存储在配置文件中,并且可以给它指定一个别名(简称)。这类信息可以通过git remote命令管理。

有两种规范的机制可以存储版本库的配置信息:

  • .git/remotes中的具名文件:该文件名应该是远程版本库的简称。这个文件中包含了URL地址,fetch和push命令refspecs规范等信息。
  • .git/branches中的具名文件:该文件应该是远程版本库的简称。这个文件中的内容只包含该版本库的URL地址,地址后面可以通过#附加分支名称。

不过时下流行的版本库中都很少采用上述两种机制。

2.1、原生的远程版本库

在克隆版本库时,Git将会为用户创建一个远程版本库,即原生的远程版本库,其中会存储用户克隆源版本库的信息,即拷贝版本库的元数据信息。用户可以使用该远程版本库获取更新信息。

这也是默认的远程版本库,例如在执行git fetch命令时,如果命令后面没有指定远程版本库的名称,那么上述命令会默认选取该原生版本库作为目标地址,除非系统对远程版本库做了特别设置,或者当前分支(分支.<分支名>.远程版本库)的配置另有规定的除外。

2.2、浏览远程版本库

为了获取目前配置的远程版本库的信息,用户可以执行git remote命令,它会列出用户拥有的所有远程版本库的别名。在一个克隆版本库中,用户至少拥有一个远程版本库——origin。

$ git remote
origin

为了查看和远程版本库相关的URL地址,可以使用-v / --verbose选项:

$ git remote --verbose
origin  https://gitee.com/lixingwu/learn-spring-cloud.git (fetch)
origin  https://gitee.com/lixingwu/learn-spring-cloud.git (push)

如果希望查看特定远程版本库的详细信息,可以使用git remote show 子命令:

$ git remote show origin
* remote origin
  Fetch URL: https://gitee.com/lixingwu/learn-spring-cloud.git
  Push  URL: https://gitee.com/lixingwu/learn-spring-cloud.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

Git将会查找远程版本库配置、分支配置和远程版本库(为了及时更新状态)。如果用户喜欢略去访问远程版本库的步骤使用本地的缓存信息,那么可以在使用git remote命令时添加-n选项。

因为和远程版本库有关的信息是存储在版本库配置文件中的,因此用户可以在.git/config中查看它们:
Git——协作开发_第6张图片

2.3、新建远程版本库

为了新建一个Git的远程版本库,并给它添加一个别名以便记忆,可以执行git remote add 命令:

$ git remote add alice https://git.company.com/alice/random.git

新建远程版本库的过程中,系统并不会自动拉取远程版本库的信息到本地,为了达到上述目的,用户需要在执行上述命令时添加-f选项(或者执行run git fetch<别名>命令)。

这个命令的部分参数选项会对Git新建远程版本库产生影响。用户可以使用-t 选项在远程版本库中指定特定分支,还可以使用-m 选项为远程版本库指定默认分支(以及相关的远程分支名称),也可以使用--mirror=push或者--mirror=fetch选项配置远程版本库镜像方便协作。

例如执行如下命令:

$ git remote add -t master -t next -t maint github \ https://github.com/
jnareb/git.git

将会产生下列远程版本库配置信息:

[remote "github"]
  url = https://github.com/jnareb/git.git
  fetch = +refs/heads/master:refs/remotes/github/master
  fetch = +refs/heads/next:refs/remotes/github/next
  fetch = +refs/heads/maint:refs/remotes/github/maint

2.4、远程版本库信息更新

远程版本库的信息分别存放在下面几个地方:远程版本库配置:remote.<远程版本库名称>,远程跟踪分支,远程首部(refs/remotes//HEAD是一个用来表示默认远程跟踪分支的符号引用(symref),即远程跟踪分支的<远程版本库名称>用来指代分支),此外还有可选的单个分支配置信息:branch.<分支名>。

用户可以通过编辑相应的配置文件或者使用类似git configgit symbolic-ref的命令操作这些配置信息,而且Git还提供了多个git remote的子命令来实现上述目的。

1、远程版本库重命名

远程版本库重命名,即修改它的别名,是一项非常复杂的操作。执行git remote rename 命令后,不仅会修改remote.中的名字,而且还会修改远程跟踪分支和相关的refspec,即引用日志以及它们对应的分支配置。

2、远程版本库URL修改

用户可以使用git remote set-url命令为远程版本库添加或者替换URL地址,不过也可以直接编辑相关的配置文件进行修改。

当然用户还可以使用insteadOf(还有pushInsteadOf)配置变量进行替换。当用户希望使用另外一个临时的服务端主机时,上述特性是非常有用的,例如用户希望从官方网站获取Git软件的更新,当https://www.kernel.org/这个地址暂时无法访问时,用户就可以访问GitHub上的备用服务器。用户可以在配置文件中添加如下配置:

[url "https://github.com/git/git.git"]
    insteadOf = git://git.kernel.org/pub/scm/git/git.git

该特性的另外一个用处就是处理版本库迁移,用户可以使用insteadOf变量重写单用户配置文件~/.gitconfig(或者~/.config/ git/config)中的相关配置,因而无须手动修改每个版本库.git/config文件中的URL。在出现多个匹配的情况下,会选用匹配数量最多的那个。

远程版本库的多个URL地址:用户可以为远程版本库设置多个URL地址。Git在拉取远程版本更新时会依次尝试上述地址,并使用第一个成功访问的URL作为服务源,同时在推送更新时,Git会同步推送更新到上述所有URL地址的服务主机上。

3、远程版本库跟踪分支列表编辑

修改URL常见的一种情况是修改远程版本库的跟踪分支列表(拉取内容的列表):用户可以使用git remote set- branches命令(通过一款当前最新的高效Git软件实现)或者直接手动编辑配置文件实现该目的。

需要注意的是,从远程版本库跟踪列表中移除一个分支并没有删除该跟踪分支,后者只是不再更新了。

4、远程版本库的默认分支设置

远程版本库上不一定必须有一个默认分支,不过它可以让用户通过远程版本库名称替代某些特定的远程跟踪分支(例如orgin/master)。这类信息一般存放在符号化引用<远程版本库名称>/HEAD中(例如origin/HEAD)。

用户可以使用git remote set-head命令设置它,–auto选项的作用是选择远程版本库中的当前分支:

$ git remote set-head origin master
$ git branch -r
  origin/HEAD -> origin/master
  origin/master

用户还可以使用–delete选项删除远程版本库上的默认分支。

5、远程版本库跟踪分支删除

当远程版本库上的一个公共分支被删除后,Git仍然会保留相关的远程跟踪分支。这样做的原因是用户可能已经基于上述分支添加了新的变更。当然,用户也可以使用git branch -r -d命令删除远程跟踪分支,或者可以通过git remote prune命令让Git系统对远程版本库中的远程跟踪分支进行精简,它的作用和git fetch命令搭配–prune选项是类似的,当然前提是用户必须设置好fetch.prune和remote.等精简配置变量。用户可以使用git remote prune命令搭配–dry-run选项或者使用git remote show命令检查远程跟踪分支中哪些是需要被清理的内容。

用户还可以使用git remote delete命令(或者git remote rm命令)将整个远程版本库删除。同时系统还会将与之相关的远程跟踪分支移除。

2.5、兼容不规则工作流

在很多协作工作流中,例如维护者(集成管理)工作流,用户需要从一个URL地址获取更新(从大家“推举”的公共版本库),但是还需要将自己的更新推送到另外一个URL(自己的公共版本库)。如下图所示,开发者需要和其他3个版本库交互——其需要从“推举”版本库(浅红色的)获取更新并集成到自己的私人版本库(深色的),同时还需要推送自己的工作成果到开发者公共版本库(浅色的)上。
Git——协作开发_第7张图片
在这种不规则工作流中(3个版本库),用户拉取或者推送的远程版本库通常是默认的origin远程版本库(或者remote.default)。一个备选的版本库配置方案是将用户推送的目标版本库当作一个独立的远程版本库,当然有可能用户还需要使用remote.push Default变量将其设置为默认主机。

[remote "origin"]
    url = https://git.company.com/project
    fetch = +refs/heads/*:refs/remotes/origin/*
[remote "myown"]
    url = git@work.company.com:user/project
    fetch = +refs/heads/*:refs/remotes/myown/*
[remote]
    pushdefault = myown

用户还可以在单个分支配置中将它设置为推送远程主机(pushremote):

[branch "master"]
    remote = origin
    pushremote = myown
    merge = refs/heads/master

另外一个备选方案是使用单一的远程版本库(甚至只有origin),但是分别设定独立的推送URL地址。当然,这个方案有一个小小的瑕疵,即用户不能为版本库设置独立的远程跟踪分支(因为@{upstream}之外不存在@{push}这样的修饰符来表示相应的特定远程跟踪分支的快捷方式,不过Git 2.5.0之后的软件版本已经支持上述语法了):

[remote "origin"]
    url = https://git.company.com/project
    pushurl = git@work.company.com:user/project
    fetch = +refs/heads/*:refs/remotes/origin/*

3、传输协议

一般来说,远程版本库配置文件中的URL都包含传输协议信息,即远程主机的地址和版本库的路径。有些时候,远程版本库服务端为用户提供了使用多种协议访问它的支持,用户可以根据喜好选择其中的一种。我们将会介绍可供用户选择的多种传输协议。

3.1、本地传输

如果其他版本库是放在本地的其他文件系统中的,那么用户可以使用下列语法格式指定访问该版本库的URL地址:

/path/to/repo.git/
file:///path/to/repo.git/

前一种实现了Git克隆版本库操作中–local选项的功能,它绕过了Git的智能感知机制,只是简单地执行了拷贝操作(或者是.git/对象下的所有文件链接,当然用户还可以使用–no-hardlinks选项禁用此功能),后者虽然效率比较低,但是可以获取一个干净的版本库拷贝。

这是一种非常好的办法,它可以快速地从某人正在工作的版本库中获得工作成果,也可以通过授予文件系统一定访问权限和他人共享自己的工作成果。

有一种特殊的情况,单个"." 符号可以代表当前版本库,下列命令的含义:

$ git pull . next

是和下文等效的:

$ git merge next

哑传输协议:某些传输过程不需要任何Git智能感知服务——它们不需要在服务端安装Git软件(对于智能传输过程至少需要用到git- upload-pack或者git-receive-pack组件),不过其中大部分需要通过git update-server- info根据版本库引用、对象和打包文件(以某种方式拷贝)生成的附加信息才能工作。

1、Rsync传输协议:不安全

作为Git最初支持的最古老传输协议之一,通过它用户可以从远程版本库上执行拉取、推送、读取和写入等操作,即rsync协议,它的URL类型如下所示:

rsync://host.example.com/path/to/repo.git/

rsync协议已经过时了,因为它无法保证在获取数据时得到合适的序列,如果用户从一个非静态版本库中拉取数据,那么可能会得到无效的数据。另外,它的传输速度很快并且支持断点续传。当然,如果用户通过一个不太稳定的网络环境执行初始化克隆操作时出现问题,那么最好采用bundle替代rsync协议。

2、FTP(S)和哑HTTP(S)传输协议:效率低

这类传输只需要提供相应的服务器即可(例如FTP服务器或者Web服务器),然后通过git update-server-info命令进行数据更新。当从这样一台服务器上拉取数据时,Git会通过名为commit walker的下载器下载相应的分支和标签数据,遍历整个提交链条,下载对象、包含新修订的压缩包以及其他数据。

这种传输是非常低效的(由于带宽的原因,但是主要还是由于网络延迟),但是另外它还支持断点续传。尽管如此,还有不少优于哑传输协议的解决方案,当网络环境不稳定以致于用户无法获取版本库克隆时,用户可以试试借助bundle软件。

向一个哑服务器推送内容只能通过HTTP和HTTPS协议,同时需要Web服务器支持WebDAV功能,而且采用的Git软件必须使用外部的链接库进行编译生成。FTP和FTPS协议是只读的(即只支持克隆、下载和拉取操作)。

3.2、智能传输

当用户希望获取的版本库在另外一台机器上时,就需要借助Git服务器才能访问了。当前常见的设备是Git智能感知服务器。智能下载协议可以自动选择必要的软件版本,然后创建一个自定义打包文件发送到客户端。同时,在推送变更过程中,Git服务端会和用户机器上的Git软件(客户端)交互,以便确定哪些修订版本需要上传。

Git智能感知服务器会使用git upload-pack命令下载更新,使用git receive-pack命令推送变更。如果无法在PATH环境变量(也可能在安装主目录下)中找到相应的地址,用户可以通过–upload-pack和–receive-pack选项告诉Git系统获取更新和推送变更的目标地址,当然也可以使用远程版本库配置变量uploadpack和receivepack进行设置。

极个别情况下(例如版本库使用的子模块是通过一个版本较低的Git软件进行访问的,以致于该软件无法有效识别其他模块),Git传输过程是向下兼容的,即客户端和服务端可以共同协商,采用二者都支持的协议进行数据传输。

1、Git原生通信协议

原生的传输协议采用的格式为git://URLs,它支持只读式的匿名访问(从理论上说,用户可以通过命令行或者布尔值配置变量daemon.receivePack让Git的receive-pack服务支持推送,不过并不建议用户使用这种机制,即使在一个完全封闭的本地网络中也是如此)。

Git原生通信协议是没有授权验证机制的,其中还包括服务端授权验证,因此在不安全的网络环境中要慎用。上述协议Git的TCP守护进程一般会监听9418端口,因此用户必须有权限能够访问该端口(穿透防火墙)才能使用该协议。

2、SSH协议

安全外壳传输协议(SSH)提供了支持安全验证的读写访问功能。Git在服务端执行git upload-pack或者git receive-pack命令时就是采用的SSH协议进行远程调用的。该协议不支持匿名和未授权访问,因此用户可以专门为它创建一个guest账户(使用简单密码或者空密码)。

采用公私钥授权之后,用户每次连接时无需输入密码,只需第一次连接时提供密码即可——解密受保护的私钥。

在使用SSH协议时,用户可以将ssh://作为URL语法的一部分:

ssh://[user@]host.example.com[:port]/path/to/repo.git/ 

当然也可以使用类scp的语法:

[user@]host.example.com:path/to/repo.git/

此外SSH协议还支持~username表达式,其在语法形式上和原生的Git传输协议类似(~代表当前登录用户的主目录,~user代表当前主目录的用户):

ssh://[user@]host.example.com[:port]/~[user]/path/to/repo.git/
[user@]host.example.com:~[user]/path/to/repo.git/

SSH在服务端采用首次用户验证机制:它会记住用户上一次登录时提供的密钥,并在此次用户登录时向用户发出警示信息,确认该信息是否有误(服务端的密钥有可能已经发生变更,例如重新安装SSH服务器软件)。用户可以在首次连接服务器时对密钥指纹进行校验。

3、智能HTTP(S)协议

Git也支持智能HTTP(S)协议,不过它需要借助Git感知CGI和服务模块,例如git-http-backend(它本身就是一个CGI模块)。由于设计特性,Git可以自动将简单传输URL转换成智能URL地址。

与此相反,Git感知HTTP服务可以向下兼容哑传输协议(至少支持拉取操作,不过它不支持基于WebDAV的哑HTTP推送)。这个特性允许用户使用相同的HTTP(S)的URL地址进行哑传输和智能传输访问:

http[s]://[user@]host.example.com[:port]/path/to/repo.git/

默认情况下,如果不做任何配置,Git还支持匿名下载(git fetch、git pull、git clone和git ls-remote),不过在上传信息时,需要客户端进行授权验证(git push)。

如果访问某个版本库时需要授权验证,那么一般会通过HTTP服务端软件进行标准的HTTP验证。如果需要发送密码,最好采用SSL/TLS协议进行HTTPS加密传输,并且服务端的凭据是经过验证的(使用服务端CA证书)。

3.3、使用bundle进行离线传输

有时用户的机器无法直接访问Git版本库所在主机,又或者服务端主机关闭了,但是用户又希望将某些变更拷贝到其他机器上。也有可能用户的网络出现故障。有时也许是因为安全因素用户无法访问本地网络。有时也许是因为无线/以太网卡失灵。

这时git bundle命令就能派上用场了。该命令会将本该通过无线网络进行传输的对象和引用打包成名为bundle的特定二进制文件(例如只包含分支的打包文件等)。此时用户需要声明哪些提交对象应该被打包(通常在进行网络传输时网络协议会自动为用户处理)。

当用户采用了某个智能协议之后,上述步骤通常在网络协商过程发生,客户端会告知服务端自己本地版本库中包含了哪些内容,希望从服务端版本获取哪些引用,并找到相应的修订版本。然后服务端通过客户端发送的信息创建一个打包文件,返回客户端的只是一些必要文件,从而减少带宽占用。

接下来,由于某些原因,用户希望将它们移动到自己的机器上(例如sneakernet,这意味着需要将bundle文件拷贝到移动硬盘上来达到文件共享的目的)。用户可以通过git clone和git fetch命令并用bundle文件名替换版本库URL进行相关操作。

Git传输代理:有时用户无法直接访问服务器,例如在安装了防火墙的局域网中,因此用户可能需要借助代理访问外网。

对于原生的Git通信协议(git://),用户可以使用配置变量core.gitProxy或者环境变量GIT_PROXY_COMMAND声明一个代理命令,例如ssh。

可以使用core.gitProxy值—— for 这样特殊的语法对单个远程版本库进行配置。用户可以通过配置变量http.proxy或者环境变量curl声明HTTP代理服务器,以便调用HTTP(s)协议(http(s)/)。用户可以在单个远程版本库中使用配置变量remote..proxy进行相关配置。

用户可以配置SSH(使用它的配置文件,例如~/.ssh/ config)来使用隧道(端口转发)或者使用一个代理命令(例如netcat/nc或者ssh的netcat模式)。SSH代理是一个非常棒的解决方案。如果既没有隧道也没有代理可用的话,还可以使用ext::通信助手,本章后续部分会对它进行详细介绍。

3.4、远程版本库传输助手

当Git无法识别某个特定的传输协议时(内置协议不支持),它会尝试使用相应的远程版本库助手解析该协议。当Git服务解析某个协议时,用户会看到如下错误提示信息:

$ git clone shh://git@example.com:repo
Cloning into 'repo'...
fatal: Unable to find remote helper for 'shh'

上述异常信息的含义是指Git系统尝试查找git-remote-shh处理shh协议(实际上因为印刷错误,应该是ssh),但是却无法找到与之相关的可执行程序名称。用户可以使用::

这样的语法,显式明确指定某个特定的远程版本库助手,中定义了助手名称(git remote-),
中定义了助手需要查找的版本库地址。

时下最新版Git程序对哑HTTP、HTTPS、FTP和FTPS等协议都提供了远程版本库助手,它们分别是git-remote-http、git-remote-https、git-remote-ftp和git-remote-ftps。

3.5、凭据/密码管理

大部分情况下,除了本地协议之外,将变更发布到远程版本库上都需要用户身份验证(用户自身的身份验证)和Git系统授权(给定用户是否有权限推送变更)。有时从版本库拉取更新记录也需要身份验证。常用于身份验证的信息是用户名和密码。如果用户不介意信息泄漏,就可以将用户名添加到HTTP和SSH的版本库URL中,或者还可以使用验证助手机制。虽然技术上来说没什么问题,但是用户永远不应该将密码放在URL中,因为密码可能会被其他不怀好意的人看到,例如他们在监听进程通信时。

除了底层传输引擎的内在机制、ssh的SSH_ASKPASS变量,以及基于curl传输的~/.netrc文件,Git自身也提供了相关的解决方案。

1、密码验证

某些Git命令支持交互式密码验证(当然还需要知道相关的用户名),例如git svn、HTTP接口以及IMAP授权(可以用来调用外部程序)。该程序可以被相应的对话框(又称为域,描述密码的用途)调用,Git从上述程序的标准输出中读取密码。

Git将会以此在以下几个地方验证用户的密码和用户名信息:

  • 程序通过环境变量GIT_ASKPASS设定的参数,如果设置了该变量值(Git声明的环境变量优先级永远比配置变量高)。
  • 然后检查配置变量core.askpass是否做了相关设置。
  • 然后检查用户终端对话框。“askpass”外部程序经常会检查用户桌面环境(安装后,如果有必要的话)。例如(x11-)ssh-askpass提供了一个纯文本的X-Window对话框来验证用户名和密码,此外还有GNOME环境的ssh-askpass-gnome,KDE环境的ksshaskpass,MacOS X下的mac-ssh-askpass,MS Windows下的win-ssh-askpass可供用户选择。Git还提供了一款使用Tcl/Tk—git-gui–askpass开发的跨平台密码验证对话框来适配git gui图形界面和gitk历史记录查看器。

Git配置的优先级,可以通过多种方法配置Git中命令的行为。它们遵循如下规则:
少数服从多数,先来优于后到。
命令行选项,例如–pager;Git特别声明的环境变量,例如GIT_PAGER和GIT_ASKPASS(这类变量常使用GIT_前缀);配置选项(在某个配置文件中,配置选项也有相应的优先级),例如core.pager和core.askpass;通用环境变量,例如PAGER和SSH_ASKPASS;内置的默认属性,例如less打印或者终端提示。

2、SSH公钥授权

SSH传输协议除了密码之外还有别的授权验证机制。公钥授权就是其中之一。这种机制可以避免用户一次又一次地输入密码。此外,版本库主机服务提供的SSH访问也可能会用到它,这可能是因为一个用户进行验证时,基于其公钥不需要使用一个独立的账户(这也是gitolite的用途)。

其设计理念是用户创建公钥/私钥对一起运行,例如ssh-keygen软件。公钥会被发送到服务端,例如使用ssh-copy-id(在远程版本库服务器上的~/.ssh/authorized_keys文件夹下添加名为*.pub的公钥文件)。 然后用户就可以在自己的本地机器上使用私钥访问远程版本库了,例如使用~/.ssh/id_dsa这样的私钥文件。
Git——协作开发_第8张图片

如果不是默认的验证密钥,用户还需要使用特定的验证凭据文件为给定主机进行ssh配置(Linux下配置文件在~/.ssh/config文件夹中)。

另外一种简便的方法是使用像ssh-agent这样的授权代理软件进行公钥授权(在Windows上使用基于PuTTY的Pagenat软件)。通过代理还可以让用户更方便地使用带密码保护的私钥。对于代理来说,用户只需要在添加密钥时输入一次密码即可(当然还需要用户执行一些其他操作,例如在ssh-agent上执行ssh-add命令)。

3、验证助手

一次又一次地输入相同的凭据信息的用户体验是非常糟糕的。对于SSH,用户可以使用公钥进行授权验证,这一点也是它有别于其他协议的地方。Git凭据配置提供了两种办法简化问题。

第1种方法是对于给定的授权上下文默认用户名(如果URL中没有提供用户名的话)提供静态配置,例如主机名:

[credential "https://git.example.com"]
        username = user

这一点对于没有提供安全的存放凭据机制的用户来说是非常有利的。

第2种方法是使用Git可以获取用户名和密码的外部程序作为凭据助手。这些程序通常都有桌面环境或者操作系统提供安全存储机制的接口(例如keychain、keyring、wallet和credentials manager等)。

一般来说,Git软件至少都内置了缓存和存储助手。缓存助手(git-credential- cache)可以临时将凭据信息存放在内存中,默认情况下它存储密码和用户名的时间是15分钟。存储助手(git-credential-store)可以将用于授权验证的信息长期存放在硬盘上,并且存放的文件只能由对应的用户读取(例如~/.netrc文件);当然还有第三方的netrc助手(git-credential-netrc),它可以对netrc/authinfo中的文件进行GPG加密。

如前面示例所述,如果希望使用某个凭据助手的功能和选项,既可以通过全局性的,也可以通过局部验证授权上下文环境实现。全局凭据的配置如下:

[credential]
        helper = cache --timeout=300

这将创建一个寿命为300秒的Git缓存凭据。如果凭据助手的名称不是绝对路径(例如/usr/local/bin/git-kde- credentials-helper),Git将会为助手名称前面添加credential-前缀。用户如果希望查看哪些助手是可供选择的,可以使用git help -a | grep credential-命令(排除名称中包含–的名字,因为这和内部实现有关)。

现有的凭据助手一般采用的是相关桌面环境的安全存储机制。当用户使用它们时,只需要输入一次密码将它们解锁即可(某些助手可以在Git源码中的contrib/区域找到)。现有针对Gnome Keyring的git-credential-gnome-keyring和git-credential-gnomekeyring,针对MacOS X Keychain的git-credential-osxkeychain,针对MS Windows的git-credential-wincred和git-credential-winstore的凭据管理和存储助手组件。

Git将会采用凭据配置中最常用的授权验证上下文环境,因此如果用户希望通过路径名辨别HTTP的URL(例如在同一主机不同版本库分别提供各自不同的用户名),那么需要将配置变量useHttpPath的值设置为true。如果配置了多种上下文凭据助手,Git将会依次调用它们,直到系统获得匹配的用户名和密码。

在了解凭据助手之前,用户可以使用包含桌面环境keychain接口的askpass程序,例如kwalletaskpass(KDE Wallet)和git-password (MacOS X Keychain)。

4、发布变更到上游

“协作工作流”已经介绍过多种版本库的创建方法,我们将会学习一些常见的为项目添加工作成果的方式。接下来将会向大家介绍发布变更的主要方法。

在开始为项目工作之前,用户最好能够经常和主工作流保持同步,将官方版的版本库及时集成到自己的本地版本库中。当然,上述工作是属于维护者的。

4.1、推送变更到公共版本库

在中心式工作流中,用户只需要将本地的变更推送到中心服务器即可,如下图所示。因为用户将中心版本库共享给了团队其他成员,也有可能出现用户尝试将变更推送到中心版本库上时,其他成员已经推送了一些变更到分支上(即非快进)的情况。在这种情况下,用户需要先执行pull操作(拉取和合并相关变更,或者拉取变更后进行变基操作)集成其他人员的工作成果,然后再推送自己的变更。
Git——协作开发_第9张图片
另外一个在相同工作流中可能遇到的问题是,你的团队中每个成员都提交了自己的变更集到代码审核系统中,例如Gerrit。一个可行的解决方案是在特定版本库中推送某个特定的ref引用(以目标分支名结尾,例如refs/for/)。然后变更审核服务器可以根据独立的ref引用集合自动集成每组变更(例如refs/changes/是根据一系列的变更ID进行提交命名的)。

4.2、生成pull请求

在使用个人公共版本库的工作流中,用户需要向合作开发者、维护者或者集成经理发送有新的变更需要推送的通知。git request-pull命令可以帮助用户完成此目的。给定起始点(目标修订区间的起始位置)、远程公共版本库的URL或者名称,它将会生成一个变更记录摘要:

$ git request-pull origin/master publish
The following changes since commit
ba5807e44d75285244e1d2eacb1c10cbc5cf3935:

   Merge: strtol() + checks (2014-05-31 20:43:42 +0200)

are available in the git repository at:

  https://git.example.com/random master

Alice Developer (1):
      Support optional <count> parameter

src/rand.c |    26 +++++++++++++++++++++-----
1 files changed, 21 insertions(+), 5 deletions(-)

pull请求包含基于变更记录的SHA-1码(一系列准备执行pull操作的首个提交对象之前的修订)、提交的标题、公共版本库的URL和分支(作为git pull命令的相关参数),以及变更的shortlog和diffstat。

上述输出结果可以发送给维护者,例如通过电子邮件的形式。很多Git托管软件和服务都内置了对git request-pull命令相同的功能(例如GitHub上创建pull请求的特性)。

4.3、交换补丁

很多大型项目(以及很多开源项目)都建立了以补丁的形式接受变更记录的流程,例如为了降低贡献变更的准入门槛。如果用户想给某个项目发送一次性的贡献代码,但是又不打算成为正式的项目开发人员,那么发送补丁也许会比创建完整的协作流程更简单一些(为了获得向中心工作流提交修订的权限,用户需要为拉取工作流建立个人的公共版本库)。此外,用户可以使用任何兼容的工具生成补丁,项目可以在不考虑用户使用何种版本控制工具类型的前提下接受补丁。

如今,随着各种免费的Git托管服务的普及,可能更不容易找到一款可以发送电子邮件格式补丁的邮件客户端了,不过submitGit(专门提交补丁到Git项目的邮件列表)之类服务应该可以帮上忙。

此外,作为变更记录文本形式的补丁,可以方便地被计算机和人类读取。这使得它们广受用户青睐,而且非常适合进行代码审核。很多开源项目都使用公共的邮件列表来达到此目的:用户可以发送一封电子邮件到该列表,然后公众可以在你提交的变更记录的基础上审核和添加注释。为了将每个提交生成电子邮件的版本转换成mbox格式的补丁,用户可以使用git format-patch命令:

$ git format-patch -M -1
0001-Support-optional-count-parameter.patch

用户可以将任意修订区间指示符与上述命令一起使用,如前面的示例所述,最常用的方式是限定修订数量,以及使用双点符号表示修订区间,例如@{u}…。当生成大批量的补丁时,选择一个目录来保存生成的补丁是非常有用的。用户可以使用-o 指定目录地址。-M选项在git format-patch命令(通过git diff命令)中的作用是启用重命名检测功能,这样做的好处是补丁体积更小,更方便审核。

补丁文件通常是以下列类似内容结尾的:

From db23d0eb16f553dd17ed476bec731d65cf37cbdc Mon Sep 17 00:00:00 2001
From: Alice Developer <alice@company.com>
Date: Sat, 31 May 2014 20:25:40 +0200
Subject: [PATCH] Initialize random number generator

Signed-off-by: Alice Developer <alice@company.com>
---
 random.c |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/random.c b/random.c 
index cc09a47..5e095ce 100644
--- a/random.c
+++ b/random.c
@@ -1,5 +1,6 @@
 #include 
 #include 
+#include 

 int random_int(int max)
@@ -15,6 +16,7 @@ int main(int argc, char *argv[])

  int max = atoi(argv[1]);

+  srand(time(NULL));
   int result = random_int(max);
   printf("%d\n", result);
--
2.5.0

这实际上是一封mbox格式的完整电子邮件。从主题(Subject)开始到—符号之前的内容是注释信息,即变更记录的说明。为了将它们发送到邮件列表或者一个开发人员那里,用户可以使用git send-email或者git imap-send命令。维护者可以使用git am命令集成上述一系列补丁,自动化创建相关的提交对象。

这里的[PATCH]前缀是为了让用户更方便地将它们和其他电子邮件区分开来。前缀可以也经常包含额外信息,例如一系列补丁的数目、修订的名称、正在研发中的描述信息,或者请求相关的状态说明,例如[RFC/PATCHv4 3/8]。

用户可以帮助将来的审核人员给这些补丁添加更多信息。例如有关替代方法的信息,补丁和上一修订版本之间的差异(以前的提交),或者补丁在代码实现上成员讨论的引用摘要(例如相关的邮件列表)。用户可以将文本添加到变更摘要之前,即补丁起始位置和—符号之间。不过上述内容在执行git am命令时会被忽略。

5、信任链

项目开发过程中的代码质量是衡量协作效果好坏的重要指标之一,其中包括防止意外事件对版本库造成损害、黑客的恶意入侵等,这些问题都是可以借助版本控制系统解决的。Git需要确保版本中的内容是可信赖的,其中包括用户自己和其他开发人员(特别是项目的官方版本库)。

5.1、内容地址存储

Git采用SHA-1哈希码作为提交对象(代表项目历史的修订记录)的原生标识符。这种机制能够以分布式生成提交标识符,使得提交对象的SHA-1加密函数可以指向上一个提交对象(包含父提交的SHA-1标识符)。

此外,所有其他存储在版本库中(包括用blob对象表示修订对象的文件内容,以及树对象代表的层级文件结构)的数据可以采用上述机制。所有类型的对象都是根据其内容编址的,更精确地说,是对象的哈希函数。用户可以将Git版本库的基础结构当作根据内容编址的对象数据库。

Git通过安全的SHA-1码内建了信任链。一方面来看,提交的SHA-1码是基于其内容生成的,其中还包括父提交的内容,依次类推直到根提交。另一方面,提交对象的内容包括项目顶层目录树的SHA-1码,它根据相关内容生成,这些内容又包含子目录树和文件内容的blob对象,依次延伸到每个独立文件。

上述特性使得SHA-1码可以用来验证对象来源(存在不信任因素)的合法性,即它们自创建以来是否被非法篡改过。

5.2、轻量级标签、附注标签和签名标签

信任链可以帮助用户验证内容,不过无法验证创建该内容的用户的标识符(作者和提交者的名字都是可以进行配置的)。GPG/PGP签名就是为了解决这一问题的,其包括签名标签、签名提交和签名合并。

Git支持两种标签:轻量级标签和附注标签。

1、轻量级标签

轻量级标签和分支很像,并不修改变更记录,它只是修订图上指向特定提交节点的指针(引用),因此在refs/tags/命名空间中的数据比在refs/heads/中功能更强。

2、附注标签

附注标签,顾名思义,也包含标签对象。这里的标签引用(在refs/tags/种)指向了标签对象,然后该对象指向某个提交对象。标签对象内包含其创建日期、标签化的标识符(名称和电子邮件),以及一个标签化的信息。用户可以使用git tag –a(或者—annotate)命令创建一个附注标签。如果用户没有在命令行中为附注标签声明信息(例如使用-m ""选项),Git将会自动打开用户的编辑器,以方便用户输入信息。

用户可以使用git show命令查看标签化提交中的标签数据(注释已略去):

$ git show v0.2
tag v0.2
Tagger: Joe R Hacker <joe@company.com>
Date:   Sun Jun 1 03:10:07 2014 -0700

random v0.2

commit 5d2584867fe4e94ab7d211a206bc0bc3804d37a9

3、签名标签

签名标签是附有明文GnuPG签名的附注标签。用户可以使用git tag -a或者git tag -u 命令创建它(使用开发者自己的提交者标识符选择签名密钥,或者使用user.signingKey设置),上述两种方式都假定用户拥有私人的GPG密钥(创建时可以使用gpg–gen-key选项)。

附注或者签名标签的诞生通常意味着软件有新版本发布,轻量级标签代表私人或者临时的修订标签。为此,某些Git命令(例如git describe)将默认忽略轻量级标签。

当然,在协作工作流中将签名标签公开是非常重要的,而且这也是一种验证方式。

4、发布标签

默认情况下,Git系统是不会推送标签的,用户必须明确声明。一种解决方案是使用git push tag 命令独立推送标签(标签符号和更长的refspec的符号refs/tags/:refs/tags/是等效的);当然,大部分情况下,用户还可以忽略该标签符号。另外一种解决方案是使用–tags选项大批量推送轻量级标签和附注标签,或者使用–follow-tags选项只推送指向提交对象的附注标签。很明显,当发现提交对象被标记了错误的标签时,又或者标签是非公开的时,用户可以随意地对标签进行编辑(使用git tag -f命令)。

当用户更新变更时,Git会自动遵循标签的规则,下载附注标签指向的提交节点对象。这意味着下游开发者将会自动获取签名标签,并且能够对预览版本进行校验。

4、标签验证

为了对签名标签进行验证,用户可以使用git tag -v 命令。为此用户还需要将签名者的GPG公钥添加到自己的密钥环中(可以通过gpg --import或者gpg --keyserver --recv-key 等命令导入),当然标签生成者的密钥也能够通过用户自身信任链的审查。

$ git tag -v v0.2
object 1085f3360e148e4b290ea1477143e25cae995fdd
type commit
tag signed
tagger Joe Random <jrandom@example.com> 1411122206 +0200

project v0.2
gpg: Signature made Fri Jul 19 12:23:33 2014 CEST using RSA key ID
A0218851
gpg: Good signature from "Joe Random "

5.3、签名提交

签名标签是用户和开发人员校验发布的相关标签是否是维护者创建的一个很好的解决方案。不过如何确认一个提交是某个名为Jane Doe、电子邮件是[email protected]的人创建的呢?如何让任何人都可以对上述情况进行校验检查呢?

一个可选的解决方案是Git自1.7.9版本以来支持的GPG签名的独立提交。用户可以使用git commit --gpg-sign[=]命令(或者-S的缩写形式)达到上述目的。密钥标识符是可选的,如果没有提供该标识符,Git将会自动采用用户的标识符。注意,-S选项和-s选项是有区别的,后者会在提交信息尾部添加一行经过数字证书所有权审核的验证结果信息。

$ git commit -a --gpg-sign

You need a passphrase to unlock the secret key for
user: "Jane Doe "
2048-bit RSA key, ID A0218851, created 2014-03-19

[master 1085f33] README: eol at eof
1 file changed, 1 insertion(+), 1 deletion(-)

为了让提交支持校验,只需推送它们即可。任何人希望对它们进行校验的话,都可以通过git log(或者git show)命令配合–show-signature选项;在git log --format=中添加%Gx占位符也能达到同样的目的。

$ git log -1 --show-signature
commit 1085f3360e148e4b290ea1477143e25cae995fdd
gpg: Signature made Wed Mar 19 11:53:49 2014 CEST using RSA key ID
A0218851
gpg: Good signature from "Jane Doe "
Author: Jane Doe <jane@company.com>
Date:   Wed Mar 19 11:53:48 2014 +0200

    README: eol at eof

自Git 2.1.0版本以来,用户还可以使用git verify-commit命令对相关提交进行校验。

5.4、合并签名标签(合并标签)

前文所述的签名提交机制在某些工作流中可能会非常有用,不过在用户的早期推送过程中可能会带来不便,例如在用户自己的公共版本库中,经过一段时间后,用户会对是否值得将它们推送到上游(是否知道发送到主版本上)而举棋不定。上述情况是可能发生的,而且只会在事后(提交创建很久之后)被察觉,即系列迭代提交经过几轮代码审核之后。

用户可以通过它的图形闭合之后(通过代码审核之后)重写整个提交集合来解决这个问题,对每个重写后的提交进行签名,或者只对顶层的提交进行修改和签名。上述两种方法都需要强制推送提交以替换签名的提交历史记录。用户还可以创建一个空的提交对象(使用-allow-empty选项),对它签名,然后将它推送到顶层提交记录上。不过还有一个更好的解决方案——请求拉取签名标签(Git 1.7.9以及以上版本支持)。

在这种工作流中,用户可以进行功能特性的开发,当它们达到预期目标之后,就可以创建一个签名标签并将它们推送到服务器上(在一系列开发工作中最后一个提交上添加标签)。用户不需要将自己的工作分支推送到服务端——只需推送标签即可。如果工作流中出现了一个发送给集成管理者的拉取请求,用户可以使用一个标签作为其最终的提交节点:

$ git tag -s for-maintainer
$ git request-pull origin/master public-repo 1253-for-maintainer \
  >msg.txt

签名标签的信息会在pull请求中用虚线与其他内容区分开,这意味着用户在创建签名标签时可以在标签信息中添加和该工作有关的解释性信息。维护者在收到这样的pull请求之后,可以根据它拷贝相关的版本库,获取和集成相关的具名标签。当记录汇总拉取请求的具名标签之后,Git将会打开用户的编辑器,并要求用户输入注释信息。集成人员将会看到以如下内容开始的模版:

Merge tag '1252-for-maintainer'

Work on task tsk-1252

# gpg: Signature made Wed Mar 19 12:23:33 2014 CEST using RSA key ID
A0218851
# gpg: Good signature from "Jane Doe "

这个提交模版包括被合并签名标签对象的GPG验证输出结果注释(不过这并不是最终的合并操作的注释信息)。标签信息有助于用户更好地描述合并操作。

签名标签并不是和标签对象那样被拉取后存放在集成者的版本库中的,它的内容其实是被隐藏存放在合并提交对象里的。这么做的原因是不希望这类工作标签因为数量庞大的原因而污染标签的命名空间。在签名标签被集成之后,开发者可以安全地将这些标签删除(例如git push public-repo --delete 1252-for- maintainer)。

记录合并提交内部的签名可以方便地使用–show-signature选项进行事后验证:

$ git log -1 --show-signature
commit 0507c804e0e297cd163481d4cb20f3f48ceb87cb
merged tag '1252-for-maintainer'
gpg: Signature made Wed Mar 19 12:23:33 2014 CEST using RSA key ID
A0218851
gpg: Good signature from "Jane Doe "
Merge: 5d25848 1085f33
Author: Jane Doe <jane@company.com>
Date:   Wed Mar 19 12:25:08 2014 +0200

    Merge tag 'for-maintainer'

    Work on task tsk-1252

你可能感兴趣的:(#,DevOps,git)