Git正解 脱水版 【5. 分布式Git】

2023-11-01 20:41
文章标签 分布式 git 脱水 正解

本文主要是介绍Git正解 脱水版 【5. 分布式Git】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

5.1 分布式工作流程

Git的分布式特性,可使协作开发更加灵活,在中心式版本控制系统(CVCS)中,每个用户可视为连接到中心hub的一个节点,而在Git中,每个用户既是节点又是hub,用户既可以向远程仓库推送数据,也可维护同一个远程仓库,由此产生了多种的工作流程,当然各有优缺点,而用户可单独选用,也可以混合使用。

中心式工作流

在CVCS中,只有一种协作模式,即中心式工作流,只有中心hub(或称仓库)可放置代码,所有用户节点必须连接到中心hub,才能进行开发,同时需要保证中心hub的数据同步,
在这里插入图片描述
这意味着,如果两个协作者同时基于中心hub的数据,进行后续变更,首个协作者者完成推送后,第二个协作者必须合并首个协作者的推送,再推送自己的变更,同时不能覆盖首个协作者的推送,这一点已被Git继承。

如果公司或团队内部,已经习惯了中心式工作流,迁移到Git,无需任何改变,只需配置一个仓库,将推送权限授予所有用户,Git同样不允许用户推送的相互覆盖。

举例说明,John和Jessica处于并行开发中,John率先完成开发,并推送到服务器,当Jessica完成开发后,向服务器推送数据时,却被服务器驳回,原因是,Jessica必须获取合并John的提交,首先完成与服务器的同步后,才可推送自己的数据,这类工作流的优势在于,大多数人已经习惯了这种模式,当然这类工作流并不局限于小团队,基于Git的分支功能,可满足多条分支上,数百人的同步开发。

集中管理工作流

Git允许用户同时使用多个远程仓库,每个开发者都能写入自己的远程仓库,并读取他人的仓库,在这种情况下,通常会存在一个标准仓库,或称之为官方仓库,如果用户想参与该项目,则需要创建一个官方仓库的公开副本,再将自己的开发结果,推送公开副本,之后可告知官方仓库的维护者,获取开发数据,然后维护者可将用户仓库,设为远程仓库,获取并测试用户的代码,最后合并到本地仓库,再推送到官方仓库,整个流程如下,

  1. 项目维护者可向官方仓库推送数据
  2. 代码贡献者需克隆官方仓库,再进行开发
  3. 代码贡献者将开发结果,推送到自己的远程仓库
  4. 代码贡献者使用邮件,告知维护者,申请合并自己的开发成果
  5. 维护者添加代码贡献者的远程仓库,进行本地合并
  6. 维护者将本地仓库的变更,推送到远程仓库

在这里插入图片描述
GitHub和GitLab均采用这类常见的工作流,任何人都能创建项目副本,并查看他人的开发成果,另一个优势在于,项目成员之间不会相互打扰,维护者可在任意时间,获取代码贡献者的数据,代码贡献者也无需等待自己的数据,被合并到官方仓库。

分层集中(司令官与副官)工作流

这是集中管理工作流的一个变种,适用于拥有数百位协作者的巨型项目,比如Linux内核,多位集中管理员负责整个仓库的不同部分,他们被称为副官,所有副官之上,还有一位司令官,司令官可将所有协作者的工作成果,推送到远程仓库,整个工作流程如下:

  1. 开发者基于远程仓库的最新master分支,创建特性分支,并开始工作,之后将衍合提交,可简化提交历史,便于上层的合并
  2. 副官们可将下层开发者们的特性分支,合并到自己的本地分支master
  3. 司令官可将副官们的master分支,合并到自己的本地分支master
  4. 最后,司令官可将本地分支master推送到远程仓库的分支master
    在这里插入图片描述
    上述工作流并不常见,只适用于巨型项目,或者需要分级管理的环境,它只允许司令官分配任务,收集来自不同部分的大量变更。

5.2 贡献代码

由于Git足够灵活,有多种协作开发的方式,给出一个贡献代码的标准方法,这相当困难,因为每个项目都存在差异,比如贡献者的数量,选定的工作流,用户权限,外部用户的沟通方式,

首先是贡献者的数量,在小团队中,贡献代码相对简单,而在大型项目中,数千位贡献者每天会产生成百上千个提交,更多的贡献者意味着,
保持代码的整洁,以及易于合并,会变得更加困难,所以每个贡献者都必须保证自己的代码,在合并过程中,不会出现过期或严重错误。

其次是工作流的选择,在中心式工作流中,所有用户是否都需要写入权限,项目维护者是否需要检查所有的补丁,而所有补丁需要通过同级评审还是核准,在分层管理中,如何保证提交的快速处理。

之后是用户权限,在工作流的影响下,用户是否拥有写入权限,在贡献代码时,则有相当大的差异,如果没有写入权限,项目需要提供一个接受贡献的方式或策略,这将影响到用户单次贡献的工作量,以及贡献的间隔时间。

只有想清楚以上问题的答案,才能得到一个有效的工作流,以及实现高效的代码贡献。

提交指南

在Git自身的项目中,提供了一份文档,其中包含了一些技巧,可基于补丁的方式,创建一个提交,参见Git源码包的Documentation/SubmittingPatches文件。

