章节 ▾ 第二版

6.2 GitHub - 参与一个项目

参与一个项目

现在我们的账户已经设置完毕,让我们来了解一些对您参与现有项目有帮助的细节。

Fork 项目

如果您想参与到一个您没有推送权限的现有项目中,您可以“fork”这个项目。 当您“fork”一个项目时,GitHub 会制作一个完全属于您的项目副本;它存在于您的命名空间中,您可以向它推送。

注意

历史上,术语“fork”在上下文中有些负面含义,意味着有人以不同的方向采用了开源项目,有时会创建一个竞争项目并拆分贡献者。 在 GitHub 中,“fork”只是您自己命名空间中的同一个项目,允许您公开更改项目,从而以更开放的方式做出贡献。

这样,项目不必担心添加用户作为协作者来授予他们推送权限。 人们可以 fork 一个项目,向它推送,并通过创建所谓的“拉取请求”将他们的更改贡献回原始仓库,我们将在接下来介绍它。 这会打开一个包含代码审查的讨论主题,所有者和贡献者可以就更改进行交流,直到所有者满意为止,此时所有者可以将其合并。

要 fork 一个项目,请访问项目页面并单击页面右上角的“Fork”按钮。

The “Fork” button
图 88.“Fork”按钮

几秒钟后,您将被带到新的项目页面,其中包含您自己的可写代码副本。

GitHub 工作流程

GitHub 的设计围绕着特定的协作工作流程,该工作流程以拉取请求为中心。 无论您是在单个共享存储库中与紧密联系的团队协作,还是在全球分布的公司或通过数十个 fork 为项目做出贡献的陌生人网络,此流程都有效。 它以主题分支工作流程为中心,该工作流程在Git 分支中介绍。

以下是它通常的工作方式

  1. Fork 项目。

  2. master 创建一个主题分支。

  3. 提交一些提交以改进项目。

  4. 将此分支推送到您的 GitHub 项目。

  5. 在 GitHub 上打开一个拉取请求。

  6. 讨论,并可选择继续提交。

  7. 项目所有者合并或关闭拉取请求。

  8. 将更新后的 master 同步回您的 fork。

这基本上是在集成管理员工作流程中介绍的集成管理器工作流程,但团队不是使用电子邮件进行通信和审查更改,而是使用 GitHub 基于 Web 的工具。

让我们通过一个示例来演示如何使用此流程提出对 GitHub 上托管的开源项目的更改。

提示

您可以使用官方的 GitHub CLI 工具来代替 GitHub 的 Web 界面执行大多数操作。该工具可以在 Windows、macOS 和 Linux 系统上使用。请访问 GitHub CLI 主页获取安装说明和手册。

创建 Pull Request

Tony 正在寻找可以在他的 Arduino 可编程微控制器上运行的代码,并在 GitHub 上找到了一个很棒的程序文件,地址是 https://github.com/schacon/blink

The project we want to contribute to
图 89. 我们想要贡献的项目

唯一的问题是闪烁速率太快了。我们认为在每个状态变化之间等待 3 秒钟而不是 1 秒钟会更好。因此,让我们改进程序并将其作为建议的更改提交回项目。

首先,我们点击之前提到的“Fork”按钮来获取我们自己的项目副本。我们的用户名是“tonychacon”,所以我们项目的副本位于 https://github.com/tonychacon/blink,我们可以在那里编辑它。我们将克隆到本地,创建一个主题分支,进行代码更改,最后将更改推送回 GitHub。

$ git clone https://github.com/tonychacon/blink (1)
Cloning into 'blink'...

$ cd blink
$ git checkout -b slow-blink (2)
Switched to a new branch 'slow-blink'

$ sed -i '' 's/1000/3000/' blink.ino (macOS) (3)
# If you're on a Linux system, do this instead:
# $ sed -i 's/1000/3000/' blink.ino (3)

