如何参与开源项目 - 细说 GitHub 上的 PR 全过程

Posted 胡说云原生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何参与开源项目 - 细说 GitHub 上的 PR 全过程相关的知识,希望对你有一定的参考价值。

一、概述

今天我准备和你详细介绍如何开始参与开源项目,帮助你在 GitHub 上完成第一个 PR 的合入

当然,除了正常的 PR 合入流程之外,我还准备详细介绍一下如果一个 PR 提交后遇到了冲突、需要追加 commits、需要合并 commits 等等相对复杂问题该如何解决。

总的来说,本文计划分为4个部分:

  1. 谈谈为什么要参与开源项目以及我为什么要介绍如何 PR
  2. 谈谈怎么开始参与开源项目,也就是如何寻找合适的开源项目、如何寻找贡献点
  3. 介绍怎么上手 PR 流程,即从 fork 到 push 全流程
  4. 介绍提交了 PR 之后遇到各种常见问题如何解决

Ok, lets get started!

二、为什么要参与开源项目

本文我不打算长篇大论“为什么要参与开源”,详细介绍参与开源项目的收获,我想仅从“提升编码能力”角度谈一谈“为什么要参与开源项目”。

在面试的时候我有个习惯,如果候选人在自己的简历里说到自己熟悉某一门语言,我就会习惯性问他一个问题:

你有没有阅读过某个开源项目的源码?或者更进一步,有没有参与过某个开源社区,或者说给开源项目提过 PR

如果答案是肯定的,比如候选人说自己读过部分 Kubernetes 模块的源码,再进一步我确认他真的读过并且读懂了或者说真的提交过 bugfix/feature 类型的 PR,那我就不再问编程语言层面的问题了,因为我相信能看懂一个成熟的开源项目部分模块源码或者能够提交 bugfix/feature 类型的 PR 已经说明了一切。

我自己在学习 Golang 的时候,大致分为两个阶段:

  1. 学习基础语法,开始写项目,直到能够熟练完成各种业务功能的开发;
  2. 看了一些开源项目的源码,深感受益颇多,编码水平再上一个台阶。

差不多也就是在看 Kubernetes 项目源码的时候,我深刻认识到一般的企业内部项目和汇集全世界最优秀的程序员智慧结晶的开源项目之间的巨大差距,也意识到学习优秀开源项目源码对于一个程序员编码水平提升的重要性(当然,你可以说 Google 内部也存在非开源的非常优秀的代码,这毫无疑问,但是我想今天我们没有必要讨论特例)。

认真阅读开源项目源码,你总会发现一些小瑕疵,这时候提一个 PR(Pull Request),让你的代码合入开源项目,运行在“世界每一个角落”,那是多么有趣的事情!而成功合入第一个 PR 往往就像打开潘多拉魔盒一样,你会进入到另外一个世界,开始接触到开源社区,感受开源的魅力!

三、为什么我想介绍如何 PR

我司开源了2个项目,分别是:

  1. CNCF Project DevStream

  1. Apache DevLake

DevStream 项目和 DevLake 项目隔三差五就会有新贡献者提交 PR 过来,但是多数贡献者在提交第一个 PR 时往往会遇到一个或多个问题,比如产生冲突、commits 记录过多或者混乱、commit 没有签名、commit message 不规范、各种 ci 流程检查报错等等。

在看到新贡献者提交 PR 时,我们自然是非常开心且热情地对他表示欢迎并且告知如何修复各种问题,但是随着贡献者的增多,我们的开源社区几乎每天都需要回答一个问题:“如何正确地提交一个 PR”。可能此时你会开始怀疑我们是不是没有提供相应的文档?其实不然,我们有详细的文档,但是人总是有惰性的,多数的新贡献者并没有足够的意愿去仔细看翻看文档然后再提交 PR,甚至很多新贡献者由于刚开始接触开源项目,对于项目结构和文档组织结构比较陌生,甚至不会想到有这些文档的存在,总之各种各样的理由让多数的新贡献者会选择“先提了 PR再说”。

