记我的第一次GitHub Pull Request(完结篇)Fork与PR

2020-03-08

Previously On This Serie

在本系列的前面3篇文章《提交与分支》《合并与冲突处理》《拉取与推送》中我们先后介绍了『提交』(git commit),『分支』(git branch),『合并』(git merge),『冲突处理』(git add),『拉取』(git fetch)和『推送』(git push)的概念,每个概念都配以特定情形的模拟作为示例,我们记得,提交(Commit)其实就是快照,准确地说是记录提交版本和上一个版本的变更,而分支(Branch)则是允许若干个基于不同提交起点的开发任务得以同时进行,合并(Merge)用于将一个分支自分叉节点起所做的改变全部在当前分支上重演(replay)一遍,合并期间遇到的分支的发展过程的差异使得重演不能顺利进行(到底是选择当前分支到分叉提交节点的变化进行重演还是选择要合并分支对分叉提交节点的变化进行重演?),所以需要解决冲突,解决之后(git add, git commit)提交了事,而拉取则是使本地的远程分支(再强调一遍远程分支不是『远程的分支』,而仅仅是一个名字)跟上时代的步伐,与服务器上的最新的分支进行同步,推送则是试图将本地较新的分支合并到服务器上较旧的分支上去.

关于这个

关于Git基础知识

记得本系列的第一篇文章本来就是想记录我的第一个被Merge的GitHub Pull Request,但是当时我想先介绍一些基本概念,后来干脆就试图从比较基本的角度开始逐步地引入一些Git中的基本概念,到目前为止,我相信我是完成了一部分了,更多的关于Git的内容推荐参阅挂载在git-scm.com网站上的Pro Git Book.我们讲过的内容,不论是对于想要流畅地使用Git,还是对于熟练地参与到GitHub上开源项目来说,都远远不够!

关于本系列文章

需要注意的是,本系列文章最多只能算是娱乐读物,为的是实现概念上的讲解,而非成体系的、全面的学习,简单来说,我们写本系列的文章是为了引导读者主动去查阅更多地资料,去做更多的有效阅读,去使用Git,去找到自己感兴趣的开源项目然后做贡献,但是,由于作者能力有限,很遗憾地说,我们这几篇文章Covered的知识还是太少了,真的不能止步学习!

探索GitHub的世界

既然我们已经在《提交与分支》中已经介绍过GitHub是什么,这里就不再重复了.

Fork和Pull Request

什么是Pull Request呢?什么又是Fork呢?为什么git help命令打出的帮助文档并没有显示出有关这两个概念的内容?而且我们确实不像在前面三篇文章那样给他们找一些形象的中文词汇作为对应……

额…… 其实呢,Fork就是,额,Fork,就是叉子,想象欧洲人进食用到的叉子,把食物从餐盘里边叉出来,比如说你看在GitHub,Fork一个项目的时候,页面会变成这样:

Fork一个项目的时候

估计GitHub把这个功能叫做Fork就真的是拿叉子去叉食物的意思.

去Fork一个项目

忘了说了,Fork其实不是git软件的功能,是GitHub网站的功能,所以你在git的帮助文档找不到关于Fork的内.我们先注册一个GitHub账号,注册之后登录,你会看到:

GitHub登录后的视图

类似这样的页面,其中呢左边会是你创建过的仓库,GitHub中仓库(Repository)的概念其实和Git中仓库的概念是一样的,所以我们在《拉取与推送》这篇文章里才以GitHub作为例子来展示,GitHub顾名思义就是Git的Hub,很好理解.中间呢,是你关注过的用户的动态,要关注一个用户你就点击他/她的头像,然后进去之后点Follow之类的按钮,一般都是这样,然后这个用户有了什么动态GitHub就会展示给你,最右边呢是GitHub向你推荐的一些开源项目,其中不一定是你关注过的,可以是根据流行趋势来推荐,也可以是根据分析你的浏览记录和关注记录来实现的智能推荐,不是很懂.

看到页面最上边有Pull Request的按钮,先不管它,先看Expore,点进去,看到这些:

GitHub Explore页面

看到中间那行字——Here’s what we found based on your interests…,那中间那栏就是给你看的推荐内容了,而右边的就是Trending repositories,即最近正在流行的项目,我们会看到,在GitHub中会有 xxxx/yyyy 之类的名称出现,先说其中的xxxx是作者名,作者也可以是一个GitHub Organization,即一个组织,或者说一个团队,而yyyy则是项目名称.