$ git diff --word-diff (4)
diff --git a/blink.ino b/blink.ino
index 15b9911..a6cc5a5 100644
--- a/blink.ino
+++ b/blink.ino
@@ -18,7 +18,7 @@ void setup() {
// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  [-delay(1000);-]{+delay(3000);+}               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  [-delay(1000);-]{+delay(3000);+}               // wait for a second
}

$ git commit -a -m 'Change delay to 3 seconds' (5)
[slow-blink 5ca509d] Change delay to 3 seconds
 1 file changed, 2 insertions(+), 2 deletions(-)

$ git push origin slow-blink (6)
Username for 'https://github.com': tonychacon
Password for 'https://tonychacon@github.com':
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 340 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To https://github.com/tonychacon/blink
 * [new branch]      slow-blink -> slow-blink
  1. 将我们 fork 的项目克隆到本地。

  2. 创建一个描述性的主题分支。

  3. 对代码进行修改。

  4. 检查修改是否良好。

  5. 将我们的更改提交到主题分支。

  6. 将新的主题分支推送到我们的 GitHub fork。

现在,如果我们回到 GitHub 上的 fork,我们可以看到 GitHub 注意到我们推送了一个新的主题分支,并向我们展示了一个大的绿色按钮,以检查我们的更改并打开一个到原始项目的 Pull Request。

您也可以转到 https://github.com/<user>/<project>/branches 上的“分支”页面,找到您的分支并从那里打开一个新的 Pull Request。

Pull Request button
图 90. Pull Request 按钮

如果我们点击那个绿色按钮,我们将看到一个屏幕,要求我们给我们的 Pull Request 一个标题和描述。花一些精力在这上面几乎总是值得的,因为好的描述可以帮助原始项目的所有者确定您想要做什么,您提出的更改是否正确,以及接受更改是否会改善原始项目。

我们还会看到一个列表,其中包含我们主题分支中“领先”于 master 分支的提交(在这种情况下,只有一个),以及如果项目所有者合并此分支将进行的所有更改的统一差异。

Pull Request creation page
图 91. Pull Request 创建页面

当您在此屏幕上点击“创建 pull request”按钮时,您 fork 的项目的所有者将收到通知,表明有人建议进行更改,并将链接到包含所有信息的页面。

注意

虽然 Pull Request 通常用于像这样的公共项目,当贡献者有一个完整的更改准备好进行时,但它也经常用于内部项目,*在开发周期的开始*。由于即使在 Pull Request 打开**之后**您也可以继续推送到主题分支,因此通常会尽早打开它,并将其用作在上下文中与团队一起迭代工作的方式,而不是在流程的最后才打开。

迭代 Pull Request

此时,项目所有者可以查看建议的更改并合并、拒绝或评论它。假设他喜欢这个想法,但希望灯关闭的时间比打开的时间稍长。

分布式 Git 中介绍的工作流程中,此对话可能会通过电子邮件进行,但在 GitHub 上,此对话在线进行。项目所有者可以查看统一差异,并通过单击任何行来留下评论。

Comment on a specific line of code in a Pull Request
图 92. 在 Pull Request 中评论特定代码行

一旦维护者发表此评论,打开 Pull Request 的人(实际上是任何关注存储库的人)将收到通知。我们稍后将介绍自定义此项,但如果他启用了电子邮件通知,Tony 将收到一封这样的电子邮件

Comments sent as email notifications
图 93. 作为电子邮件通知发送的评论

任何人也可以在 Pull Request 上发表一般评论。在Pull Request 讨论页面中,我们可以看到项目所有者既评论了一行代码,然后在讨论部分留下了一般评论的示例。您可以看到代码注释也被带入了对话中。

Pull Request discussion page
图 94. Pull Request 讨论页面

现在,贡献者可以看到他们需要做什么才能使他们的更改被接受。幸运的是,这非常简单。如果通过电子邮件,您可能必须重新滚动您的系列并将其重新提交到邮件列表,而在 GitHub 上,您只需再次提交到主题分支并推送,这会自动更新 Pull Request。在Pull Request 最终中,您还可以看到旧的代码注释已在更新的 Pull Request 中被折叠,因为它是在已被更改的行上进行的。