首先用户更新中,不能包含空白符号的错误,Git提供了一个检查工具git diff --check,如下的红色块,因为空白符的问题,可能会骚扰到其他协作者。
在这里插入图片描述
其次,保证每次提交都是一个独立逻辑单元,以使得每次提交都易于理解,千万不要采用扔垃圾的方式,打包提交,应当提供每次提交的明确的指向性,如果同一文件中,进行多次修改,可使用git add --patch,实现局部暂存,其实无论用户提交一次,还是提交十次,项目分支上,呈现的最后结果都是一致的,但提交十次,可使协作者更容易查看变更的细节。同时这类方式,更有利于提交的发布和恢复,之前的内容中,已经介绍了一些技巧,用于修整提交历史,以及暂存区文件的处理,以期望获得一个更简洁和易于理解的提交历史。

最后值得注意的事,养成高质量提交描述的编写习惯,可使协作开发更简单,一般情况下,提交描述只有一行,并且不超过50个字符,同时简明扼要,提交描述之后是一个空白行,空白行之后,将给出更多的提交细节,其中包含了修改原因,修改前后的对比,在提交描述的编写中,推荐使用一般语态,以下是Tim Pope总结的提交描述的模板,

标题,不超过50个字符的总结如果有必要,应给出更详细的解释,但不要超过72个字符,
在某些情况下,标题行将视为邮件标题,后续文本将视为邮件正文,
使用空白行,分割标题行和正文,如上,除非正文被忽略,
如果忽略空白行,类似于rebase的工具,将无法正确解析。使用一般语态,编写提交描述,更符合git merge和git revert等命令,
生成的结果。后续描述,应放置在空白行之后。- 前缀也使用圆点符号- 前缀通常会使用连字符或星号,后跟一个空格符,并使用空白行,分割文本行,这可自行约定- 使用缩进

运行git log --no-merges,用户可查看Git自身项目中,包含的格式规整的提交描述,注意,本教程的大多数提交,为了简化描述,都没有提供一个格式规整的提交描述。

小型团队

如果是几人参与的私有闭源项目,所有项目成员都具有远程仓库的推送权限,在这种情况下,可选择简单的中心式工作流,即使在这类工作流中,Git相比于中心式CVS,同样具备优势。举例说明,两个开发者加一个远程仓库的故事,首位开发者John克隆了远程仓库,并完成了修改,再提交到本地仓库,

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'remove invalid default value'
[master 738ee87] remove invalid default value
1 files changed, 1 insertions(+), 1 deletions(-)

第二个开发者Jessica也克隆了远程仓库,并将一个变更,提交到本地仓库,

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
1 files changed, 1 insertions(+), 0 deletions(-)

之后Jessica将工作成果,推送到远程仓库,

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
1edee6b..fbff5bc master -> master

在推送操作的输出结果中,最后一行给出了一个有价值的信息,基本格式为<原有提交的校验码>…<当前提交的校验码> [本地分支名] -> [远程分支名],继续故事,之后John也将本地仓库,推送到远程仓库,

# John's Machine
$ git push origin master
To john@githost:simplegit.git
! [rejected]master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

John推送出错的原因,John的本地仓库与远程仓库不同步,首先John需下载Jessica的推送数据,注意Jessica的最后提交的验证码为fbff5bc,即下图的fbff5,738ee为John本地仓库的最新提交,

$ git fetch origin
...
From john@githost:simplegit
+ 049d078...fbff5bc master  -> origin/master

在这里插入图片描述
将Jessica的推送数据,与John的最新提交进行合并,

$ git merge origin/master
Merge made by the 'recursive' strategy.
TODO | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)

在这里插入图片描述
这时John需测试Jessica的推送数据,对自己的代码是否有影响,完成测试后,John可将合并生成的最新提交,推送到远程仓库,

$ git push origin master
...
To john@githost:simplegit.git
fbff5bc..72bbc59 master -> master

在这里插入图片描述
这时Jessica又创建了另一个分支issue54,并向本地仓库中,提交了三次更新,同时她并未获取远程仓库中,John的最新推送,
在这里插入图片描述
John告知Jessica,自己已向远程仓库,推送了最新数据,为了避免推送失败,Jessica需要获取远程仓库的最新数据,

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
fbff5bc..72bbc59 master -> origin/master

在这里插入图片描述
当新建分支的开发完成后,Jessica需要了解John的工作成果中,哪些可以合并,可运行git log进行查找,

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date: Fri May 29 16:01:27 2009 -0700remove invalid default value

issue54…origin/master格式是一个过滤器,用于显示,包含在origin/master分支,但并未包含在issue54分支的提交,基于上述输出,只有一个提交,需要Jessica进行合并,如果直接合并到新分支,该提交将改变Jessica本地仓库的历史记录,不可取,因此Jessica首先需要将新建分支,合并到本地master分支,再将John的工作成果(origin/master分支),合并到本地master分支,之后才可推送到远程仓库,为合并issue54分支,首先Jessica需切换到本地master分支,

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Jessica可自由选择origin/master和issue54分支的合并次序,因为它们都是上游分支,次序不会产生任何问题,合并的最终结果都是一样的,只是提交的历史记录会有差别,Jessica决定首先合并issue54分支,