那么今天我想尝试彻底讲明白“如何正确地提交一个 PR”,尝试细说 GitHub 上的 PR 全过程,以及这里面可能会遇到的各种困难和解决办法。一方面希望对第一次参与开源项目的新人有所帮助,另一方面希望能够进一步降低 DevStream 社区和 DevLake 社区的参与门槛

四、我想参与开源项目,怎么开始?

不管你为什么决定开始参与开源项目,不管出发点是出于学习、兴趣、成就感等等,还是为了让某个自己需要的特性合入某个开源项目,总之今天你下定决心,要给某个开源项目提交一个 PR 了,好,我们开始吧!

4.1、寻找一个合适的开源项目

如果你已经决定参与某个开源社区了,那么请直接跳过本小节。

如果你就只是想开始参与开源,暂时还不知道该参与哪个社区,那么我有几个小建议:

  1. 不要从特别成熟的项目开始。比如现在去参与 Kubernetes 社区,一方面由于贡献者太多,很难抢到一个入门级的 issue 来开始第一个 PR;另外一方面也由于贡献者太多,你的声音会被淹没,社区维护者并不在意多你一个或者少你一个(当然可能没有人会承认,但是你不得不信),如果你提个 PR 都遇到了各种问题还不能自己独立解决,那么很可能你的 PR 会直接超时关闭,没有人在意你是不是有一个好的参与体验;
  2. 不要从特别小的项目开始。这就不需要我解释了吧?很早期的开源项目可能面临着非常多的问题,比如代码不规范、协作流程不规范、重构频繁且不是 issue 驱动的,让外部参与者无所适从……
  3. 选择知名开源软件基金会的孵化项目,这类项目一方面不是特别成熟,所以对新贡献者友好;另一方面也不会特别不成熟,不至于给人很差的参与体验,比如 Apache 基金会、Linux 基金会、CNCF 等

比如可以从这些地方寻找自己感兴趣的开源项目:

当然,你也可以直接选择从 CNCF 沙箱项目 DevStream 或者 Apache 孵化项目 Apache DevLake,以此敲开开源世界的大门。

4.2、寻找贡献点

开源项目的参与方式很多,最典型的方式是提交一个特性开发或者 bug 修复相关的 PR,但是其实文档完善、测试用例完善、bug 反馈等等也都是非常有价值的贡献。不过本文还是从需要提 PR 的贡献点开始上手,以 DevStream 项目为例(其他项目也一样),在项目 GitHub 代码库首页都会有一个 Issues 入口,这里会记录项目目前已知的 bug、proposal(可以理解成新需求)、计划补充的文档、亟需完善的 UT 等等,如下图:

在 Issues 里我们一般可以找到一个“good first issue”标签标记的 issues,点击这个标签可以进一步直接筛选出所有的 good first issues,这是社区专门留给新贡献者的相对简单的入门级 issues:

没错,从这里开始,浏览一下这些 good first issues,看下有没有你感兴趣的而且还没被分配的 issue,然后在下面留言,等待项目管理员分配任务后就可以开始编码了,就像这样:

如图所示,如果一个 issue 还没有被认领,这时候你上去留个言,等待管理员会将这个任务分配给你,接着你就可以开始开发了。

五、我要提交 PR,怎么上手?

一般开源项目代码库根目录都会有一个 CONTRIBUTING.md 或者其他类似名字的文档来介绍如何开始贡献,像这样:

DevStream 的 Contributing 文档里我们放了一个 Development Workflow,其实就是 PR 工作流的介绍,不过今天,我要更详细地聊聊 PR 工作流。

5.1、第一步:Fork 项目仓库

GitHub 上的项目都有一个 Fork 按钮,我们需要先将开源项目 fork 到自己的账号下,以 DevStream 为例:

点一下 Fork 按钮,然后回到自己账号下,可以找到 fork 到的项目了:

这个项目在你自己的账号下,也就意味着你有任意修改的权限了。我们后面要做的事情,就是将代码变更提到自己 fork 出来的代码库里,然后再通过 Pull Request 的方式将 commits 合入上游项目。

