-
1. 起步
-
2. Git 基础
-
3. Git 分支
-
4. 服务器上的 Git
- 4.1 协议
- 4.2 在服务器上部署 Git
- 4.3 生成 SSH 公钥
- 4.4 架设服务器
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管服务
- 4.10 小结
-
5. 分布式 Git
-
A1. 附录 A: Git 在其他环境
- A1.1 图形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小结
-
A2. 附录 B: 在应用程序中嵌入 Git
-
A3. 附录 C: Git 命令
5.2 分布式 Git - 参与项目
参与项目
描述如何参与项目的主要困难在于其多种多样的变体。由于 Git 非常灵活,人们可以并且确实以多种方式协作,因此很难描述你“应该”如何参与——每个项目都略有不同。涉及的一些变量包括活跃贡献者数量、选择的工作流、你的提交访问权限以及可能的外部贡献方法。
第一个变量是活跃贡献者数量——有多少用户正在积极地为这个项目贡献代码,以及频率如何?在许多情况下,你会遇到两三个开发者,每天提交几次,对于一些不活跃的项目可能更少。对于大型公司或项目,开发者数量可能达到数千人,每天有数百甚至数千次提交。这很重要,因为随着开发者数量的增加,你会在确保代码干净应用或易于合并方面遇到更多问题。在你工作期间或你的更改等待批准或应用期间,提交的更改可能会因为合并的工作而变得过时或严重破坏。你如何保持代码持续更新和提交有效?
下一个变量是项目中使用的工作流。它是集中的吗,每个开发者都拥有对主代码行的同等写入权限?项目是否有维护者或集成管理员来检查所有补丁?所有补丁都经过同行评审和批准吗?你是否参与了该过程?是否存在一个副手系统,你是否必须首先向他们提交工作?
下一个变量是你的提交访问权限。如果你拥有项目的写入权限,贡献项目所需的工作流与你没有写入权限时大不相同。如果你没有写入权限,项目倾向于如何接受贡献的工作?它是否有政策?你一次贡献多少工作?你多久贡献一次?
所有这些问题都会影响你如何有效地参与项目以及哪些工作流是首选或可供你使用的。我们将在一系列从简单到复杂的用例中涵盖这些方面的各个方面;你应该能够从这些示例中构建出实际所需的特定工作流。
提交指南
在我们开始研究具体的用例之前,这里有一个关于提交信息的快速说明。为创建提交制定良好的指南并坚持下去,会使使用 Git 和与他人协作变得容易得多。Git 项目提供了一个文档,其中列出了许多创建提交以提交补丁的好技巧——你可以在 Git 源代码的 Documentation/SubmittingPatches 文件中阅读它。
首先,你的提交不应包含任何空白错误。Git 提供了一种简单的方法来检查这一点——在提交之前,运行 git diff --check,它会识别可能的空白错误并为你列出它们。
git diff --check 的输出如果你在提交之前运行该命令,就可以知道你是否即将提交可能会让其他开发者烦恼的空白问题。
接下来,尝试让每个提交成为一个逻辑上独立的变更集。如果可以的话,尝试让你的更改易于消化——不要在周末花一个周末在五个不同的问题上编码,然后在周一将它们全部作为一个巨大的提交提交。即使你在周末不提交,也在周一使用暂存区将你的工作至少拆分为每个问题一个提交,每个提交都有一个有用的信息。如果某些更改修改了同一个文件,尝试使用 git add --patch 来部分暂存文件(在交互式暂存中详细介绍)。只要所有更改都在某个时候添加,分支顶部的项目快照是相同的,无论你进行一次提交还是五次提交,所以当你的同事开发者必须审查你的更改时,尽量让他们更容易。
这种方法还使得你以后需要时,更容易抽出或回滚其中一个变更集。重写历史描述了许多有用的 Git 技巧,用于重写历史和交互式暂存文件——在将工作发送给其他人之前,使用这些工具来帮助创建清晰易懂的历史。
最后要记住的是提交信息。养成创建高质量提交信息的习惯,会使使用 Git 和与 Git 协作变得容易得多。通常,你的信息应该以一行开头,不超过大约 50 个字符,并简洁地描述变更集,然后是一个空行,然后是更详细的解释。Git 项目要求更详细的解释包括你进行更改的动机,并将其实现与之前的行为进行对比——这是一个很好的遵循指南。以祈使句编写提交信息:“Fix bug”而不是“Fixed bug”或“Fixes bug”。这里有一个你可以遵循的模板,我们对它进行了一些轻微改编,最初由 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,看看一个格式良好的项目提交历史是什么样子。
|
注意
|
言传身教。
为了简洁起见,本书中的许多示例都没有像这样格式良好的提交信息;相反,我们只是使用 简而言之,言传身教。 |
小型私有团队
你最可能遇到的最简单的设置是一个拥有两到三个开发者的私有项目。“私有”在这个上下文中意味着闭源——外部世界无法访问。你和其他开发者都拥有对仓库的推送权限。
在这种环境下,你可以遵循类似于使用 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 可以将他获取的 Jessica 的工作合并到他自己的本地工作中
$ git merge origin/master
Merge made by the 'recursive' strategy.
TODO | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
只要本地合并顺利进行,John 更新后的历史将如下所示
origin/master 之后此时,John 可能想测试这段新代码,以确保 Jessica 的工作不会影响他的任何代码,只要一切看起来正常,他就可以最终将新合并的工作推送到服务器
$ git push origin master
...
To john@githost:simplegit.git
fbff5bc..72bbc59 master -> master
最后,John 的提交历史将如下所示
origin 服务器后的历史记录与此同时,Jessica 创建了一个名为 issue54 的新主题分支,并对该分支进行了三次提交。她还没有获取 John 的更改,所以她的提交历史看起来像这样
突然,Jessica 得知 John 已将一些新工作推送到服务器,她想查看一下,因此她可以从服务器获取她还没有的所有新内容
# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
fbff5bc..72bbc59 master -> origin/master
这会拉下 John 在此期间推送的工作。Jessica 的历史现在看起来像这样
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 -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/master 或 issue54——它们都位于上游,因此顺序无关紧要。无论她选择哪种顺序,最终快照都应该相同;只有历史会不同。她选择首先合并 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 的 master 分支可以访问 origin/master,因此她应该能够成功推送(假设 John 在此期间没有推送更多更改)
$ git push origin master
...
To jessica@githost:simplegit.git
72bbc59..8059c15 master -> master
每个开发者都提交了几次,并成功合并了彼此的工作。
这是最简单的工作流之一。你工作一段时间(通常在主题分支中),并在准备好集成时将该工作合并到你的 master 分支中。当你想要共享该工作时,如果 origin/master 已更改,你将从 origin/master 中获取并合并你的 master,最后推送到服务器上的 master 分支。一般的顺序是这样的
私有受管团队
在下一个场景中,你将看到一个大型私有组中的贡献者角色。你将学习如何在小团队协作开发功能的环境中工作,然后这些基于团队的贡献由另一方集成。
假设 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
Jessica 给 John 发邮件,告诉他她已经将一些工作推送到了一个名为 featureA 的分支中,他现在可以查看了。在等待 John 反馈的同时,Jessica 决定开始与 Josie 一起开发 featureB。首先,她创建一个新的功能分支,以服务器的 master 分支为基础
# 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 '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 的仓库现在看起来像这样
她准备好推送她的工作,但收到 Josie 的一封电子邮件,告知服务器上已经推送了一个带有初始“featureB”工作的分支,名为 featureBee 分支。Jessica 需要将这些更改与她自己的更改合并,然后才能将她的工作推送到服务器。Jessica 首先使用 git fetch 获取 Josie 的更改
$ git fetch origin
...
From jessica@githost:simplegit
* [new branch] featureBee -> origin/featureBee
假设 Jessica 仍在她的已检出 featureB 分支上,她现在可以使用 git merge 将 Josie 的工作合并到该分支中
$ 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”工作推回到服务器,但她不想简单地推送她自己的 featureB 分支。相反,由于 Josie 已经启动了一个上游 featureBee 分支,Jessica 希望推送到*该*分支,她使用以下命令完成
$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
fba9af8..cd685d1 featureB -> featureBee
这被称为*引用规范*。有关 Git 引用规范以及你可以用它们做的不同事情的更详细讨论,请参阅引用规范。另请注意 -u 标志;这是 --set-upstream 的缩写,它配置分支以便将来更轻松地推送和拉取。
突然,Jessica 接到 John 的邮件,他告诉她,他已经向他们正在协作的 featureA 分支推送了一些更改,他请 Jessica 查看一下。Jessica 再次运行一个简单的 git fetch 来获取服务器上的*所有*新内容,包括(当然)John 的最新工作
$ git fetch origin
...
From jessica@githost:simplegit
3300904..aad881d featureA -> origin/featureA
Jessica 可以通过比较新获取的 featureA 分支的内容与她本地的相同分支的副本来显示 John 新工作的日志
$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date: Fri May 29 19:57:33 2009 -0700
Increase log output to 30 from 25
如果 Jessica 对她所看到的一切感到满意,她可以将 John 的新工作合并到她的本地 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(-)
最后,Jessica 可能想对所有这些合并的内容进行一些微小的更改,所以她可以自由地进行这些更改,将它们提交到她的本地 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 的提交历史现在看起来像这样
在某个时候,Jessica、Josie 和 John 通知集成者,服务器上的 featureA 和 featureBee 分支已准备好集成到主线中。在集成者将这些分支合并到主线后,一次 fetch 将拉下新的合并提交,使历史记录看起来像这样
许多团队转向 Git,正是因为其能够让多个团队并行工作,并在流程后期合并不同的工作线。团队中较小的子组能够通过远程分支协作,而无需必然涉及或阻碍整个团队,这是 Git 的巨大优势。你在这里看到的工作流的序列大致如下
分叉的公共项目
参与公共项目略有不同。因为你没有直接更新项目分支的权限,所以你必须通过其他方式将工作提交给维护者。第一个示例描述了通过支持轻松分叉的 Git 主机进行贡献。许多托管站点都支持此功能(包括 GitHub、BitBucket、repo.or.cz 等),并且许多项目维护者都期望这种贡献方式。下一节将讨论那些倾向于通过电子邮件接受贡献补丁的项目。
首先,你可能需要克隆主仓库,为你要贡献的补丁或补丁系列创建一个主题分支,并在那里完成你的工作。序列基本上如下所示
$ git clone <url>
$ cd project
$ git checkout -b featureA
... work ...
$ git commit
... work ...
$ git commit
|
注意
|
你可能希望使用 |
当你的分支工作完成并且你准备好将其贡献给维护者时,请转到原始项目页面并单击“Fork”按钮,创建你自己的可写入的项目分叉。然后你需要将此仓库 URL 添加为你本地仓库的新远程;在这个例子中,我们称之为 myfork
$ git remote add myfork <url>
然后你需要将你的新工作推送到这个仓库。最简单的方法是将你正在处理的主题分支推送到你的分叉仓库,而不是将该工作合并到你的 master 分支并推送。原因是,如果你的工作未被接受或被挑选,你无需回溯你的 master 分支(Git 的 cherry-pick 操作将在变基和挑选工作流中详细介绍)。如果维护者 merge、rebase 或 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
现在,你的每个主题都被包含在一个筒仓中——类似于补丁队列——你可以重写、变基和修改它们,而不会让主题相互干扰或相互依赖,就像这样
featureB 工作的初始提交历史假设项目维护者已经拉入了一堆其他补丁,并尝试了你的第一个分支,但它不再干净地合并。在这种情况下,你可以尝试在 origin/master 之上重新定位该分支,为维护者解决冲突,然后重新提交你的更改
$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA
这将重写你的历史记录,使其现在看起来像featureA 工作后的提交历史。
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 分支中找到这些更改。
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 <jessica@example.com>
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 = user@gmail.com
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 = user@gmail.com
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 <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] Add 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
|
提示
|
有关配置系统和电子邮件的帮助、更多提示和技巧,以及通过电子邮件发送试用补丁的沙盒,请访问 git-send-email.io。 |
总结
在本节中,我们涵盖了多种工作流,并讨论了作为小型团队成员在闭源项目上工作与为大型公共项目做贡献之间的差异。你知道在提交之前检查空格错误,并且可以编写出色的提交信息。你学习了如何格式化补丁,以及如何将它们通过电子邮件发送给开发者邮件列表。在不同工作流的上下文中,合并的处理也得到了介绍。你现在已充分准备好在任何项目上协作。
接下来,你将看到如何处理事情的另一面:维护一个 Git 项目。你将学习如何成为一名仁慈的独裁者或集成管理员。