向现有 Pull Request 添加提交不会触发通知,因此一旦 Tony 推送了他的更正,他决定发表评论,通知项目所有者他进行了请求的更改。

Pull Request final
图 95. Pull Request 最终

一个有趣的事情要注意的是,如果您单击此 Pull Request 上的“文件更改”选项卡,您将获得“统一”差异 — 也就是说,如果将此主题分支合并到您的主分支中,则会引入的总聚合差异。用 git diff 术语来说,它基本上会自动向您显示基于此 Pull Request 的分支的 git diff master…​<branch>。有关此类差异的更多信息,请参见确定引入了什么

您还会注意到,GitHub 会检查 Pull Request 是否干净地合并,并提供一个按钮来在服务器上为您执行合并。只有当您具有对存储库的写入权限并且可以进行简单的合并时,才会显示此按钮。如果您点击它,GitHub 将执行“非快进”合并,这意味着即使合并**可以**是快进,它仍然会创建一个合并提交。

如果您愿意,您可以简单地将分支拉下来并在本地合并它。如果您将此分支合并到 master 分支并将其推送到 GitHub,则 Pull Request 将自动关闭。

这是大多数 GitHub 项目使用的基本工作流程。创建主题分支,在其上打开 Pull Request,进行讨论,可能在该分支上完成更多工作,最终请求被关闭或合并。

注意
不仅是 Fork

重要的是要注意,您也可以在同一存储库中的两个分支之间打开 Pull Request。如果您正在与某人一起开发某个功能,并且你们都具有对项目的写入权限,则可以将主题分支推送到存储库,并在该同一项目的 master 分支上打开 Pull Request,以启动代码审查和讨论过程。无需 fork。

高级 Pull Request

既然我们已经介绍了在 GitHub 上贡献项目的基础知识,那么让我们介绍一些关于 Pull Request 的有趣技巧,以便您可以更有效地使用它们。

Pull Request 作为补丁

重要的是要理解,许多项目并不真正将 Pull Request 视为完美的补丁队列,这些补丁应该按顺序干净地应用,就像大多数基于邮件列表的项目认为的补丁系列贡献一样。大多数 GitHub 项目将 Pull Request 分支视为围绕建议的更改进行迭代对话,最终形成通过合并应用的统一差异。

这是一个重要的区别,因为通常是在认为代码完美之前提出更改建议,这在基于邮件列表的补丁系列贡献中更为罕见。这使得与维护人员的早期对话成为可能,以便达成适当的解决方案更像是一种社区努力。当使用 Pull Request 提出代码并且维护人员或社区建议进行更改时,补丁系列通常不会重新滚动,而是将差异作为新的提交推送到分支,从而在之前的工作上下文中推动对话。

例如,如果您返回并再次查看Pull Request 最终,您会注意到贡献者没有 rebase 他的提交并发送另一个 Pull Request。相反,他们添加了新的提交并将其推送到现有分支。这样,如果您将来返回并查看此 Pull Request,您可以轻松找到做出决策的所有上下文。在网站上点击“合并”按钮有目的地创建一个引用 Pull Request 的合并提交,以便在必要时可以轻松返回并研究原始对话。

与上游保持同步

如果您的 Pull Request 变得过时或无法干净地合并,您将需要修复它,以便维护者可以轻松地合并它。 GitHub 将为您测试这一点,并在每个 Pull Request 的底部告知您合并是否简单。

Pull Request does not merge cleanly
图 96. Pull Request 无法干净地合并

如果您看到类似Pull Request 无法干净地合并的内容,您将需要修复您的分支,使其变为绿色,并且维护者不必做额外的工作。

您有两种主要选择来执行此操作。您可以将您的分支 rebase 到任何目标分支之上(通常是您 fork 的存储库的 master 分支),或者您可以将目标分支合并到您的分支中。