比如说我们在这个页面看到的chatwoot/chatwoot,是一个叫做chatwoot的团队开发的叫做chatwoot的开源项目(且默认都是GitHub上的你能看到的开源的),右边那栏有一个项目叫做 tokyo-metropolitan-gov/covid19,是一个叫做tokyo-metropolitan-gov的组织(这个名字是指『东京市政厅』?)创建的项目名叫covid19的开源项目,简介是:東京都 新型コロナウイルス感染症対策サイト,好像是东京新型冠状病毒感染应对政策?

项目名称右边有一个星星符号,星星符号右边的多少多少k实际上是指有多少人正在关注这个项目(你也可以关注你感兴趣的项目),很简单点那个Star按钮就算是关注了,以后那个项目有什么变动GitHub会通知你.比如说chatwoot/chatwoot这个项目的关注度是3.7k,也就是说大概有叁仟柒佰左右个人正在关注着这个项目,并不是字面意义上的关注,并不是叁仟柒佰人都时刻盯着电脑屏幕看着这个项目的一举一动,而是至少有这么多人曾经点过这个Star按钮!

我们点进去,点chatwoot/chatwoot这个项目,看到这样的页面:

看到这样的页面点开一个项目后

第一行就找到了Fork按钮,Sponsor是赞助,就是捐钱给项目作者,Watch是密切关注!Unstar是不再关注,Fork就是我们本来要做的,等下点一下这个Fork按钮就算是任务完成了(开个玩笑),不过在那之前先不急,先看一下这个页面哈.

首先还是Fork旁边那些个按钮,右边的数字,很好理解吧?就是代表点过这些按钮还没点取消的人数,就是关注人数,比如说这里,有50人密切关注这个项目,3700左右的人关注这个项目,271人Fork过这个项目,就是这么简单.

然后项目全名charwoot/chatwoot下面有几个标签栏,我们先不急着看其他的,容易眼花,先看这个页面吧,首先下面有一行字,你已经知道这是项目简介了,右边是网址或者项目的文档网站,然后呢,下边就是标签了,标签嘛很好理解,作者觉得这个项目能和什么扯上关系,就加什么标签,没有什么一定之规.

