Git
章节 ▾ 第二版

5.2 分布式 Git - 为项目做出贡献

为项目做出贡献

描述如何为项目做出贡献的主要困难在于有许多不同的做法。由于 Git 非常灵活,人们可以而且确实以多种方式进行协作,描述你应该如何做出贡献是有问题的——每个项目都有些不同。所涉及的一些变量包括活跃贡献者数量、选择的工作流、你的提交权限,以及可能的外部贡献方法。

第一个变量是活跃贡献者数量——有多少用户正在积极地为这个项目贡献代码,以及他们贡献的频率如何?在很多情况下,你会有两到三个开发者每天提交几次代码,对于一些不太活跃的项目,这个数字可能会更少。对于大型公司或项目,开发者的数量可能达到数千,每天会有数百或数千次提交。这很重要,因为随着开发者的增多,你将面临更多的问题,以确保你的代码能够干净地应用或能够轻松地合并。在你工作时或在你等待更改获得批准或应用时,合并的工作可能会使你提交的更改过时或严重损坏。你如何才能让你的代码始终保持最新,并且你的提交保持有效?

下一个变量是项目中使用的工作流。它是否集中化,每个开发者对主代码行拥有相等的写访问权限?该项目是否有一个维护者或集成管理器来检查所有补丁?所有补丁是否都经过同行评审和批准?你是否参与了这个过程?是否有一个中尉系统,你是否必须首先向他们提交你的工作?

下一个变量是你的提交访问权限。如果你对项目有写访问权限,那么为项目做出贡献所需的工作流与你没有写访问权限时大不相同。如果你没有写访问权限,项目更喜欢如何接受贡献的工作?它甚至有政策吗?你一次贡献多少工作?你贡献的频率如何?

所有这些问题都会影响你如何有效地为项目做出贡献,以及哪些工作流是你首选或可用的。我们将在一系列用例中涵盖其中每个方面的各个方面,从简单到复杂;你应该能够根据这些示例构建你在实践中需要的具体工作流。

提交指南

在我们开始查看具体用例之前,这里有一个关于提交消息的快速说明。有一个好的创建提交的指南并坚持它,这使得使用 Git 和与他人协作变得更加容易。Git 项目提供了一份文档,其中列出了许多从提交中提交补丁的良好提示——你可以在 Git 源代码中的 Documentation/SubmittingPatches 文件中阅读它。

首先,提交内容中不应包含任何空格错误。Git 提供了一种简单的方法来检查这一点——在提交前,运行 git diff --check,它会识别可能的空格错误并将其列出。

Output of `git diff --check`
图 56. git diff --check 的输出

在提交前运行该命令,你可以判断自己是否即将提交可能让其他开发者感到困扰的空格问题。

接下来,尝试让每个提交成为一个逻辑上独立的变更集。如果可以,请尝试让你的变更易于理解——不要在整个周末为五个不同的问题编写代码,然后在周一将它们全部提交为一个巨大的提交。即使你没有在周末提交,也要在周一使用暂存区域将你的工作拆分为每个问题至少一个提交,并为每个提交提供有用的消息。如果某些变更修改了同一个文件,请尝试使用 git add --patch 来部分暂存文件(在 交互式暂存 中有详细介绍)。分支顶部的项目快照是相同的,无论你执行一次提交还是五次提交,只要所有变更都在某个时间点添加,因此当其他开发者必须审查你的变更时,请尝试让他们更容易一些。

此方法还让你在需要时更容易提取或还原其中一个变更集。重写历史 介绍了一些有用的 Git 技巧,用于重写历史和交互式暂存文件——在将工作发送给其他人之前,使用这些工具来帮助构建一个干净且易于理解的历史记录。

需要记住的最后一件事是提交消息。养成创建高质量提交消息的习惯会让使用 Git 和与他人协作变得容易得多。一般来说,你的消息应以不超过 50 个字符的单行开头,并简洁地描述变更集,然后空一行,然后是更详细的说明。Git 项目要求更详细的说明包括你变更的动机,并将其实现与之前的行为进行对比——这是一个很好的准则。以命令式写你的提交消息:“修复错误”,而不是“已修复错误”或“修复错误”。这里有一个你可以遵循的模板,我们稍作改编,源自 Tim Pope 最初编写的一个

Capitalized, short (50 chars or less) summary

More detailed explanatory text, if necessary.  Wrap it to about 72
characters or so.  In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body.  The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase will confuse you if you run the
two together.

Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
or "Fixes bug."  This convention matches up with commit messages generated
by commands like git merge and git revert.