GitHub 上的大多数开发人员会选择后者,原因与我们刚才在上一节中介绍的相同。重要的是历史和最终合并,因此 rebase 除了稍微干净的历史记录之外,没有给您带来太多好处,而且作为回报,它**更加**困难且容易出错。

如果您想合并目标分支以使您的 Pull Request 可合并,您需要将原始存储库添加为新的远程,从中获取数据,将该存储库的主分支合并到您的主题分支中,修复任何问题,最后将其推送回您打开 Pull Request 的同一分支。

例如,假设在之前我们使用的“tonychacon”示例中,原始作者做出了一个会在 Pull Request 中产生冲突的更改。让我们完成这些步骤。

$ git remote add upstream https://github.com/schacon/blink (1)

$ git fetch upstream (2)
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
Unpacking objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
From https://github.com/schacon/blink
 * [new branch]      master     -> upstream/master

$ git merge upstream/master (3)
Auto-merging blink.ino
CONFLICT (content): Merge conflict in blink.ino
Automatic merge failed; fix conflicts and then commit the result.

$ vim blink.ino (4)
$ git add blink.ino
$ git commit
[slow-blink 3c8d735] Merge remote-tracking branch 'upstream/master' \
    into slower-blink

$ git push origin slow-blink (5)
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 682 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
To https://github.com/tonychacon/blink
   ef4725c..3c8d735  slower-blink -> slow-blink
  1. 将原始存储库添加为名为 upstream 的远程。

  2. 从该远程获取最新的工作。

  3. 将该存储库的主分支合并到您的主题分支中。

  4. 修复发生的冲突。

  5. 推送回同一个主题分支。

完成此操作后,Pull Request 将自动更新并重新检查以查看是否可以干净地合并。

Pull Request now merges cleanly
图 97. Pull Request 现在可以干净地合并

Git 的一大优点是你可以持续地进行这些操作。如果你的项目运行时间很长,你可以轻松地反复从目标分支合并,并且只需要处理自上次合并以来出现的冲突,从而使整个过程变得非常易于管理。

如果你确实希望通过变基来清理分支,当然可以这样做,但强烈建议不要强制推送已经打开了 Pull Request 的分支。如果其他人已经拉取了该分支并进行了更多工作,你就会遇到 变基的风险 中概述的所有问题。相反,将变基后的分支推送到 GitHub 上的新分支,并打开一个全新的 Pull Request,引用旧的 Pull Request,然后关闭原始的 Pull Request。

参考

你接下来的问题可能是“我如何引用旧的 Pull Request?”。事实证明,有很多很多方法可以在 GitHub 上几乎任何可以编写的地方引用其他内容。

让我们从如何交叉引用另一个 Pull Request 或 Issue 开始。所有 Pull Request 和 Issue 都会被分配一个数字,并且在项目中是唯一的。例如,你不能同时拥有 Pull Request #3 *和* Issue #3。如果你想从任何其他 Pull Request 或 Issue 中引用任何 Pull Request 或 Issue,你只需在任何评论或描述中放入 #<num>。如果 Issue 或 Pull Request 位于其他地方,你也可以更具体地指定;如果你要引用你所在仓库的一个分支中的 Issue 或 Pull Request,请写入 username#<num>,或者如果你要引用另一个仓库中的内容,请写入 username/repo#<num>

让我们看一个例子。假设我们在之前的例子中变基了分支,为它创建了一个新的 pull request,现在我们想从新的 pull request 中引用旧的 pull request。我们还想引用仓库分支中的一个 issue 和一个完全不同的项目中的一个 issue。我们可以像 Pull Request 中的交叉引用 中那样填写描述。

Cross references in a Pull Request
图 98. Pull Request 中的交叉引用

当我们提交这个 pull request 时,我们将看到所有这些都像 Pull Request 中呈现的交叉引用 中那样呈现。

Cross references rendered in a Pull Request
图 99. Pull Request 中呈现的交叉引用

请注意,我们放入的完整 GitHub URL 已缩短为仅所需的信息。