再往下看,401 commits,项目组总共往GitHub的服务器提交过401次这个项目(我们假设他们没有做自架GitHub服务器),嗯,这里的提交呢其实就是我们第一篇《提交与分支》讲过的提交,由于Git的软件实现是公开的,所以GitHub有专门的程序可以在你push之后自动分析这个项目有多少次提交,就拿我们前面用的示例项目,有c1 c2 c3 c4’ c4 c5 c6 c7 ,我们数出8次提交,但是别忘了做过两次合并(其实是冲突处理过后的合并,第一次是在第二篇《合并与冲突处理》删除add函数的错误,第二次是在第三篇《拉取与推送》sam推送之前要把david的代码合并进来,所以learngit项目截止目前总共是有10次提交!因为git会自动为合并创建一次专门的提交,代表这个合并的过程也被记录了下来,只要提交了,只要不故意,Git几乎不会丢掉任何信息!

以及10个分支,就我所知learngit项目有2个,但是GitHub会显示只有1个是因为我们只推送了1个分支到GitHub,并且GitHub不会主动问你“你本地还有没有分支没推送给我?”.

然后嘛,packages其实是指maven或者gradle这类自动构建工具和包管理工具中的package,比如说pip install pandas这个命令就是给python安装pandas这个package,package你可以理解为编程语言的一种插件吧,一种扩展,一堆可复用的代码,API的实现等等.

10个releases,release其实就是最终发行版了,可以理解为作者正式发布的版本,以后要找开源软件在哪下载首先点releases,然后再去找其他地方,或者干脆用brew,apt-get之类的包管理工具安装.

50个贡献者,是说50个人贡献过这个项目,所有给这个项目提出过Pull Request(简称PR)并且PR被Merge(git merge,我们讨论过)的都算.右边是开源协议,感兴趣的可以点进去看一看,锻炼英语阅读理解能力的.

中间那个彩色条也是可以点的,可以点一下试试看,我们会看到大概是这个样子:

各编程语言代码数量占比

熟悉吧?其实都是各种带结构的语言的占比,毕竟GitHub不会统计像中文,英文,法语,日文等等这类非结构的语言的占比,是说这个项目所有的代码中大概有41.9%是Ruby语言,23.8%是Vue语言,23.1%是JavaScript语言(也可能是TypeScript),8.8%是CSS语言,2.0%是HTML语言,写Dockerfile的内容占到了大概0.3%,Shell脚本的内容占到了大概0.1%吧.还是比较全的.再点一下彩虹bar可以关闭彩虹bar.(也可以一直点下去)

看到了吗?下面就有个New pull request按钮,点这个就可创建PR了,不过很显然,我们不能就这样随意的创建PR(后边我还会介绍什么是PR),哪怕是自己感兴趣的项目(我讲这个项目只是为了介绍),也要仔细阅读文档,全面测试,认真观察和思考,读通代码之后再去PR,右边有几个按钮是创建和上传文件的,不过不建议你把GitHub当网盘用,Find file还有用些,不过不如把项目克隆到本地再查找文件.

右边绿色按钮就是克隆按钮了,点了之后是这样:

点了克隆按钮

会看到地址右边有一个『复制到剪贴板』按钮,点了就复制这个网址了,比如说我们在命令行运行

git clone https://github.com/chatwoot/chatwoot.git 

里面的网站就是这么复制出来的,Download ZIP嘛是下载项目的打包之后的文件,在Windows系统用的可能比较多一点,不过我们能用命令行就用命令行不是?还有个Open in Desktop按钮要求安装GitHub客户端,没装过不知道.再点一下绿色(现在应该变成深绿色)按钮收起小窗口.

下面这一行是项目最新动向,可以看到谁提交了,提交的摘要内容,PR的编号,最后提交的时间.

项目最新动向

最后提交的时间(Lastest commit x hours ago)也可以用来衡量一个项目的热度,一个项目如果一两年没人维护了,那基本是没人维护了,如果半年没人维护了,那很可能已经没人维护或者即将没人维护了,如果几个月没人维护了说明可能维护的频率比较低.当然很久不提交也不能说是没人维护,也可能是作者认为该项目所开发的软件各方面已经趋于稳定了,已经严格意义上的bug-free了(理论上一些函数式编程语言是可以写出软件上bug-free的软件的,是可以数学上形式化地证明的,这里我就不证了),不过我觉得那样应该把项目设为Archive才对.

下边这个列表是文件列表,以及每个文件最近的一次提交,代码的文件可能我们看不懂,我们就先看看熟悉的Markdown格式的文件吧,先在文件列表里边找到README.md,点它右边那行字,出现这个:

当你点击README.md右边那行字之后出现这个

有点类似我们见过的处理合并冲突的VS Code的界面,不过这里并不叫你来处理冲突的,这个页面显示的是该次提交和上次提交的差异,我们可以看到该次提交的提交id是c7a4550起头的,而该提交的上一次提交,也就是父提交,提交id是fd8e8c7起头的,这里显示的就是c7a4550这次提交提交的内容和fd8e8c7提交的内容的差异!

我们可以看到fd8e8c7那次提交提交了的55到59行的内容,也就是红色标记的,可以看到左边的-号,都被删掉了,而c7a4550这次提交则新增了现在的55行,也就是绿色标记的,可以看到左边的+号,往下翻页还可以看到更多更改.

点parent 右边的fd8e8c7,会打开fd8e8c7和fd8e8c7的父提交的差异页面,是这样的:

fd8e8c7和fd8e8c7的父提交2d32125的差异

可以看到页面的结构是相似的,commit右边变成了fd8e5c7c8….刚好就是fd8e8c7的全写,git语境下大家也都习惯用16进制的前若干位描述引用的,而2d32125是fd8e8c7的parent,实际上就是最开始的c7a4550的$\mathop{parent}^2$了.就这样一次次地点parent右边的链接可以看这个文件过去的一次次更改具体是怎样的,很方便.当然还可以看到这个更改是谁做出的,什么时候做出的.

点Browse files按钮回到刚才那个页面,再往下就是这个项目的介绍了,按照约定GitHub会把项目根目录下的README.md拿来做项目介绍页面,当然是经过一定的渲染的,我们看到的是这样:

chatwoot项目的README.md展示

还挺好看的哈.感兴趣的可以去多了解一下,顺便捐助或者贡献些代码什么的.我不是故意给这个项目做广告的,只是随便点了个拿来讲解GitHub的一些基本概念.

我们现在可以Fork自己感兴趣的项目,但是PR则是另一回事,如果自己不知道自己在做什么建议还是不要随便PR.这样Fork一个项目这个任务就算完成了.下面我以learngit项目作为示例来讲解Pull Request是怎样的.

来Pull我啊 来Pull我啊

首先,从字面意义上理解啊,Pull Request就算『请求拉取』的意思,我们先不看Request,Pull大家都知道吧?

在上一篇文章《拉取与推送》中,我们介绍了git pull命令,在sam想要推送自己写的compare函数时,服务器告诉sam,服务器已经有比sam本地的learngit1/a还要新的分支a了,sam本地的分支a比服务器上的分支a旧,所以sam不能推送到分支a,因为我们知道同一个分支内是不允许两个提交指向同一个父提交的,原理在第一篇《提交与分支》中我已经介绍过了,于是sam就把服务器上最新的分支a pull到了自己本地,把david写的div函数与自己写的compare函数整合起来(git add),创建一个新的提交(git commit),重新推送(git push)到服务器(GitHub).

等等!刚才我们说是服务器要sam pull的?服务器请求sam做这个pull的?(服务器:我有这样说吗?我有请求sam吗?)好像是这样的,服务器说sam本地的分支a的版本旧过服务器的分支a,所以跟sam所要先pull才能提交,噢,我们知道了:那又是谁让服务器的分支a的版本新过sam本地的分支a的呢?

是David!(不是$\mathop{David}$阶乘,后面那个是感叹号)

是David请求Sam Pull的!Pull Request是David发起的!

我们可以这么看,假如说Sam是项目的主管人,为了简单起见现在我们只假设learngit项目规定大家只在分支a上工作,如果有人把自己的新提交推送到服务器上,就会使服务器上的分支a指针后移,使得服务器的分支a的版本比所有人都新(我们假设这个人推送成功了),那么所有人的任何一个,如果还要继续参与这个项目,比如说还要向这个项目推送自己的更新,他就必须把,我们假设推送更新的哪个还是david,必须把david推的更新pull下来,这样david的主动推送等于说是像这个项目的所有人发了Pull Request! David在请求所有人Pull他刚刚推送的更新!

这样不太行哈.

这样,我们假设Sam还是项目主管人,这次david降级了,失去了随意推送的权限(因为刚才David擅自推送到主分支从而影响到所有人太过分了),David变成了平民,同时呢Sam决定这次将推送权限从所有人身上收回,比说直接就把项目团队其他人的公钥都删掉了,这是可以实现的.同时呢,Sam制定这么一个规章,团队里的所有人如果想要推送自己的更新,他自己必须:

  1. 先把项目克隆到自己的开发电脑上

  2. 所做更改必须有详细的文档,至少说清楚都改了些什么,为什么要这么改

  3. 通知项目主管人审阅自己的提交申请,然后项目主管人再决定要不要pull申请者的仓库并且合并到项目主管人控制的仓库中.

我们还假设这个团队所处的网络环境允许他们之间相互pull,事实上这在局域网中是可以实现的,可以参看Pro Git Book相关的内容,好像是搭建远程仓库那节吧.总之Sam的这种方案是可以实现的,在特定环境下.

这样问题不就解决了?!

再说Fork和Pull Request

其实上面这个过程并不适用于通过广域网工作的项目者,因为现在大多数电脑都没有公网IPv4地址的,所以让项目主管人去拉取每一个人的仓库,去审阅每个人的提交申请,很低效,于是GitHub就做了这么一个功能,首先GitHub用户可以在自己账户下创建仓库,创建了的仓库其实就是git仓库,就和我们本地用git命令创建的一样的,因此既然是git仓库当然也可以克隆,不过在GitHub的环境中仓库的克隆其实是叫做Fork,就是Fork,Fork之后相当于把原来的仓库克隆到自己账户名下一份,跟我们自己在本地或者在公网做的clone是一样的(或者差不多一样的,如果有差别那我不清楚),之后呢,推送请求或者拉取请求(都是一样的,推送最终还是要靠项目主管人通过拉取申请者的更新来实现)也是在GitHub这个平台创建,PR的创建就像我们在论坛网站写帖子一样方便,项目主管人只要登录GitHub就可以看到每个人对项目发起的Pull Request了,不用找单独的渠道来接收大家的Pull Request了,而且GitHub背靠微软这座大山,目前看来还算是可靠的.

就这样,Fork和Pull Request这样简单而有用的功能给GitHub吸引来了越来越多优质的项目和优秀的开发者,是的,优秀的产品就是这么简单,一些简单的功能,实现着最关键的的需求.

感谢

至此呢,PR系列(记我的第一次GitHub Pull Request)就算是完结了,也感谢大家一直以来的关注,感谢大家花时间阅读和思考,感谢在屏幕前默默关注着这个网站的你,我相信,一切的努力最终会化作盛开的果实,让我们带着美好的期许,不断地探索这个神奇的世界.

参考文献

[1] Git SCM

[2] Pro Git Book

概念普及githubgit

为你的网站添加Cache-Control

记我的第一次GitHub Pull Request(下篇)拉取与推送