5.2、第二步:克隆项目仓库到本地

对于任意一个开源项目,流程几乎都是一样的。我直接写了一些命令,大家可以复制粘贴直接执行。当然,命令里的一些变量还是需要根据你自己的实际需求修改,比如对于 DevStream 项目,我们可以先这样配置几个环境变量:

  • 环境变量
export WORKING_PATH="~/gocode"
export USER="daniel-hutao"
export PROJECT="devstream"
export ORG="devstream-io"

同理对于 DevLake,这里的命令就变成了这样:

export WORKING_PATH="~/gocode"
export USER="daniel-hutao"
export PROJECT="incubator-devlake"
export ORG="apache"

记得 USER 改成你的 GitHub 用户名,WORKING_PATH 当然也可以灵活配置,你想把代码放到哪里,就写对应路径。

接着就是几行通用的命令来完成 clone 等操作了:

  • clone 等
mkdir -p $WORKING_PATH
cd $WORKING_PATH
# You can also use the url: git@github.com:$USER/$PROJECT.git
# if your ssh configuration is proper
git clone https://github.com/$USER/$PROJECT.git
cd $PROJECT

git remote add upstream https://github.com/$ORG/$PROJECT.git
# Never push to upstream locally
git remote set-url --push upstream no_push

如果你配置好了 ssh 方式来 clone 代码,当然,git clone 命令用的 url 可以改成git@github.com:$USER/$PROJECT.git

完成这一步后,我们在本地看到的 remote 信息应该是这样的:

  • git remote -v
origin  git@github.com:daniel-hutao/devstream.git (fetch)
origin  git@github.com:daniel-hutao/devstream.git (push)
upstream    https://github.com/devstream-io/devstream (fetch)
upstream    no_push (push)

记住啰,你本地的代码变更永远只提交到 origin,然后通过 origin 提交 Pull Request 到 upstream。

5.3、第三步:更新本地分支代码

如果你刚刚完成 fork 和 clone 操作,那么你本地的代码肯定是新的。但是“刚刚”只存在一次,接着每一次准备开始写代码之前,你都需要确认本地分支的代码是新的,因为基于老代码开发你会陷入无限的冲突困境之中。

  • 更新本地 main 分支代码:
git fetch upstream
git checkout main
git rebase upstream/main

当然,我不建议你直接在 main 分支写代码,虽然你的第一个 PR 从 main 提交完全没有问题,但是如果你需要同时提交2个 PR 呢?总之鼓励新增一个 feat-xxx 或者 fix-xxx 等更可读的分支来完成开发工作。

  • 创建分支
git checkout -b feat-xxx

这样,我们就得到了一个和上游 main 分支代码一样的特性分支 feat-xxx 了,接着可以开始愉快地写代码啦!

5.4、第四步:写代码

没啥好说的,写就是了,写!

5.5、第五步:Commit 和 Push

  • 通用的流程:
git add <file>
git commit -s -m "some description here"
git push origin feat-xxx

当然,这里大家需要理解这几个命令和参数的含义,灵活调整。比如你也可以用git add --all完成 add 步骤,在 push 的时候也可以加-f参数,用来强制覆盖远程分支(假如已经存在,但是 commits 记录不合你意)。但是请记得git commit-s参数一定要加哦!

如果你习惯用 IDE 来 commit,当然也没有任何问题,像这样:

这里要注意 commit message 的规范,可能每个开源项目的要求不尽相同,比如 DevStream 的规范是类似这样的格式:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

举几个例子:

  • feat: some description here
  • docs: some description here
  • fix: some description here
  • fix(core): some description here
  • chore: some description here
  • ...

commit 和 push 两个步骤可以在 IDE 里一步到位,也可以分开,我习惯分开操作,给自己多一些余地。另外,我更习惯命令行操作:

  • git push origin feat-1
