-
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 命令
6.2 GitHub - 参与项目贡献
参与项目贡献
既然我们的账户已经配置好了,现在让我们来了解一些有助于你向现有项目贡献代码的细节。
派生项目
如果你想要向一个你没有推送权限的现有项目贡献代码,你可以“派生(fork)”该项目。当你“派生”一个项目时,GitHub 会为你复制一份完全属于你的该项目副本;它存在于你的命名空间中,且你可以向其推送。
|
注意
|
从历史上看,“派生(fork)”一词在语境中带有一些消极意味,意味着有人将一个开源项目引向了不同的方向,有时甚至会创建一个竞争项目并导致贡献者分裂。但在 GitHub 中,“派生”仅仅是处于你自己命名空间下的同一个项目,让你能够公开地对项目进行修改,以此作为一种更开放的贡献方式。 |
这样一来,项目维护者就无需担心为了赋予他人推送权限而必须将其添加为协作者。人们可以派生一个项目,向其推送,并通过创建所谓的“拉取请求(Pull Request)”来将修改贡献回原始仓库,我们稍后会对此进行介绍。这会开启一个带有代码审查功能的讨论贴,项目所有者和贡献者可以就此修改进行沟通,直到所有者满意为止,届时所有者就可以将其合并进来。
要派生一个项目,请访问该项目页面,并点击页面右上角的“Fork”按钮。
几秒钟后,你将被带到你的新项目页面,其中包含你自己的、可写的代码副本。
GitHub 工作流
GitHub 是围绕一种特定的协作工作流设计的,该工作流以拉取请求(Pull Requests)为中心。无论你是与一个紧密合作的团队在单个共享仓库中协作,还是与一家全球分布的公司或通过数十个派生向项目贡献代码的陌生人网络合作,此工作流都适用。它以 Git 分支 中介绍的 主题分支 工作流为中心。
它的一般工作流程如下:
-
派生项目。
-
从
master创建一个主题分支。 -
做出一些提交以改进项目。
-
将此分支推送至你的 GitHub 项目。
-
在 GitHub 上开启一个拉取请求。
-
讨论,并根据需要继续提交。
-
项目所有者合并或关闭该拉取请求。
-
将更新后的
master同步回你的派生项目。
这基本上是 集成管理者工作流 中介绍的工作流,但团队使用的是 GitHub 的网页端工具,而不是通过电子邮件来沟通和审查修改。
让我们通过一个例子,来看看如何使用该工作流向一个托管在 GitHub 上的开源项目提交修改建议。
|
提示
|
在大多数情况下,你可以使用官方的 GitHub CLI 工具来代替 GitHub 的网页界面。该工具可在 Windows、macOS 和 Linux 系统上使用。请访问 GitHub CLI 主页 获取安装说明和使用手册。 |
创建拉取请求
托尼(Tony)正在寻找要在他的 Arduino 可编程微控制器上运行的代码,并在 GitHub 上的 https://github.com/schacon/blink 处找到了一个非常棒的程序文件。
唯一的问题是闪烁频率太快了。我们觉得在每次状态变化之间等待 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
-
在本地克隆我们派生的项目副本。
-
创建一个具有描述性的主题分支。
-
修改代码。
-
检查修改是否正确。
-
将我们的修改提交到主题分支。
-
将我们新的主题分支推送回我们的 GitHub 派生项目中。
现在,如果我们回到 GitHub 上的派生项目,我们可以看到 GitHub 注意到了我们推送了一个新的主题分支,并为我们提供了一个醒目的绿色按钮,用于查看我们的更改并向原始项目开启一个拉取请求。
你也可以转到位于 https://github.com/<user>/<project>/branches 的 “Branches” 页面,找到你的分支并从该处开启一个新的拉取请求。
如果我们点击那个绿色按钮,我们会看到一个页面,要求我们为拉取请求填写标题和描述。花些心思在这上面几乎总是值得的,因为一份优秀的描述可以帮助原始项目的维护者确定你想要做什么、你提交的修改是否正确,以及接受这些修改是否能改进原始项目。
我们还会看到我们主题分支中“领先于” master 分支的提交列表(在本例中只有这一个),以及一个标准差异(unified diff)对比,展示了如果该分支被项目所有者合并,将会做出的所有修改。
当你点击此屏幕上的 “Create pull request” 按钮时,你派生的项目的原作者会收到一条通知,告知有人提出了修改建议,并且该通知会链接到一个包含所有这些信息的页面。
|
注意
|
虽然在像这样的公共项目中,当贡献者准备好完整的修改后,通常会使用拉取请求,但在内部项目中,它也经常在开发周期的 一开始 就被使用。因为即使在拉取请求开启 之后,你仍然可以继续向主题分支推送,所以它通常会很早就被开启,并在团队内部作为一种在特定上下文中对工作进行迭代的方式,而不是直到最后才开启。 |
迭代拉取请求
此时,项目所有者可以查看建议的修改并进行合并、拒绝或发表评论。假设他很喜欢这个想法,但希望灯熄灭的时间比亮起的时间稍微长一点。
在 分布式 Git 介绍的工作流中,这类对话可能会通过电子邮件进行,但在 GitHub 上,这一切都是在线上完成的。项目所有者可以查看标准差异,并通过点击任意一行来留下评论。
一旦维护者发表了这条评论,开启拉取请求的人(以及关注该仓库的其他所有人)都会收到一条通知。我们稍后会介绍如何自定义此设置,但如果托尼开启了邮件通知,他将会收到一封像下面这样的邮件
任何人也可以对拉取请求发表全局评论。在 拉取请求讨论页面 中,我们可以看到一个项目所有者既对某行代码进行了评论,又在讨论区留下了全局评论的例子。你可以看到,针对代码的评论也被引入到了对话中。
现在,贡献者可以看到他们需要做些什么来让自己的修改被接受。幸运的是,这非常简单。如果通过电子邮件,你可能需要重新整理补丁集并重新提交到邮件列表,但在 GitHub 上,你只需再次向主题分支进行提交并推送,这会自动更新拉取请求。在 拉取请求最终状态 中,你还可以看到,旧的代码评论在更新后的拉取请求中已被折叠,因为它是针对此后已被修改的代码行发表的。
向已有的拉取请求中添加提交并不会触发通知,因此托尼在推送了他的修正后,决定留下一条评论,告知项目所有者他已经做出了要求的修改。
值得注意的一个有趣现象是,如果你点击该拉取请求上的 “Files Changed” 标签页,你将获得“标准(unified)”差异对照——也就是如果该主题分支被合并,将引入到你的主分支中的所有累计差异。用 git diff 的术语来说,它基本上是自动为你展示该拉取请求所基于分支的 git diff master…<branch>。欲了解有关此类差异的更多信息,请参阅 确定引入了哪些内容。
你还会注意到的另一件事是,GitHub 会检查该拉取请求是否可以无冲突合并,并在服务器上提供一个为你执行合并的按钮。该按钮仅在你对仓库拥有写权限且可以进行简单合并时才会显示。如果你点击它,GitHub 将执行“非快进(non-fast-forward)”合并,这意味着即使该合并 可以 是快进合并,它也依然会创建一个合并提交。
如果你愿意,也可以简单地将该分支拉取到本地并在本地进行合并。如果你将此分支合并到 master 分支并将其推送至 GitHub,该拉取请求将自动关闭。
这是大多数 GitHub 项目使用的基本工作流。创建主题分支,针对它们开启拉取请求,随后展开讨论,可能在分支上进行更多工作,最终该请求要么被关闭,要么被合并。
|
注意
|
不仅仅是派生
需要特别注意的是,你也可以在同一个仓库中的两个分支之间开启拉取请求。如果你正和某人共同开发一项功能,且你们两人都对项目拥有写权限,你可以向该仓库推送一个主题分支,并针对它向该项目的 |
高级拉取请求
既然我们已经介绍了向 GitHub 项目贡献代码的基础知识,下面让我们来了解一些关于拉取请求的有趣技巧和诀窍,以便你可以更高效地使用它们。
将拉取请求视为补丁
重要的是要理解,许多项目并不会像大多数基于邮件列表的项目看待补丁集贡献那样,将拉取请求视为应该按顺序无缝应用的完美补丁队列。大多数 GitHub 项目将拉取请求分支视为围绕修改建议进行的迭代对话,并以通过合并来应用的标准差异对照而告终。
这是一个重要的区别,因为通常在代码被认为完美之前就已经提出了修改建议,这在基于邮件列表的补丁集贡献中要罕见得多。这使得可以尽早与维护者展开对话,从而使找到合适的解决方案更像是一种社区协作。当通过拉取请求提交代码,且维护者或社区提出修改建议时,补丁集通常不会被重新整理,而是将这些差异作为新的提交推送到分支中,在保留之前工作上下文完整的前提下推动对话向前发展。
例如,如果你倒回去重新看一看 拉取请求最终状态,你会注意到贡献者并没有变基(rebase)他的提交并发送另一个拉取请求。相反,他们添加了新的提交并将其推送到现有的分支中。这样,如果你以后再回头查看这个拉取请求,就可以轻松地找到做出决定的所有背景信息。点击网站上的“Merge”按钮会特意创建一个引用了该拉取请求的合并提交,以便在必要时能轻松回溯并研究最初的对话。
与上游保持同步
如果你的拉取请求已过期或无法无冲突合并,你会想要修复它,以便维护者可以轻松合并。GitHub 会为你测试这一点,并在每个拉取请求的底部通知你合并是否简单。
如果你看到类似 拉取请求无法无冲突合并 的情况,你会想要修复你的分支,使其变成绿色,这样维护者就不需要做额外的工作。
为此,你主要有两种选择。你既可以将你的分支变基到目标分支(通常是你派生的仓库的 master 分支)之上,也可以将目标分支合并到你的分支中。
GitHub 上的大多数开发者会选择后者,原因正如我们在上一节中介绍的那样。重要的是历史记录和最终的合并,因此变基除了带给你稍微整洁一些的历史记录之外并没有多大用处,相反,它的难度要 大得多 且更容易出错。
如果你想合并目标分支以使你的拉取请求可合并,你需要将原始仓库添加为新的远程仓库,从中获取(fetch)更新,将该仓库的主分支合并到你的主题分支中,修复所有问题,最后将其推送回你开启拉取请求的同一个分支上。
例如,假设在之前使用的 “tonychacon” 例子中,原作者做出了一个修改,导致该拉取请求产生了冲突。让我们来看看这些步骤。
$ 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
-
将原始仓库添加为名为
upstream的远程仓库。 -
从该远程仓库获取最新的工作成果。
-
将该仓库的主分支合并到你的主题分支中。
-
修复出现的冲突。
-
推送回相同的主题分支。
完成这些操作后,拉取请求将自动更新,并重新检查是否可以无冲突合并。
Git 的优点之一就是你可以持续进行此操作。如果你有一个运行周期非常长的项目,你可以轻松地一次又一次地合并目标分支,并且只需处理自上次合并以来出现的冲突,这使整个过程非常易于管理。
如果你确实希望通过变基来清理分支,当然可以这样做,但强烈建议不要强制推送(force push)到已经开启了拉取请求的分支上。如果其他人已经拉取了该分支并在其上进行了更多工作,你将会遇到 变基的风险 中概述的所有问题。相反,你应该将变基后的分支推送至 GitHub 上的一个新分支,并开启一个引用了旧请求的全新拉取请求,然后关闭原始的拉取请求。
引用
你的下一个问题可能是“我该如何引用旧的拉取请求?”。事实证明,在 GitHub 上,几乎所有可以输入文字的地方,都有非常多的方式来引用其他事物。
让我们先从如何交叉引用另一个拉取请求或议题(Issue)开始。所有的拉取请求和议题都会被分配编号,并且这些编号在项目内是唯一的。例如,你不能同时拥有拉取请求 #3 和 议题 #3。如果你想在任何地方引用另一个拉取请求或议题,只需在任意评论或描述中输入 #<num>。如果该议题或拉取请求存在于别处,你也可以写得更具体些:如果你要引用当前仓库派生副本中的议题或拉取请求,请编写 username#<num>,或使用 username/repo#<num> 来引用另一个仓库中的内容。
让我们看一个例子。假设我们对前一个例子中的分支进行了变基,并为其创建了一个新的拉取请求,现在我们想在新请求中引用旧的拉取请求。我们还想引用该仓库派生副本中的一个议题,以及一个完全不同的项目中的议题。我们可以像 拉取请求中的交叉引用 那样填写描述。
当我们提交此拉取请求时,我们会看到所有这些都像 拉取请求中渲染后的交叉引用 那样被渲染出来。
请注意,我们输入的完整 GitHub URL 被缩短为仅包含所需的信息。
现在,如果托尼回去关闭原始的拉取请求,我们可以看到,由于在新拉取请求中提到了它,GitHub 自动在该拉取请求的时间线中创建了一个追溯(trackback)事件。这意味着,任何访问此拉取请求并看到它已关闭的人,都可以轻松地链接到取代它的新请求。该链接看起来类似于 已关闭拉取请求时间线中指向新拉取请求的链接。
除了议题编号之外,你还可以通过 SHA-1 引用特定的提交。你必须指定完整的 40 字符 SHA-1 值,但如果 GitHub 在评论中看到它,就会直接建立指向该提交的链接。同样,你可以像引用议题那样,以相同的方式引用派生项目或其他仓库中的提交。
GitHub 风格的 Markdown
链接 to 其他议题只是你在 GitHub 上几乎所有文本框中能做的有趣事情的开始。在议题和拉取请求的描述、评论、代码注释等处,你可以使用被称为“GitHub 风格的 Markdown(GitHub Flavored Markdown)”的语法。Markdown 就像是用纯文本编写,但最终能被渲染出丰富排版效果的格式。
参阅 GitHub 风格 Markdown 的编写与渲染示例,以了解如何使用 Markdown 编写评论或文本并对其进行渲染。
GitHub 风格的 Markdown 在基础 Markdown 语法之上增加了一些额外功能。这些在创建实用的拉取请求、议题的评论或描述时,都非常有用。
任务列表
第一个非常实用的 GitHub 特有 Markdown 功能(尤其是对于拉取请求)是任务列表(Task List)。任务列表是一个包含待办事项复选框的列表。将它们放入议题或拉取请求中,通常表示在你认为该项工作完成之前你需要做完的事情。
你可以像这样创建任务列表
- [X] Write the code
- [ ] Write all the tests
- [ ] Document the code
如果我们将此内容包含在我们的拉取请求或议题描述中,我们会看到它被渲染为类似于 Markdown 评论中渲染的任务列表 的效果。
这在拉取请求中经常使用,用以表示在拉取请求准备好合并之前,你想在分支上完成的所有工作。最棒的一点是,你只需直接点击复选框即可更新评论——你无需手动编辑 Markdown 文本来勾选完成任务。
此外,GitHub 会在你的议题和拉取请求中检索任务列表,并在展示它们的列表页面上将其作为元数据(metadata)显示出来。例如,如果你有一个包含任务的拉取请求,并且查看所有拉取请求的概览页面,你就可以看到它的完成进度。这有助于人们将拉取请求分解为子任务,并帮助其他人跟踪分支的进度。你可以在 拉取请求列表中的任务列表摘要 中看到这样一个例子。
当你早早地开启一个拉取请求,并用它来跟踪你在实现特定功能期间的进度时,这些复选框会变得极其有用。
代码片段
你也可以在评论中添加代码片段。如果你想在实际作为提交(commit)应用到你的分支之前,展示一些你 可能 尝试去做的事情,这会特别有用。这通常也用于添加一些说明“什么无法正常工作”或“该拉取请求可以实现什么”的示例代码。
要添加一段代码,你必须用反引号将其“围起来”。
```java
for(int i=0 ; i < 5 ; i++)
{
System.out.println("i is : " + i);
}
```
如果你像我们之前那样加上语言名称(例如 'java'),GitHub 还会尝试对该代码片段进行语法高亮。对于上述例子,它最终会渲染成类似于 渲染后的围栏代码示例 的效果。
引用
如果你只想回复长评论中的一小部分,可以通过在每行前面加上 > 字符来选择性地引用其他评论的内容。实际上,由于这个操作如此常见且实用,为此还有一个键盘快捷键。如果你在想要直接回复的评论中高亮一段文本并按下 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?
渲染后,评论将类似于 渲染后的引用示例。
表情符号(Emoji)
最后,你还可以在评论中使用表情符号(Emoji)。这在许多 GitHub 议题和拉取请求的评论中被广泛使用。GitHub 中甚至还有一个表情助手。如果你在输入评论时以 : 字符开头,自动补全器就会帮你找到你想要的表情符号。
表情符号在评论中的任何位置都以 :<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:
渲染后,它看起来就像 大量的表情符号评论。
这并不是说它有什么极大的用处,但它确实在一种原本很难传达情感的媒介中增添了趣味和情感元素。
|
注意
|
如今其实有很多网络服务都在使用表情符号。一个非常实用的表情符号备忘单(可以用来寻找能表达你想法的表情符号)可以在以下地址找到: |
图片
严格来说,这不属于 GitHub 风格的 Markdown,但它非常有用。除了在评论中添加 Markdown 图片链接之外(这有时需要费力寻找并嵌入 URL),GitHub 还允许你直接通过拖放图片到文本区域来进行嵌入。
如果你查看 拖放图片以进行上传并自动嵌入,你可以在文本区域上方看到一个小的 “Parsed as Markdown” 提示。点击它将为你提供一份关于在 GitHub 上使用 Markdown 所能做的一切事情的完整备忘单。
保持你的 GitHub 公共仓库最新
一旦你派生了一个 GitHub 仓库,你的仓库(你的“派生项目”)就会独立于原始项目而存在。特别是,当原始仓库有了新的提交时,GitHub 会通过类似于以下的消息来通知你:
This branch is 5 commits behind progit:master.
但 GitHub 绝不会自动更新你的 GitHub 仓库;这是你必须自己动手做的事情。幸运的是,这非常容易做到。
实现此操作的一种方法不需要任何配置。例如,如果你是从 https://github.com/progit/progit2.git 派生的,你可以像这样保持你的 master 分支最新:
$ git checkout master (1)
$ git pull https://github.com/progit/progit2.git (2)
$ git push origin master (3)
-
如果你在其他分支上,请返回
master。 -
从
https://github.com/progit/progit2.git获取更改并将其合并到master中。 -
将你的
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)
-
添加源仓库并为其命名。在这里,我选择将其命名为
progit。 -
获取 progit 各个分支的引用,特别是
master。 -
设置你的
master分支以从progit远程仓库获取更新。 -
将默认的推送仓库定义为
origin。
完成这些配置后,工作流就会变得简单得多:
$ git checkout master (1)
$ git pull (2)
$ git push (3)
-
如果你在其他分支上,请返回
master。 -
从
progit获取更改并将其合并到master中。 -
将你的
master分支推送至origin。
这种方法虽然有用,但并非没有缺点。Git 会在后台默默地为你完成这些工作,但如果你向 master 进行提交、从 progit 拉取、然后推送到 origin,Git 不会发出警告——在此配置下,所有这些操作都是合法的。因此,你必须注意绝对不要直接提交到 master,因为该分支实际上属于上游仓库。