$ git merge issue54
Updating fbff5bc..4af4298
Fast forwardREADME           | 1 +lib/simplegit.rb | 6 +++++-2 files changed, 6 insertions(+), 1 deletions(-)

合并未出现问题,Jessica继续合并,包含John推送的origin/master分支,

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.lib/simplegit.rb | 2 +-1 files changed, 1 insertions(+), 1 deletions(-)

在这里插入图片描述
这时Jessica已完成了所有的本地合并,可将最新提交,推送到远程仓库,

$ git push origin master
...
To jessica@githost:simplegit.git72bbc59..8059c15 master -> master

在这里插入图片描述
当然这是一种最简单的工作流,对应的流程图如下,
在这里插入图片描述

分组协作

再来看一个更复杂的分组协作示例,John和Jessica负责开发功能A,Jessica又和Josie负责开发功能B,并使用集中管理工作流,每一组只有一人可管理分支,同时所有开发者中,也只有一人,可向远程仓库的master分支,推送数据,因此所有开发都将在组分支上完成,最后合并到一起,Jessica担负了两个小组的开发和管理任务,假定她已克隆了远程仓库,并决定先从功能A开始,于是创建featureA分支,并完成开发和提交,

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function1 files changed, 1 insertions(+), 1 deletions(-)

这时Jessica需要将工作成果,分享给John,因此将featureA分支推送到远程仓库,注意,Jessica并没有向远程仓库的master分支推送数据,当然她也没有权限,

$ git push -u origin featureA
...
To jessica@githost:simplegit.git* [new branch] featureA -> featureA

Jessica通过邮件,告知John,从远程仓库中,获取featureA分支,并开始等待John的反馈,这时Jessica决定开发功能B,之后基于远程master分支的本地副本origin/master,创建一个新分支featureB,注意,如果直接套用featureA的创建命令,等同于在featureA分支的基础上,创建featureB分支,

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

之后Jessica在featureB分支上,提交了若干更新:

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files1 files changed, 5 insertions(+), 0 deletions(-)

在这里插入图片描述
当Jessica准备推送featureB分支时,收到了Josie邮件,邮件中提到,Josie也在本地仓库中创建了新分支,完成了功能B的初始功能,并推送到远程仓库的featureBee分支,这时Jessica需要首先获取Josie的推送数据,

$ git fetch origin
...
From jessica@githost:simplegit* [new branch] featureBee -> origin/featureBee

假定Jessica当前处于featureB分支,可将origin/featureBee分支,合并到当前分支,

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.lib/simplegit.rb | 4 ++++1 files changed, 4 insertions(+), 0 deletions(-)

Jessica当然希望直接将合并后的featureB分支,直接推送到远程仓库,但是Josie已创建一个上游分支featureBee,因此Jessica必须使用分支映射功能,完成featureB分支的推送,

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.gitfba9af8..cd685d1 featureB -> featureBee

上述操作即为分支名的映射,-u选项(–set-upstream的缩写)可配置分支名的映射。

之后Jessica又收到了John的邮件,邮件中提到,John已向远程仓库的featureA分支,推送了更新,这时Jessica运行git fetch,实现本地仓库与远程仓库的同步,

$ git fetch origin
...
From jessica@githost:simplegit3300904..aad881d featureA -> origin/featureA

Jessica可查看John所推送的提交,

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date: Fri May 29 19:57:33 2009 -0700changed log output to 30 from 25

之后Jessica需将John的推送,合并到本地featureA分支,

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forwardlib/simplegit.rb | 10 +++++++++-1 files changed, 9 insertions(+), 1 deletions(-)

之后Jessica完成了功能A的开发,提交到本地featureA分支,并推送到远程仓库,

$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git3300904..774b3ed featureA -> featureA

在这里插入图片描述
此时Jessica,Josie,John已完成了功能开发,项目管理者可将featureA和featureBee分支,合并到远程仓库的master分支,之后Jessica又将远程仓库,同步到本地仓库,如下,
在这里插入图片描述
许多开发团队迁移到Git的原因之一,Git允许多个开发组的并行,并在开发过程中,不断进行组分支的合并,这使得团队的开发组也能通过远程分支完成协作,且不会影响到整个团体的开发,上述工作流的流程图如下,
在这里插入图片描述

公共项目

公共项目的贡献稍有不同,代码贡献者一般不会有,直接更新项目分支的权限,必须使用其他方法,其一,采用Git的复制功能,大多数Git线上主机(GitHub,BitBucket,repo.or.cz等)都支持该功能,之后可通过邮件,发送更新补丁,完成代码贡献,而大多数公共项目的维护者也喜欢这种方式,即使在私有项目中,开发者也可使用rebase -i,将工作成果或是多个提交,转换成更新补丁,发送给项目维护者审核。

代码贡献者可在项目的原始页面中,找到Fork按钮,点击它,即可生成一个可写入的项目副本,这等同于创建一个可写入的远程仓库,该远程仓库将命名为myfork,

$ git remote add myfork <url>

之后贡献者可将提交,推送到这个远程仓库,当然更简单的方式,即为创建一个新分支,而不是直接合并到远程仓库的master分支,因为贡献者的提交不一定会被项目维护者采纳,如果直接合并到master分支,则无法保证master分支的同步,如果项目维护者接纳了贡献者的代码,贡献者可将官方远程仓库,同步到本地仓库,再将本地仓库,推送到(从属于贡献者的)副本远程仓库,贡献者首先将提交,推送到副本远程仓库,

