-
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 - 参与项目
参与项目
既然我们的账号已经设置好了,让我们来详细了解一些有助于你参与现有项目的内容。
派生(Forking)项目
如果你想参与一个你没有推送权限的现有项目,你可以“派生”(“fork”)这个项目。当你“派生”一个项目时,GitHub 会创建一个完全属于你的项目副本;它存在于你的命名空间中,你可以向其推送。
|
注意
|
在历史上,“fork”这个词在语境中带有一些负面含义,表示有人将一个开源项目引向了不同的方向,有时会创建一个竞争项目并分裂贡献者。在 GitHub 中,“fork”只是你个人命名空间中的同一个项目,允许你公开地对项目进行更改,以此更开放的方式进行贡献。 |
通过这种方式,项目就不必担心将用户添加为协作者以授予他们推送权限。人们可以派生项目,推送更改,并通过创建拉取请求(Pull Request,我们将在后面介绍)将更改贡献回原始仓库。这会开启一个带有代码审查的讨论线程,项目所有者和贡献者可以就更改进行沟通,直到所有者满意为止,然后所有者就可以将其合并。
要派生一个项目,请访问项目页面并单击页面右上角的“Fork”按钮。
几秒钟后,你将被带到你的新项目页面,其中包含你自己的可写代码副本。
GitHub 工作流
GitHub 是围绕一种特定的协作工作流设计的,以拉取请求为中心。这种工作流适用于你与一个紧密协作的团队在单个共享仓库中协作,或是与一个全球分布式公司或陌生人网络通过数十个派生(fork)项目进行协作。它以 主题分支(Topic Branches) 工作流为中心,如 Git 分支 所述。
它的通用工作流程如下:
-
派生项目。
-
从
master分支创建主题分支。 -
提交一些改进项目的更改。
-
将此分支推送到你的 GitHub 项目。
-
在 GitHub 上发起拉取请求。
-
讨论,并可选择继续提交。
-
项目所有者合并或关闭拉取请求。
-
将更新的
master同步回你的派生仓库。
这基本上是 整合管理者工作流 中介绍的整合管理者工作流,但是团队使用 GitHub 的基于 Web 的工具来代替电子邮件进行沟通和审查更改。
让我们通过一个示例来演示如何使用此工作流向 GitHub 上托管的开源项目提出更改。
|
提示
|
大多数情况下,你可以使用官方的 GitHub CLI 工具来代替 GitHub Web 界面。该工具可以在 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 分支的提交列表(在本例中只有一个),以及如果项目所有者合并此分支,将进行的所有更改的统一差异。
当你在此屏幕上点击“Create pull request”按钮时,你所派生项目的拥有者将收到有人建议更改的通知,并将链接到一个包含所有这些信息的页面。
|
注意
|
尽管拉取请求通常用于像这样的公共项目,当贡献者准备好完整的更改时,它也常用于开发周期 开始时 的内部项目。因为即使拉取请求打开 之后,你也可以继续推送到主题分支,所以它通常在早期打开并作为在特定上下文中团队协作迭代工作的一种方式,而不是在过程的最后才打开。 |
迭代拉取请求
此时,项目所有者可以查看建议的更改并合并、拒绝或评论它。假设他喜欢这个想法,但希望灯灭的时间比亮的时间稍长一些。
这种对话在 分布式 Git 中介绍的工作流中可能通过电子邮件进行,而在 GitHub 上则是在线发生的。项目所有者可以查看统一差异并单击任意一行留下评论。
一旦维护者发表此评论,打开拉取请求的人(以及任何关注该仓库的人)都将收到通知。我们稍后会介绍如何自定义此功能,但如果他启用了电子邮件通知,Tony 将收到一封类似这样的电子邮件:
任何人也可以对拉取请求留下一般性评论。在 拉取请求讨论页面 中,我们可以看到项目所有者既评论代码行,又在讨论区留下一般性评论的示例。你可以看到代码评论也已纳入对话中。
现在,贡献者可以看到他们需要做什么才能使他们的更改被接受。幸运的是,这非常简单。如果通过电子邮件,你可能需要重新整理你的系列并重新提交到邮件列表,但在 GitHub 上,你只需再次提交到主题分支并推送,这会自动更新拉取请求。在 拉取请求最终页面 中,你还可以看到旧的代码注释在更新的拉取请求中已被折叠,因为它是在已更改的行上进行的。
向现有拉取请求添加提交不会触发通知,所以一旦 Tony 推送了他的修改,他决定留下一条评论,告知项目所有者他已经完成了所请求的更改。
一个有趣的地方是,如果你点击此拉取请求上的“Files Changed”选项卡,你将看到“统一”差异——也就是说,如果此主题分支被合并,将引入到你的主分支中的总聚合差异。用 git diff 术语来说,它基本上会自动显示基于此拉取请求的分支的 git diff master...。有关此类差异的更多信息,请参阅 确定引入了什么。
你还会注意到另一件事,GitHub 会检查拉取请求是否可以干净地合并,并提供一个按钮让你在服务器上进行合并。这个按钮只有在你对仓库有写入权限并且可以进行简单合并时才会显示。如果你点击它,GitHub 将执行一个“非快进”合并,这意味着即使合并 可以 是快进的,它仍然会创建一个合并提交。
如果你愿意,你可以简单地拉取分支并在本地合并。如果你将此分支合并到 master 分支并推送到 GitHub,拉取请求将自动关闭。
这是大多数 GitHub 项目使用的基本工作流。创建主题分支,在其上打开拉取请求,进行讨论,可能在该分支上进行更多工作,最终请求被关闭或合并。
|
注意
|
不仅仅是派生
值得注意的是,你也可以在同一个仓库中的两个分支之间打开拉取请求。如果你正在与某人合作开发一个功能,并且你们都对项目拥有写入权限,你可以将主题分支推送到仓库,并打开一个针对该项目 |
高级拉取请求
既然我们已经介绍了在 GitHub 上参与项目的基本知识,接下来我们将介绍一些关于拉取请求的有趣技巧和窍门,以便你可以更有效地使用它们。
拉取请求作为补丁
重要的是要理解,许多项目并不真正将拉取请求视为按顺序干净地应用的完美补丁队列,而大多数基于邮件列表的项目则将补丁系列贡献视为如此。大多数 GitHub 项目将拉取请求分支视为围绕提议更改的迭代对话,最终形成一个通过合并应用的统一差异。
这是一个重要的区别,因为通常在代码被认为是完美的之前就建议更改,这在基于邮件列表的补丁系列贡献中更为罕见。这使得可以与维护者进行更早的对话,从而使找到正确的解决方案成为一种更具社区性质的努力。当通过拉取请求提出代码并且维护者或社区建议更改时,补丁系列通常不会被重新整理,而是将差异作为新的提交推送到分支,在保留先前工作上下文的情况下推进对话。
例如,如果你回头再看 拉取请求最终页面,你会注意到贡献者没有 rebase 他的提交并发送另一个拉取请求。相反,他们添加了新的提交并将它们推送到现有分支。这样,如果你将来回头查看这个拉取请求,你可以很容易地找到所有关于为什么做出这些决定的上下文。点击站点上的“Merge”按钮会特意创建一个引用拉取请求的合并提交,这样在必要时可以轻松地回溯并研究原始对话。
与上游保持同步
如果你的拉取请求过期或无法干净地合并,你需要修复它,以便维护者可以轻松合并。GitHub 会为你测试这一点,并在每个拉取请求的底部告诉你合并是简单的还是不简单的。
如果你看到类似 拉取请求无法干净地合并 的情况,你需要修复你的分支,使其变成绿色,这样维护者就不必做额外的工作。
你有两种主要选择来完成此操作。你可以将你的分支 rebase 到目标分支之上(通常是你派生仓库的 master 分支),或者你可以将目标分支合并到你的分支中。
GitHub 上的大多数开发者会选择后者,原因与我们前一节所述相同。重要的是历史和最终合并,所以 rebase 除了稍微干净一点的历史记录外,并没有带来太多好处,反而 非常 困难且容易出错。
如果你想合并目标分支以使你的拉取请求可合并,你需要将原始仓库添加为新的远程,从它那里抓取,将该仓库的主分支合并到你的主题分支中,修复任何问题,最后将其推送到你打开拉取请求的同一个分支。
例如,假设在我们之前使用的“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 的优点之一是你可以持续这样做。如果你有一个运行时间很长的项目,你可以很容易地一次又一次地从目标分支合并,并且只需要处理自上次合并以来出现的冲突,这使得整个过程非常易于管理。
如果你绝对希望重新整理分支以清理它,你当然可以这样做,但强烈建议不要强制推送拉取请求已经打开的分支。如果其他人已经拉取并在此基础上做了更多工作,你将遇到 Rebase 的危险 中列出的所有问题。相反,将重新整理后的分支推送到 GitHub 上的一个新分支,并打开一个全新的拉取请求,引用旧的拉取请求,然后关闭原始的拉取请求。
参考
你的下一个问题可能是“如何引用旧的拉取请求?”。事实证明,在 GitHub 上几乎任何可以书写的地方,都有许多许多种方法可以引用其他内容。
我们先从如何交叉引用另一个拉取请求或一个 Issue 开始。所有拉取请求和 Issue 都分配了编号,并且在项目内部是唯一的。例如,你不能同时拥有拉取请求 #3 和 Issue #3。如果你想从任何其他拉取请求或 Issue 引用它们,你只需在任何评论或描述中放入 #。如果 Issue 或拉取请求位于其他位置,你也可以更具体;如果你指的是你所在仓库的一个派生(fork)中的 Issue 或拉取请求,则写入 username#,或者写入 username/repo# 来引用另一个仓库中的内容。
让我们看一个例子。假设我们重新整理了前面例子中的分支,为其创建了一个新的拉取请求,现在我们想从新的拉取请求中引用旧的拉取请求。我们还想引用仓库派生中的一个问题以及一个完全不同的项目中的一个问题。我们可以像 拉取请求中的交叉引用 一样填写描述。
当我们提交这个拉取请求时,我们将看到所有内容都像 拉取请求中呈现的交叉引用 一样渲染出来。
请注意,我们输入的完整 GitHub URL 被缩短为仅需的信息。
现在,如果 Tony 回去关闭了原始的拉取请求,我们可以看到,通过在新拉取请求中提及它,GitHub 已经自动在拉取请求时间线中创建了一个回溯事件。这意味着任何访问此拉取请求并看到它已关闭的人都可以轻松地链接回取代它的那个。该链接将看起来像 在已关闭的拉取请求时间线中链接回新的拉取请求。
除了问题编号,你还可以通过 SHA-1 引用特定的提交。你必须指定完整的 40 个字符的 SHA-1,但如果 GitHub 在评论中看到它,它将直接链接到该提交。同样,你可以像对待问题一样,以相同的方式引用派生(fork)或其他仓库中的提交。
GitHub 风味 Markdown
链接到其他 Issue 只是你可以在 GitHub 上几乎任何文本框中做的有趣事情的开始。在 Issue 和拉取请求的描述、评论、代码评论以及更多地方,你可以使用所谓的“GitHub 风味 Markdown”。Markdown 就像写纯文本一样,但它能呈现丰富的格式。
请参阅 GitHub 风味 Markdown 的书写和渲染示例,了解评论或文本如何使用 Markdown 书写和渲染的示例。
GitHub 风味 Markdown 添加了超出基本 Markdown 语法的更多功能。这些功能在创建有用的拉取请求或 Issue 评论或描述时都非常有用。
任务列表
第一个真正有用的 GitHub 特有 Markdown 功能,尤其适用于拉取请求,是任务列表。任务列表是您想要完成的事情的复选框列表。将其放入 Issue 或拉取请求中通常表示您在认为该项完成之前想要完成的事情。
你可以像这样创建一个任务列表:
- [X] Write the code
- [ ] Write all the tests
- [ ] Document the code
如果我们将其包含在拉取请求或 Issue 的描述中,我们将看到它渲染为 Markdown 评论中渲染的任务列表。
这在拉取请求中经常用于指示在拉取请求准备好合并之前,您希望在该分支上完成的所有工作。真正酷的地方在于,您只需单击复选框即可更新评论——无需直接编辑 Markdown 来勾选任务。
更重要的是,GitHub 会在你的 Issue 和拉取请求中查找任务列表,并在列出它们的页面上将其显示为元数据。例如,如果你的拉取请求包含任务,并且你查看所有拉取请求的概览页面,你可以看到它完成了多少。这有助于人们将拉取请求分解为子任务,并帮助其他人跟踪分支的进度。你可以在 拉取请求列表中的任务列表摘要 中看到一个示例。
当您在早期打开拉取请求并将其用于跟踪功能实现进度时,这些功能非常有用。
代码片段
你还可以在评论中添加代码片段。如果你想在实际将其作为提交实现到你的分支上之前展示一些你可以尝试做的事情,这尤其有用。这通常也用于添加不工作或此拉取请求可以实现的功能的示例代码。
要添加代码片段,你必须用反引号将其“围起来”。
```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?
渲染后,评论将显示为 渲染的引用示例。
表情符号
最后,你还可以在评论中使用表情符号。这在许多 GitHub Issue 和 Pull Request 的评论中实际上被广泛使用。GitHub 甚至提供了一个表情符号助手。如果你正在输入评论并以 : 字符开头,一个自动完成器将帮助你找到你想要的表情符号。
表情符号在评论的任何位置都以 : 的形式出现。例如,你可以写出这样的内容:
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。
这有效,但每次都必须拼写出 fetch 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,它不会警告你——所有这些操作在此设置下都是有效的。因此,你必须注意永远不要直接提交到 master,因为该分支实际上属于上游仓库。