Further paragraphs come after blank lines.

- Bullet points are okay, too

- Typically a hyphen or asterisk is used for the bullet, followed by a
  single space, with blank lines in between, but conventions vary here

- Use a hanging indent

如果你的所有提交消息都遵循此模型,对你和你协作的开发者来说,事情会容易得多。Git 项目具有格式良好的提交消息——尝试在那里运行 git log --no-merges,看看格式良好的项目提交历史记录是什么样的。

注意
照我们说的做,不要照我们做的做。

为了简洁起见,本书中的许多示例没有像这样的格式良好的提交消息;相反,我们只使用 -m 选项来 git commit

简而言之,照我们说的做,别照我们做的做。

私人小团队

你可能遇到的最简单的设置是与一或两个其他开发人员进行的私有项目。在此上下文中,“私有”意味着闭源——外部世界无法访问。你和其他开发人员都具有对存储库的推送访问权限。

在此环境中,你可以遵循类似于使用 Subversion 或其他集中式系统时可能执行的工作流。你仍然可以获得离线提交和更简单的分支和合并等优势,但工作流可以非常相似;主要区别在于合并发生在客户端,而不是在提交时发生在服务器上。让我们看看当两个开发人员开始使用共享存储库一起工作时会是什么样子。第一个开发人员 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

以上输出的最后一行显示了推送操作的有用返回消息。基本格式为 <oldref>..<newref> fromref → toref,其中 oldref 表示旧引用,newref 表示新引用,fromref 是正在推送的本地引用的名称,toref 是正在更新的远程引用的名称。你将在下面的讨论中看到类似的输出,因此对含义有一个基本了解将有助于理解存储库的各种状态。有关 git-push 的详细信息,请参阅文档。

继续这个示例,不久之后,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 的推送失败,原因是 Jessica 之前推送了她的更改。如果你习惯于 Subversion,这一点尤其重要,因为你会注意到两个开发人员没有编辑同一个文件。尽管 Subversion 在编辑不同文件时会在服务器上自动执行此类合并,但使用 Git 时,你必须首先在本地合并提交。换句话说,在允许 John 推送之前,他必须首先获取 Jessica 的上游更改并将其合并到他的本地存储库中。

作为第一步,John 获取 Jessica 的工作(这仅获取 Jessica 的上游工作,尚未将其合并到 John 的工作中)

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

此时,John 的本地存储库看起来像这样

John’s divergent history
图 57. John 的分歧历史

现在,John 可以将 Jessica 获取的工作合并到他自己的本地工作中

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

只要该本地合并顺利进行,John 的更新历史现在将如下所示

John’s repository after merging `origin/master`
图 58. John 在合并 origin/master 后的存储库

此时,John 可能想要测试此新代码以确保 Jessica 的工作不会影响他的任何工作,并且只要一切都很好,他最终可以将新合并的工作推送到服务器

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

最终,John 的提交历史将如下所示

John’s history after pushing to the `origin` server
图 59. John 将内容推送到 origin 服务器后的历史记录

与此同时,Jessica 创建了一个名为 issue54 的新主题分支,并对该分支进行了三次提交。她还没有获取 John 的更改,因此她的提交历史如下所示

Jessica’s topic branch
图 60. Jessica 的主题分支

突然,Jessica 得知 John 已将一些新工作推送到服务器,她想查看一下,以便可以使用以下命令从服务器获取她尚未拥有所有新内容

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

这会提取 John 在此期间推上来的工作。Jessica 的历史现在如下所示

Jessica’s history after fetching John’s changes
图 61. Jessica 获取 John 的更改后的历史记录

Jessica 认为她的主题分支已准备就绪,但她想知道她必须将 John 获取的工作的哪一部分合并到她的工作中才能推送。她运行 git log 以找出答案

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <[email protected]>
Date:   Fri May 29 16:01:27 2009 -0700

   Remove invalid default value

issue54..origin/master 语法是一个日志过滤器,要求 Git 仅显示后一个分支(在本例中为 origin/master)上而不在第一个分支(在本例中为 issue54)上的那些提交。我们将在 提交范围 中详细介绍此语法。

从以上输出中,我们可以看到 John 进行了一次提交,而 Jessica 尚未将其合并到她的本地工作中。如果她合并 origin/master,那就是将修改其本地工作的单次提交。