$ git push -u myfork featureA

再通知官方远程仓库的维护者,请求接纳贡献者的提交,通常情况下,请求可在web页面上完成,比如GitHub就有类似的请求机制,同时贡献者还可使用git request-pull命令,以及邮件,向维护者发送请求。git request-pull命令包含两个参数,一是本地新建分支的基础分支,通常是官方仓库的master分支,二是贡献者的副本远程仓库的URL,基于这两个参数,该命令可将贡献者的所有提交,生成一个细节列表。假设Jessica向John发出接纳请求,同时Jessica在本地分支上,提交了两次更新,可运行

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):added a new functionare available in the git repository at:git://githost/simplegit.git featureAJessica Smith (2):add limit to log functionchange log output to 30 from 25lib/simplegit.rb | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

以上输出将发送给维护者,其中提到,包含提交的远程分支名,所有提交的细节,以及副本远程仓库的地址。

代码贡献者不要将自己的提交,合并到master(即origin/master),一旦贡献提交被拒,也很容易丢弃提交和新建分支,重新开始,如果代码贡献者继续第二次贡献,依然需要使用master分支为基础,而不是自建分支。

$ git checkout -b featureB origin/master
... work ...
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
... email generated request pull to maintainer ...
$ git fetch origin

当然贡献者可以自建多个分支,并使用覆盖,衍合,修改等方法,对分支进行维护,但是应当保证每条分支之间的独立性,而不是相互影响,比如,
在这里插入图片描述
项目维护者获取到贡献者的提交后,在合并featureA分支时,出现冲突,这时维护者会将冲突,告知贡献者,由贡献者解决这些冲突,如下,贡献者将featureA分支衍合到origin/master,解决冲突后,再次请求维护者接纳,

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

在这里插入图片描述
由于贡献者使用了衍合,git push必须附带-f选项,以便新推送的featureA分支,可覆盖之前的featureA分支,同时之前的featureA分支不存在下游提交,否则覆盖将失败,另一个方法,将更新推送到另一个分支,比如featureAv2分支。

以下是可能出现的场景,维护者很欣赏featureB的设计思路,但觉得实现细节有瑕疵,这时贡献者需回到origin/master,再新建一个分支,用于镜像featureB分支,并重新实现featureB的设计思路,之后推送到副本远程仓库,

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
... change implementation ...
$ git commit
$ git push myfork featureBv2

–squash选项可实现分支合并的所有操作,并会告知Git仓库,两条分支是镜像关系,因此不会产生合并提交,这意味着后续提交也只有一个父提交,实际上Git提供一种分支复制的方法,同样–no-commit选项也可省略分支合并之后的自动提交,但两者之间存在区别,后者主要用于测试分支合并是否存在冲突。之后通知维护者,请求接纳贡献者的featureBv2分支,
在这里插入图片描述

基于邮件的代码贡献

大多数项目都建立了一套接纳补丁的流程,由于项目之间存在差异,因此代码贡献者需要了解每个项目的特殊规则,一些早期的大型项目通常会基于一个开发者邮件列表,来接纳补丁。

这类工作流与之前介绍的工作流基本相似,贡献者需要为每个补丁,创建一个特性分支,不同之处在于,如何提交补丁,同时贡献者无需创建项目副本,无需建立副本远程仓库,只需将所有提交,转换成邮件,并基于开发者邮件列表,完成邮件发送,如果贡献者需要发送两次提交,首先需使用git format-patch,将提交转换成邮件(mbox格式),提交描述的首行将变成邮件标题,提交描述的剩余部分,以及由提交生成的补丁,将成为邮件正文,

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

format-patch可输出,已生成的补丁文件,-M选项可允许Git检查补丁文件是否重名,以下是补丁文件的内容,

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith 
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log functionLimit log functionality to the first 20---lib/simplegit.rb |    2 +-1 files changed, 1 insertions(+), 1 deletions(-)diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGitenddef log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")enddef ls_tree(treeish = 'master')
--
2.1.0

不方便放入提交描述的内容,可加入到补丁文件中,贡献者可在—行,与补丁首行(diff --git行)之间,添加相关文本,这些文本可被开发者读取,同时又不会影响到补丁文件的正文。

为了发送邮件,可使用命令行工具,或者将文本粘贴到邮件客户端,粘贴文本经常会产生格式问题,比如一些邮件客户端,不会保留空白行和空白字符,因此Git又提供一个工具,方便用户的邮件发送,即Gmail,在Git源码文件Documentation/SubmittingPatches最后,提供了不同邮件客户端的操作指南。

首先需设定~/.gitconfig文件的imap参数项,其中包含的参数,可使用git config设定,或是手动编辑配置文件,如下,

[imap]folder = "[Gmail]/Drafts"host = imaps://imap.gmail.comuser = user@ gmail.compass = YX]8g76G_2^sFbdport = 993sslverify = false

如果IMAP服务器没有启用SSL,最后两行无需配置,邮件服务器的前缀,应使用imap://,而不是imaps://,完成配置后,可使用git send-email,将补丁文件放入,指定IMAP服务器的Drafts目录,

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