现在,如果 Tony 返回并关闭原始 Pull Request,我们可以看到,通过在新的 Pull Request 中提及它,GitHub 已经自动在 Pull Request 时间线中创建了一个回溯事件。这意味着任何访问此 Pull Request 并看到它已关闭的人都可以轻松地链接回取代它的 Pull Request。该链接将类似于 在已关闭的 Pull Request 时间线中链接回新的 Pull Request

Link back to the new Pull Request in the closed Pull Request timeline
图 100. 在已关闭的 Pull Request 时间线中链接回新的 Pull Request

除了 issue 编号之外,你还可以通过 SHA-1 引用特定的提交。你必须指定完整的 40 个字符的 SHA-1,但如果 GitHub 在评论中看到它,它将直接链接到该提交。同样,你可以像处理 issue 一样引用分支或其他仓库中的提交。

GitHub Flavored Markdown

链接到其他 Issue 只是你可以在 GitHub 上的几乎任何文本框中执行的有趣事情的开始。在 Issue 和 Pull Request 的描述、评论、代码评论等中,你可以使用所谓的“GitHub Flavored Markdown”。Markdown 就像用纯文本编写,但会以富文本形式呈现。

请参阅 作为编写和呈现的 GitHub Flavored Markdown 示例,了解如何使用 Markdown 编写和呈现评论或文本。

An example of GitHub Flavored Markdown as written and as rendered
图 101. 作为编写和呈现的 GitHub Flavored Markdown 示例

GitHub 风格的 Markdown 添加了更多你可以做的事情,超越了基本的 Markdown 语法。这些在创建有用的 Pull Request 或 Issue 评论或描述时都非常有用。

任务列表

第一个非常有用的 GitHub 特定 Markdown 功能,尤其是在 Pull Request 中使用,是任务列表。任务列表是你想要完成的事情的复选框列表。将它们放入 Issue 或 Pull Request 通常表明你希望在认为该项目完成之前完成的事情。

你可以像这样创建一个任务列表

- [X] Write the code
- [ ] Write all the tests
- [ ] Document the code

如果我们将此包含在我们的 Pull Request 或 Issue 的描述中,我们将看到它像 在 Markdown 注释中呈现的任务列表 中那样呈现。

Task lists rendered in a Markdown comment
图 102. 在 Markdown 注释中呈现的任务列表

这通常在 Pull Request 中使用,以指示在你准备好合并 Pull Request 之前你想要在分支上完成的所有操作。真正酷的部分是你可以简单地单击复选框来更新注释 — 你无需直接编辑 Markdown 来检查任务是否完成。

更重要的是,GitHub 会查找你的 Issue 和 Pull Request 中的任务列表,并将它们显示为列出它们的页面上的元数据。例如,如果你有一个带有任务的 Pull Request,并且你查看所有 Pull Request 的概览页面,你可以看到它完成了多少。这有助于人们将 Pull Request 分解为子任务,并帮助其他人跟踪分支的进度。你可以在 Pull Request 列表中的任务列表摘要 中看到一个示例。

Task list summary in the Pull Request list
图 103. Pull Request 列表中的任务列表摘要

当你提前打开一个 Pull Request 并使用它来跟踪你完成功能实现的进度时,这些非常有用。

代码片段

你还可以将代码片段添加到评论中。如果你想展示一些你*可以*尝试做的事情,然后再实际将其作为分支上的提交来实现,这将特别有用。这也经常用于添加不工作的示例代码或此 Pull Request 可以实现的代码。

要添加代码片段,你必须用反引号“围起来”。

```java
for(int i=0 ; i < 5 ; i++)
{
   System.out.println("i is : " + i);
}
```

如果你像我们那样在那里添加了一个语言名称,例如“java”,GitHub 也会尝试突出显示该片段的语法。在上面的示例中,它最终会像 呈现的围栏代码示例 中那样呈现。

Rendered fenced code example
图 104. 呈现的围栏代码示例

引用