现在,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/masterissue54——它们都是上游,因此顺序无关紧要。无论她选择哪种顺序,最终快照都应相同;只有历史记录会不同。她选择首先合并 issue54 分支

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    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 的历史记录如下所示

Jessica’s history after merging John’s changes
图 62.合并 John 的更改后 Jessica 的历史记录

现在,可以从 Jessica 的 master 分支访问 origin/master,因此她应该能够成功推送(假设 John 在此期间没有推送更多更改)

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

每个开发人员都已提交多次并成功合并了彼此的工作。

Jessica’s history after pushing all changes back to the server
图 63.将所有更改推回服务器后 Jessica 的历史记录

这是最简单的几个工作流之一。你工作一段时间(通常在主题分支中),并在准备集成时将其合并到 master 分支中。当你想要共享该工作时,如果 origin/master 已更改,则从 origin/master 获取并合并你的 master,最后推送到服务器上的 master 分支。一般顺序如下所示

General sequence of events for a simple multiple-developer Git workflow
图 64.简单多开发人员 Git 工作流的事件一般顺序

私有托管团队

在下一个场景中,你将了解大型私有组中的贡献者角色。你将学习如何在小团队协作处理功能的环境中工作,之后这些基于团队的贡献由另一方集成。

假设 John 和 Jessica 正在共同处理一项功能(称为“featureA”),而 Jessica 和第三位开发人员 Josie 正在处理第二项功能(例如,“featureB”)。在这种情况下,公司正在使用一种集成管理器工作流,其中仅由特定工程师集成各个组的工作,并且主存储库的 master 分支只能由这些工程师更新。在此场景中,所有工作都在基于团队的分支中完成,然后由集成商汇总。

让我们跟踪 Jessica 在此环境中与两位不同的开发人员并行协作处理她的两个功能时的工作流。假设她已经克隆了自己的存储库,她决定首先处理 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 function
 1 files changed, 1 insertions(+), 1 deletions(-)

此时,她需要与 John 共享她的工作,因此她将她的 featureA 分支提交推送到服务器。Jessica 无权将 master 分支推送到服务器,只有集成商才有此权限,因此她必须推送到另一个分支才能与 John 协作

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

杰西卡给约翰发电子邮件,告诉他她已将一些工作推送到名为 featureA 的分支,他现在可以查看了。在等待约翰的反馈时,杰西卡决定开始与乔西一起处理 featureB。首先,她基于服务器的 master 分支启动一个新的功能分支

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

现在,杰西卡在 featureB 分支上提交了几次

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

杰西卡的存储库现在看起来像这样

Jessica’s initial commit history
图 65. 杰西卡的初始提交历史记录

她已准备好推送她的工作,但收到乔西的一封电子邮件,其中提到一个包含一些初始“featureB”工作的分支已作为 featureBee 分支推送到服务器。在将自己的工作推送到服务器之前,杰西卡需要将这些更改与自己的更改合并。杰西卡首先使用 git fetch 获取乔西的更改

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

假设杰西卡仍处于签出的 featureB 分支上,她现在可以使用 git merge 将乔西的工作合并到该分支

$ 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(-)

此时,杰西卡希望将所有已合并的“featureB”工作推回服务器,但她不想仅仅推送自己的 featureB 分支。相反,由于乔西已经启动了一个上游 featureBee 分支,杰西卡希望推送到分支,她使用以下命令执行此操作

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

这称为引用规范。有关 Git 引用规范以及你可以使用它们执行的不同操作的更详细讨论,请参阅 引用规范。还要注意 -u 标志;这是 --set-upstream 的缩写,它配置分支以便以后更轻松地推送和拉取。

突然,杰西卡收到约翰的电子邮件,约翰告诉她他在他们正在协作的 featureA 分支上推送了一些更改,并要求杰西卡查看这些更改。杰西卡再次运行简单的 git fetch 以从服务器获取所有新内容,当然包括约翰的最新工作

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

杰西卡可以通过比较新获取的 featureA 分支的内容与其同一分支的本地副本,来显示约翰的新工作的日志

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <[email protected]>
Date:   Fri May 29 19:57:33 2009 -0700

    Increase log output to 30 from 25

如果杰西卡喜欢她看到的内容,她可以使用以下命令将约翰的新工作合并到其本地 featureA 分支

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

最后,杰西卡可能希望对所有已合并的内容进行一些小的更改,因此她可以自由地进行这些更改,将它们提交到其本地 featureA 分支,并将最终结果推回服务器

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

杰西卡的提交历史记录现在看起来像这样