之后进入Drafts目录,添加邮件的发送地址,邮件可能需要抄送给项目维护者或相关人员,邮件发送需使用SMTP服务器,所以在.gitconfig配置文件中,还需要设定sendemail参数项,如下,

[sendemail]smtpencryption = tlssmtpserver = smtp.gmail.comsmtpuser = user@gmail.comsmtpserverport = 587

运行git send-email,完成补丁邮件的发送。

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

Git完成补丁邮件的发送后,将输出以下信息,

(mbox) Adding cc: Jessica Smith <jessica@example.com> from\line 'From: Jessica Smith <jessica@ example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>Result: OK

5.3 项目维护

除了学习如何为项目贡献代码,用户还需要了解如何进行项目维护,其中包括,format-patch生成补丁或是邮件补丁的接纳,远程分支的处理,如何处理贡献者的提交,以获得仓库运行的长期稳定。

特性分支

关于特性分支,之前已介绍过多次,尝试使用特性分支(或称之为临时分支),可以保证分支之间不存在相互干扰,用户可随时弃用特性分支,回到初始点。

邮件补丁

如果需要将邮件补丁,集成到项目中,建议先放入特性分支,进行评估,补丁集成可使用两种方法,git apply和git am。

git apply

如果补丁来自于git diff或是diff变种(类unix命令,不推荐使用),补丁集成可使用git apply,假定补丁文件为/tmp/patch-ruby-client.patch,

$ git apply /tmp/patch-ruby-client.patch

被补丁修改过的文件,将放入当前工作区,git apply基本类似于patch -p1,但更加谨慎,并能实现少量的模糊匹配,如果补丁文件来自于git diff,git apply还可处理文件的添加,删除和重命名,而patch无法完成这类任务,最后git apply还引入了,全部采用或全部放弃的工作机制,这使得集成补丁文件,只有两种结果,一是全部采用,二是全部丢弃,但patch可实现补丁的局部采用,这会将工作区带入一个不可预知的状态,因此不推荐使用diff工具,生成Git补丁,同时git apply不会产生仓库提交,完成补丁集成后,用户需要将工作区的修改文件暂存,并手动提交。运行git apply --check,可测试补丁的集成结果。

$ git apply --check 0001-seeing-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply

如果无输出信息,则表明补丁能被集成,如果测试失败,该命令将返回一个非零值,所以在shell脚本中,可使用该命令。

git am

代码贡献者还可使用format-patch生成补丁文件,同时补丁文件中,还包含了作者信息和提交描述,对于维护者来说,集成这类补丁文件则更加简单,因此强烈建议,贡献者使用format-patch。

运行git am,可集成format-patch生成的补丁,该命令可读取mbox文件,这是一种简单的能包含更多邮件信息的纯文本,如下,

From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log functionLimit log functionality to the first 20

以上是git format-patch输出的头几行,用于描述一个有效的mbox格式,维护者基于邮件或手动下载,得到一个mbox补丁后,可使用git am集成该补丁,邮件客户端可将多个补丁邮件,导出为一个mbox文件,之后可一次性集成多个补丁。

当补丁集成后,会自动生成一次提交,提交中包含的作者信息,来自邮件的From和Date项,提交描述来自邮件的Subject项,以及邮件正文,以下是一个邮件补丁的提交示例,

$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author:     Jessica Smith 
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit:     Scott Chacon  <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700add limit to log functionLimit log functionality to the first 20

Commit给出了集成补丁的维护者,CommitDate给出了集成补丁的时间,Author给出了生成补丁的贡献者,AuthorDate给出了补丁的生成时间,补丁集成当然存在失败的可能性,也许是补丁过期的问题,或者是补丁之间的相互依赖,这种情况下,git am会报错,并推荐了几种常用的处理方法,

$ git am 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

git am命令会在对应文件中,添加冲突标记,类似于合并冲突和衍合冲突,当然也能使用相同的解决方法,消除文件的冲突,暂存修改文件,运行git am --resolved,逐个解决冲突补丁,

(fix the file)
$ git add ticgit.gemspec
$ git am --resolved
Applying: seeing if this helps the gem

如果用户需要Git处理冲突,可附带-3选项,以使Git尝试三方合并,来解决冲突,同时-3选项并不是默认操作,如果提交补丁与特定仓库无关,-3选项将失效,如果提交补丁来自一个公开提交,-3选项可用于解决冲突,如下,

$ git am -3 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.

如上所示,如果不附带-3选项,提交补丁的重复集成,将被视为一个冲突,如果用户需集成一组补丁(mbox格式),则使用交互模式(-i选项)运行am命令,在集成每个补丁前,将会询问用户如何处理,

$ git am -3 -i mbox
Commit Body is:
--------------------------
seeing if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all

用户可基于交互模式,直接查看补丁,以及避免补丁的重复集成,当所有提交补丁都集成到特性分支,之后可选择,是否添加到长期分支中。

远程分支

如果贡献者创建了自己的远程仓库,并推送了一些提交,之后将仓库URL和远程分支名,告知项目维护者,维护者则需要添加给远程仓库,并将远程分支合并到本地。

比如Jessica告知项目维护者,自己仓库的ruby-client分支上,开发了一个新功能,维护者需添加该远程分支,合并到本地,进行测试,