Counting objects: 80, done.
Delta compression using up to 10 threads.
Compressing objects: 100% (74/74), done.
Writing objects: 100% (80/80), 13.78 KiB | 4.59 MiB/s, done.
Total 80 (delta 55), reused 0 (delta 0)
remote: Resolving deltas: 100% (55/55), completed with 31 local objects.
remote: 
remote: Create a pull request for feat-1 on GitHub by visiting:
remote:      https://github.com/daniel-hutao/devstream/pull/new/feat-1
remote: 
To github.com:daniel-hutao/devstream.git
 * [new branch]      feat-1 -> feat-1

到这里,本地 commits 就推送到远程了。

5.6、第六步:开一个 PR

在完成 push 操作后,我们打开 GitHub,可以看到一个黄色的提示框,告诉我们可以开一个 Pull Request 了:

如果你没有看到这个框,也可以直接切换到 feat-1 分支,然后点击下方的“Contribute”按钮来开启一个 PR,或者直接点 Issues 边上的 Pull requests 进入对应页面。

  • Pull Request 格式默认是这样的:

这里我们需要填写一个合适的标题(默认和 commit message 一样),然后按照模板填写 PR 描述。PR 模板其实在每个开源项目里都不太一样,我们需要仔细阅读上面的内容,避免犯低级错误。

比如 DevStream 的模板里目前分为4个部分:

  1. Pre-Checklist:这里列了3个前置检查项,提醒 PR 提交者要先阅读 Contributing 文档,然后代码要有完善的注释或者文档,尽可能添加测试用例等;
  2. Description:这里填写的是 PR 的描述信息,也就是介绍你的 PR 内容的,你可以在这里描述这个 PR 解决了什么问题等;
  3. Related Issues:记得吗?我们在开始写代码之前其实是需要认领 issue 的,这里要填写的也就是对应 issue 的 id,假如你领的 issue 链接是 https://github.com/devstream-io/devstream/issues/796,并且这个 issue 通过你这个 PR 的修改后就完成了,可以关闭了,这时候可以在 Related Issues 下面写“close #796”;
  4. New Behavior:代码修改后绝大多数情况下是需要进行测试的,这时候我们可以在这里粘贴测试结果截图,这样 reviewers 就能够知道你的代码已经通过测试,功能符合预期,这样可以减少 review 工作量,快速合入。

这个模板并不复杂,我们直接对着填写就行。

  • 比如:

然后点击右下角“Create pull request”就完成了一个 PR 的创建了。不过我这里不能去点这个按钮,我用来演示的修改内容没有意义,不能合入上游代码库。不过我还是想给你看下 PR 创建出来后的效果,我们以 pr655 为例吧:

这是上个月我提的一个 PR,基本和模板格式一致。除了模板的内容,可能你已经注意到这里多了一个 Test 小节,没错,模板不是死的,模板只是为了降低沟通成本,你完全可以适当调整,只要结果是“往更清晰的方向走”的。我这里通过 Test 部分添加了本地详细测试结果记录,告诉 reviewers 我已经在本地充分测试了,请放心合入。

提交了 PR 之后,我们就可以在 PR 列表里找到自己的 PR 了,这时候还需要注意 ci 检查是不是全部能够通过,假如失败了,需要及时修复。以 DevStream 为例,ci 检查项大致如下:

5.7、第七步:PR 合入

如果你的 PR 很完美,毫无争议,那么过不了太长时间,项目管理员会直接合入你的 PR,那么你这个 PR 的生命周期也就到此结束了。

但是,没错,这里有个“但是”,但是往往第一次 PR 不会那么顺利,我们接下来就详细介绍一下可能经常遇到的一些问题和对应的解决办法。

六、我提交了一个 PR,然后遇到了问题 A,B,C,D,E,F,G,...

以上是关于如何参与开源项目 - 细说 GitHub 上的 PR 全过程的主要内容,如果未能解决你的问题,请参考以下文章

如何在Github上参与一个开源项目

如何在Github上参与一个开源项目

工程师参与开源的好处与挑战以及管理开源项目的那些事儿

如何参与一个开源项目!

如何参与开源项目

吴晟:如何做好一个开源项目?怎样参与开源项目?