如果你要回复一个长评论中的一小部分,你可以通过在行前加上 > 字符来选择性地引用其他评论中的内容。事实上,这非常常见和有用,以至于有一个键盘快捷键可以执行此操作。如果你突出显示要直接回复的评论中的文本并按 r 键,它将在评论框中为你引用该文本。

这些引用看起来像这样

> Whether 'tis Nobler in the mind to suffer
> The Slings and Arrows of outrageous Fortune,

How big are these slings and in particular, these arrows?

呈现后,该评论将类似于 呈现的引用示例

Rendered quoting example
图 105. 呈现的引用示例

表情符号

最后,你还可以在评论中使用表情符号。这实际上在你在许多 GitHub Issue 和 Pull Request 中看到的评论中得到了广泛应用。GitHub 中甚至还有一个表情符号助手。如果你正在键入评论并且以 : 字符开头,自动完成器将帮助你找到你正在寻找的内容。

Emoji autocompleter in action
图 106. 正在运行的表情符号自动完成器

表情符号在评论中的任何位置都采用 :<name>: 的形式。例如,你可以这样写

I :eyes: that :bug: and I :cold_sweat:.

:trophy: for :microscope: it.

:+1: and :sparkles: on this :ship:, it's :fire::poop:!

:clap::tada::panda_face:

呈现后,它看起来像 大量的表情符号评论

Heavy emoji commenting
图 107. 大量的表情符号评论

并非这非常有用,但它的确为一种难以表达情感的媒介增加了一种乐趣和情感元素。

注意

实际上,现在有很多 Web 服务都在使用表情符号。在

可以找到一个很棒的备忘单,可以参考它来找到表达你想说的话的表情符号。

图片

Drag and drop images to upload them and auto-embed them
从技术上讲,这不是 GitHub Flavored Markdown,但它非常有用。除了向评论添加 Markdown 图像链接(这可能很难找到和嵌入 URL)之外,GitHub 还允许你将图像拖放到文本区域中以嵌入它们。

图 108. 拖放图像以上传它们并自动嵌入它们

保持你的 GitHub 公共仓库最新

一旦你 fork 了一个 GitHub 仓库,你的仓库(你的 "fork")就独立于原始仓库存在。特别是,当原始仓库有新的提交时,GitHub 会通过类似这样的消息通知你

This branch is 5 commits behind progit:master.

但你的 GitHub 仓库永远不会被 GitHub 自动更新;这是你必须自己完成的事情。幸运的是,这很容易做到。

一种可能性是不需要任何配置。例如,如果你从 https://github.com/progit/progit2.git fork 出来,你可以像这样保持你的 master 分支最新

$ git checkout master (1)
$ git pull https://github.com/progit/progit2.git (2)
$ git push origin master (3)
  1. 如果你在另一个分支上,请返回到 master

  2. https://github.com/progit/progit2.git 获取更改并将它们合并到 master 中。

  3. 将你的 master 分支推送到 origin

这可行,但每次都必须拼写出提取 URL 有点乏味。你可以通过一些配置来自动化这项工作

$ git remote add progit https://github.com/progit/progit2.git (1)
$ git fetch progit (2)
$ git branch --set-upstream-to=progit/master master (3)
$ git config --local remote.pushDefault origin (4)
  1. 添加源仓库并为其指定一个名称。在这里,我选择将其称为 progit

  2. 获取对 progit 分支的引用,特别是 master

  3. 将你的 master 分支设置为从 progit 远程提取。

  4. 将默认推送仓库定义为 origin

完成此操作后,工作流程将变得更加简单

$ git checkout master (1)
$ git pull (2)
$ git push (3)
  1. 如果你在另一个分支上,请返回到 master

  2. progit 获取更改并将更改合并到 master

  3. 将你的 master 分支推送到 origin

这种方法可能很有用,但并非没有缺点。 Git 会很乐意默默地为你完成这项工作,但如果你提交到 master,从 progit 拉取,然后推送到 origin,它不会警告你——所有这些操作在这种设置下都是有效的。 所以你必须小心,永远不要直接提交到 master,因为该分支实际上属于上游仓库。

scroll-to-top