$ git remote add jessica git://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client

之后Jessica又告知维护者,在另一个远程分支上,又开发了一个新功能,这时维护者只需使用fetch和checkout,因为Jessica的远程仓库已经添加,如果团队成员很少,上述方法可行,如果开发成员很多,每次贡献只有一个补丁,采用邮件接纳的方式,远比每个成员都创建和维护自己的服务器,更加节省时间,维护者不会希望添加数百个远程仓库,而每个成员的贡献只有一两个,当然利用脚本和线上服务器,这类问题可以简化,这取决于团队规模和开发模式。

使用远程分支的另一个好处,能得到一个干净的提交历史,虽然合并问题不可避免,但是维护者可获知提交的位置,这有利于三方合并,而不必使用-3选项,去祈祷补丁能够生成一个提交,如果是临时成员,维护者不必保存该成员的远程仓库,可需使用git pull,实现单次获取,

$ git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
* branch HEAD -> FETCH_HEAD
Merge made by the 'recursive' strategy.

查看分支的差异

项目维护者通常会创建一条特性分支,用于接纳贡献者的工作成果,首先维护者需要了解特性分支的所有提交中,哪些未合并到master分支,可使用–not选项,当然也可使用master…contrib格式,查看两条分支的差异,比如贡献者发送了两个补丁,维护者创建了一条contrib分支,用于合并贡献者的提交补丁,

$ git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon  <schacon@gmail.com>
Date:   Fri Oct 24 09:53:59 2008 -0700seeing if this helps the gemcommit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon  <schacon@gmail.com>
Date:   Mon Oct 22 19:38:36 2008 -0700updated the gemspec to hopefully work better

查看每个提交的修改内容,可在git log命令中,附带-p选项,如果需查看两个分支的完整差异,可使用,

$ git diff master

上述命令的输出结果,可能会让人费解,如果master分支在前(更新),contrib分支在后(更旧),将产生一个奇怪的结果,因为Git会直接比较两个分支的最新提交,比如在master分支的最新提交中,a文件添加了一行文本,而Git直接比较的结果是,在contrib分支中,a文件被删去一行文本,这显然不是真实情况。

如果master分支是contrib分支的直接祖先,则不存在上述问题,一旦两条分支产生分叉点,差异比较的结果则是,contrib分支中添加的所有内容,将等同于在master分支中全部被删除,

如果需查看contrib分支的实际修改,即contrib分支合并到master分支,所引入的变更,则应当比较,contrib分支的最新提交,与contrib分支和master分支的公共祖先,因此必须首先找到公共祖先,

$ git merge-base contrib master 
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649 
$ git diff 36c7db

这非常复杂,因此Git提供了一种更简洁的语法,即三点(…)格式,只需在diff命令中,在两个分支名之间,加入三点符号,即可显示公共祖先之后,后续分支(contrib)产生的所有变化。

$ git diff master...contrib

贡献集成

当维护者准备好,将特性分支集成到主线分支时,该如何进行?项目维护者进行项目维护时,该如何选择工作流?以下提供了一些方案,

合并工作流

最简单的工作流,将所有工作结果,都合并到master分支,如果master分支只保存稳定代码,那么维护者只能在特性分支中,完成新功能的开发,或者是验证贡献者的提交,之后再将特性分支,合并到master分支,再删除已合并的特性分支,并反复迭代该模式。

举例说明,仓库中包含两条分支,ruby_client和php_client,并准备先合并ruby_client,再合并php_client,
在这里插入图片描述
在这里插入图片描述
虽然上述工作流很简单,但依然存在出错的风险,尤其是处理大型(稳定占优)项目时,项目维护者必须准确了解,合并引入了哪些变化。因此在一些重要项目中,项目维护者通常会使用两段式合并方案,这时会存在两个长期分支master和develop,只有当一个稳定的版本出现时,才会更新master分支,而所有的新代码将集成到develop分支,同时维护者还需要定期,将这两条分支推送到公共仓库,下图是一条特性分支合并到develop分支的前后对比,
在这里插入图片描述
在这里插入图片描述
当维护者发布一个新版本之后,master分支可快进到develop分支,如下图,
在这里插入图片描述
这时项目用户完成仓库克隆后,既可以切换到master分支,构建项目的最新版本,也可切换到develop分支,尝试最新的功能,同时维护者还能创建一条integrate分支,用于集成所有的工作成果,当这条分支的代码逐渐稳定并通过测试,则能合并到develop分支,如果这条分支的稳定性已被证实,可直接合并到master分支。

多分支合并的工作流

Git自身项目中,包含了四条长期分支,master,next,pu(建议更新的功能),maint(已发布版本的维护),当贡献者提交工作成果时,将被合并到维护者的特性分支,用于评估安全性和稳定性,或者需要进一步的改进,如果贡献者的提交通过评估,将合并到next分支,这时所有协作者可将其,合并到自己的特性分支,
在这里插入图片描述
如果合并到特性分支的贡献者提交,还需要进一步的改进,将被合并到pu分支,当这些提交稳定后,可再次合并到master分支,这时基于master分支,可重新创建next和pu分支,这意味着master可提供最新的版本,而next会偶尔衍合,pu将频繁衍合,如下图,
在这里插入图片描述
当特性分支最终被合并到master分支后,则可从仓库中,删除该特性分支,另外还有一条maint分支,它基于最新的发布版本,用于提供发布版本的维护补丁,当用户完成Git自身仓库的克隆后,将看到上述四条分支,切换到不同分支,评估当前的开发进度,当然这取决于用户是否需要尝试最新功能,或者需要贡献自己的成功,同时项目维护者还提供一个结构化的工作流程,方便用户查看最新的贡献,应当注意,Git自身项目是一个特例,为了充分理解Git的维护方式,可查阅Git的维护者指南,即git/Documentation/howto/maintain-git.txt文件。