Jessica’s history after committing on a feature branch
图 66. 杰西卡在功能分支上提交后的历史记录

在某个时间点,Jessica、Josie 和 John 通知集成器服务器上的 featureAfeatureBee 分支已准备好集成到主线中。在集成器将这些分支合并到主线后,获取将拉取新的合并提交,使历史记录如下所示

Jessica’s history after merging both her topic branches
图 67.合并两个主题分支后的 Jessica 的历史记录

许多组切换到 Git 是因为这种让多个团队并行工作的能力,在进程后期合并不同的工作线。较小的团队子组能够通过远程分支进行协作,而无需让整个团队参与或阻碍,这是 Git 的一大好处。您在此处看到的此工作流的顺序类似于以下内容

Basic sequence of this managed-team workflow
图 68.此受管理团队工作流的基本顺序

已分叉的公共项目

对公共项目做出贡献有点不同。因为您没有权限直接更新项目上的分支,所以您必须通过其他方式让维护人员获得这项工作。第一个示例描述了通过在支持轻松分叉的 Git 主机上进行分叉来做出贡献。许多托管网站都支持此功能(包括 GitHub、BitBucket、repo.or.cz 等),许多项目维护人员都希望这种风格的贡献。下一部分将处理更喜欢通过电子邮件接受贡献补丁的项目。

首先,您可能希望克隆主存储库,为计划贡献的补丁或补丁系列创建一个主题分支,并在其中完成您的工作。顺序基本如下所示

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit
注意

您可能希望使用 rebase -i 将您的工作压缩到一个提交,或重新排列提交中的工作,以便维护人员更轻松地审阅补丁 - 有关交互式变基的更多信息,请参阅 重写历史记录

当您的分支工作完成并且您准备将其贡献回维护人员时,请转到原始项目页面并单击“分叉”按钮,创建您自己的可写项目分叉。然后,您需要将此存储库 URL 添加为本地存储库的新远程;在此示例中,我们称之为 myfork

$ git remote add myfork <url>

然后你需要将你的新工作推送到此存储库。最简单的方法是将你正在处理的主题分支推送到你的分叉存储库,而不是将该工作合并到你的master分支并推送该分支。原因是,如果你的工作不被接受或被挑选,你就不必倒带你的master分支(Git cherry-pick 操作在变基和挑选工作流中进行了更详细的介绍)。如果维护者合并变基挑选你的工作,你最终可以通过从他们的存储库中拉取来取回你的工作。

在任何情况下,你都可以通过以下方式推送你的工作

$ git push -u myfork featureA

一旦你的工作被推送到你的存储库分叉中,你需要通知原始项目的维护者你有希望他们合并的工作。这通常称为拉取请求,你通常可以通过网站生成这样的请求 - GitHub 有自己的“拉取请求”机制,我们将在GitHub中介绍 - 或者你可以运行git request-pull命令,并将后续输出手动发送电子邮件给项目维护者。

git request-pull命令获取你希望将主题分支拉取到的基础分支和希望他们从中拉取的 Git 存储库 URL,并生成你要求拉取的所有更改的摘要。例如,如果 Jessica 想向 John 发送拉取请求,并且她在刚刚推出的主题分支上完成了两个提交,她可以运行此命令

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
        Create new function

are available in the git repository at:

  https://githost/simplegit.git featureA

Jessica Smith (2):
      Add limit to log function
      Increase log output to 30 from 25

 lib/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

现在,你的每个主题都包含在一个孤岛中 - 类似于补丁队列 - 你可以重写、变基和修改,而不会让主题相互干扰或相互依赖,如下所示

Initial commit history with `featureB` work
图 69. 带有featureB工作的初始提交历史记录

假设项目维护者已拉取了一堆其他补丁并尝试了你的第一个分支,但它不再干净合并。在这种情况下,你可以尝试在 origin/master 的基础上重新设定该分支,为维护者解决冲突,然后重新提交你的更改

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

这会将你的历史重写为现在看起来像 featureA 工作后的提交历史

Commit history after `featureA` work
图 70. featureA 工作后的提交历史

因为你重新设定了分支,所以你必须在推送命令中指定 -f,才能用一个不是其后代的提交替换服务器上的 featureA 分支。另一种方法是将此新工作推送到服务器上的另一个分支(也许称为 featureAv2)。

我们再来看一个可能的场景:维护者查看了你的第二个分支中的工作,喜欢这个概念,但希望你更改一个实现细节。你还可以借此机会将工作移到基于项目当前 master 分支。你基于当前 origin/master 分支启动一个新分支,在那里压扁 featureB 更改,解决所有冲突,进行实现更改,然后将其推送为一个新分支

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