衍合与挑选工作流

有些维护者更喜欢衍合贡献者的提交,合并到master分支,而不是简单合并,这可获得一个更线性的提交历史,当维护者在特性分支上,合并了贡献者的提交后,可切换到master(或develop等)分支,衍合特性分支。

另一个引入贡献者提交的方式,即挑选,Git的挑选类似于单个提交的衍合,它将单个提交补丁重新集成到当前分支,如果在特性分支中,包含大量的贡献者提交,而维护者只想集成一部分贡献者提交时,可使用挑选,即使特性分支中,只有一个贡献者提交,维护者也可使用挑选,如下,
在这里插入图片描述
如果维护者只需将e43a6提交,引入master分支,可运行

$ git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdf
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index fails."3 files changed, 17 insertions(+), 3 deletions(-)

这时e43a6提交已引入master分支,并获得了一个新提交,由于提交日期不同,新提交具有一个不同的校验值,
在这里插入图片描述
这时维护者可删除特性分支,以及不想保留的剩余提交,

rerere命令

如果用户需要进行大量的合并和衍合,或是维护一个长期的特性分支,Git提供了一个辅助功能rerere,可解释为记录重用,等同于手工解决冲突的简化方法,当rerere配置为有效,Git可将每次合并的前后记录(实际为用户手动处理冲突的记录),保存成一个记录集合,当后续合并出现一个冲突时,将查找这个记录集合,如果找到与当前冲突匹配的记录,可自动修复冲突,避免对用户造成困扰。

该功能开启一个配置选项,即rerere.enabled,可放入全局配置中,

$ git config --global rerere.enabled true

配置完成之后,用户手动处理的所有合并冲突,都将被Git保存,以供将来查找,如果有必要,在git rerere运行时,用户可与rerere缓冲实现交互模式,如果独自运行(非交互),Git将检查记录集合(数据库),并尝试查找与当前合并冲突相类似的记录,并尝试自动修复(rerere.enabled必须设为true),同时rerere还包含了一些选项,用于查看冲突记录,移除缓冲的特定记录,清空缓冲等等功能

版本标签

维护者在发布一个新版本之前,可能会为该版本分配一个标签,以便今后重建该版本,以下命令将创建一个标签,-s选项,可用仓库私钥,生成标签的PGP公钥,

$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <schacon@gmail.com>"
1024-bit DSA key, ID F721C45A, created 2009-02-09

创建标签后,将遇到另一个问题,如何通告标签的PGP公钥,项目维护者的解决方法,再创建一个公钥标签,指向标签的PGP公钥,运行gpg --list-keys,可显示仓库中保存的PGP公钥,

$ gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------------
pub 1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
uid                Scott Chacon <schacon@gmail.com>
sub 2048g/45D02282 2009-02-09 [expires: 2010-02-09]

导出数据库包含的标签公钥数据块,通过管道,传递给git hash-object,解析获得标签公钥的SHA-1校验码,

$ gpg -a --export F721C45A | git hash-object -w --stdin
659ef797d181633c87ec71ac3f9ba29fe5775b92

获得校验码之后,创建一个公钥标签,指向标签公钥的校验码,

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92

运行git push --tags,将公钥标签maintainer-pgp-pub共享给所有人,如果需要验证公钥标签,可从数据库中直接获取,这等同于导出标签的PGP公钥数据块,再导入GPG进行验证,

$ git show maintainer-pgp-pub | gpg --import

因此使用公钥标签,可验证仓库包含的标签,如果标签信息中包含了验证指令,可运行git show <标签名>,以使用户获得标签验证的相关指令,也可使用git tag -v <标签名>,直接验证已生成PGP公钥的标签。

版本号

由于Git在每次提交中,并不会自动提供一个累计序号,如果提交需要一个易于理解的名字,可运行git describe,这时Git将生成一个字符串,其中包含了,离当前提交最近的标签名(v1.6.2-rc1),最近标签之后的提交累计数(20),当前提交的局部校验值(g8c5b85c),g表示Git,

$ git describe master
v1.6.2-rc1-20-g8c5b85c

上述命令还可在快照或版本导出时,提供一个易于理解的名字,使用源码编译生成的Git,使用git --version同样能输出一个版本号,如果提交包含了标签,将会直接输出标签名。

默认情况下,git describe需要附注标签(附带-a或-s选项)的支持,如果使用了简单标签,git describe需附带–tags选项,同时版本号也可传入git checkout和git show命令,虽然版本号的末尾使用了部分SHA-1校验码,但也存在重名的风险,比如Linux内核目前就使用8-10位的校验码,来保证SHA-1值的唯一性,因此早期的git describe输出将全部失效。

归档发布

在发布新版本之前,需创建一个最新快照的文档包,以方便那么未使用Git的用户,如下,

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
$ ls *.tar.gz
v1.6.2-rc1-20-g8c5b85c.tar.gz

普通用户可下载文档包,解压之后,就可得到项目的最新快照,当然也可创建一个zip压缩包,如下,

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip

创建完成文档包之后,则可利用网页和邮件,分享给他人。

获取邮件列表

这时需要一份邮件列表,以保证所有对项目有贡献的普通用户,能够获知项目的最新进展,可使用git shortlog,列出上一个版本或上一封邮件之后,项目已加入的所有修改,并给出一个排序列表,其中将列出所有提交的简介,假定上一个版本的版本号为v1.0.1,如下,

$ git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (8):Add support for annotated tags to Grit::TagAdd packed-refs annotated tag support.Add Grit::Commit#to_patchUpdate version and History.txtRemove stray `puts`Make ls_tree ignore nilsTom Preston-Werner (4):fix dates in historydynamic version methodVersion bump to 1.0.2Regenerated gemspec for version 1.0.2

以上输出显示了v1.0.1版本之后的所有提交,并依据作者名进行了分组,维护者可将这些作者,加入到自己的邮件列表中。

这篇关于Git正解 脱水版 【5. 分布式Git】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/325624

相关文章

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

git ssh key相关

step1、进入.ssh文件夹   (windows下 下载git客户端)   cd ~/.ssh(windows mkdir ~/.ssh) step2、配置name和email git config --global user.name "你的名称"git config --global user.email "你的邮箱" step3、生成key ssh-keygen

查看提交历史 —— Git 学习笔记 11

查看提交历史 查看提交历史 不带任何选项的git log-p选项--stat 选项--pretty=oneline选项--pretty=format选项git log常用选项列表参考资料 在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的 工具是 git log 命令。 接下来的例子会用一个用于演示的 simplegit

记录每次更新到仓库 —— Git 学习笔记 10

记录每次更新到仓库 文章目录 文件的状态三个区域检查当前文件状态跟踪新文件取消跟踪(un-tracking)文件重新跟踪(re-tracking)文件暂存已修改文件忽略某些文件查看已暂存和未暂存的修改提交更新跳过暂存区删除文件移动文件参考资料 咱们接着很多天以前的 取得Git仓库 这篇文章继续说。 文件的状态 不管是通过哪种方法,现在我们已经有了一个仓库,并从这个仓

忽略某些文件 —— Git 学习笔记 05

忽略某些文件 忽略某些文件 通过.gitignore文件其他规则源如何选择规则源参考资料 对于某些文件,我们不希望把它们纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常它们都是些自动生成的文件,比如日志文件、编译过程中创建的临时文件等。 通过.gitignore文件 假设我们要忽略 lib.a 文件,那我们可以在 lib.a 所在目录下创建一个名为 .gi

取得 Git 仓库 —— Git 学习笔记 04

取得 Git 仓库 —— Git 学习笔记 04 我认为, Git 的学习分为两大块:一是工作区、索引、本地版本库之间的交互;二是本地版本库和远程版本库之间的交互。第一块是基础,第二块是难点。 下面,我们就围绕着第一部分内容来学习,先不考虑远程仓库,只考虑本地仓库。 怎样取得项目的 Git 仓库? 有两种取得 Git 项目仓库的方法。第一种是在本地创建一个新的仓库,第二种是把其他地方的某个

Git 的特点—— Git 学习笔记 02

文章目录 Git 简史Git 的特点直接记录快照,而非差异比较近乎所有操作都是本地执行保证完整性一般只添加数据 参考资料 Git 简史 众所周知,Linux 内核开源项目有着为数众多的参与者。这么多人在世界各地为 Linux 编写代码,那Linux 的代码是如何管理的呢?事实是在 2002 年以前,世界各地的开发者把源代码通过 diff 的方式发给 Linus,然后由 Linus

集中式版本控制与分布式版本控制——Git 学习笔记01

什么是版本控制 如果你用 Microsoft Word 写过东西,那你八成会有这样的经历: 想删除一段文字,又怕将来这段文字有用,怎么办呢?有一个办法,先把当前文件“另存为”一个文件,然后继续改,改到某个程度,再“另存为”一个文件。就这样改着、存着……最后你的 Word 文档变成了这样: 过了几天,你想找回被删除的文字,但是已经记不清保存在哪个文件了,只能挨个去找。真麻烦,眼睛都花了。看

开源分布式数据库中间件

转自:https://www.csdn.net/article/2015-07-16/2825228 MyCat:开源分布式数据库中间件 为什么需要MyCat? 虽然云计算时代,传统数据库存在着先天性的弊端,但是NoSQL数据库又无法将其替代。如果传统数据易于扩展,可切分,就可以避免单机(单库)的性能缺陷。 MyCat的目标就是:低成本地将现有的单机数据库和应用平滑迁移到“云”端

laravel框架实现redis分布式集群原理

在app/config/database.php中配置如下: 'redis' => array('cluster' => true,'default' => array('host' => '172.21.107.247','port' => 6379,),'redis1' => array('host' => '172.21.107.248','port' => 6379,),) 其中cl