--squash 选项获取合并分支上的所有工作,并将其压扁到一个变更集中,生成存储库状态,就像发生了真正的合并一样,而不会实际进行合并提交。这意味着你的未来提交将只有一个父级,并允许你引入另一个分支的所有更改,然后在记录新提交之前进行更多更改。此外,--no-commit 选项可用于在默认合并过程中延迟合并提交。

此时,你可以通知维护者你已进行请求的更改,并且他们可以在你的 featureBv2 分支中找到这些更改。

Commit history after `featureBv2` work
图 71. featureBv2 工作后的提交历史

通过电子邮件进行的公共项目

许多项目已建立了接受补丁的程序——你需要查看每个项目的具体规则,因为它们会有所不同。由于有几个较旧、较大的项目通过开发者邮件列表接受补丁,因此我们现在将对此进行一个示例说明。

工作流类似于之前的用例——你为正在处理的每个补丁系列创建主题分支。不同之处在于你如何将它们提交到项目。你不必分叉项目并推送到你自己的可写版本,而是生成每个提交系列的电子邮件版本,并将其通过电子邮件发送到开发者邮件列表

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

现在你有两个提交,你想将其发送到邮件列表。你使用 git format-patch 生成 mbox 格式的文件,你可以通过电子邮件将其发送到列表——它将每个提交转换为一封电子邮件,其中提交消息的第一行作为主题,其余消息加上提交引入的补丁作为正文。这样做的好处是,从使用 format-patch 生成的电子邮件中应用补丁可以正确保留所有提交信息。

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-increase-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 <[email protected]>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] Add limit to log function

Limit 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 SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

您还可以编辑这些补丁文件,为电子邮件列表添加更多信息,而您不希望这些信息显示在提交消息中。如果您在 --- 行和补丁开头(diff --git 行)之间添加文本,则开发人员可以阅读它,但修补过程会忽略该内容。

要通过电子邮件将其发送到邮件列表,您可以将文件粘贴到电子邮件程序中,或通过命令行程序发送。粘贴文本通常会导致格式问题,尤其是在不适当地保留换行符和其他空白字符的“更智能”客户端中。幸运的是,Git 提供了一个工具来帮助您通过 IMAP 发送格式正确的补丁,这可能对您来说更容易。我们将演示如何通过 Gmail 发送补丁,碰巧这是我们最熟悉的电子邮件代理;您可以在 Git 源代码中上述 Documentation/SubmittingPatches 文件的末尾阅读许多邮件程序的详细说明。

首先,您需要在 ~/.gitconfig 文件中设置 imap 部分。您可以使用一系列 git config 命令分别设置每个值,或者可以手动添加它们,但最终您的配置文件应如下所示

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = [email protected]
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

如果您的 IMAP 服务器不使用 SSL,则最后两行可能不是必需的,并且 host 值将是 imap:// 而不是 imaps://。设置好后,您可以使用 git imap-send 将补丁系列放置在指定 IMAP 服务器的草稿文件夹中

$ 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

此时,您应该能够转到草稿文件夹,将收件人字段更改为要向其发送补丁的邮件列表,可能抄送维护人员或负责该部分的人员,然后将其发送出去。

您还可以通过 SMTP 服务器发送补丁。与之前一样,您可以使用一系列 git config 命令分别设置每个值,或者可以在 ~/.gitconfig 文件中的 sendemail 部分中手动添加它们

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = [email protected]
  smtpserverport = 587

完成后,您可以使用 git send-email 发送您的补丁

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

然后,Git 会吐出一堆日志信息,对于您发送的每个补丁,看起来都像这样

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

Result: OK
提示

有关配置系统和电子邮件、更多提示和技巧以及通过电子邮件发送试用补丁的沙盒的帮助,请访问 git-send-email.io

摘要

在本节中,我们介绍了多个工作流,并讨论了作为小团队参与闭源项目与为大型公共项目做贡献之间的差异。您知道在提交之前检查空格错误,并且可以编写出色的提交信息。您学习了如何格式化补丁,并通过电子邮件将它们发送到开发者邮件列表。在不同工作流的上下文中也介绍了处理合并。您现在已经为在任何项目中协作做好了充分的准备。

接下来,您将看到如何处理另一面:维护 Git 项目。您将学习如何成为仁慈的独裁者或集成经理。

scroll-to-top