Skip to content
This page has been auto-translated and may contain errors.View in English

拉取请求流程

docs.scrimba.com

你一直在自己的分支上开发一项功能,现在已准备好。项目是共享的:其他人也会提交代码,所以你不会直接推送到 main 并冒险。你希望你的更改被审核、讨论(如有必要),并以团队其他成员能看到的方式合并。从你机器上的分支到代码合并到 main 的整个路径就是拉取请求流程,这几乎是 GitHub 上每个团队获取更改的方式。其中 Git 部分只使用前面章节中的命令:分支、提交和推送:

bash
$ git switch -c add-five-day-forecast
Switched to a new branch 'add-five-day-forecast'
$ git add src/forecast.js
$ git commit -m "Add five-day forecast panel"
$ git push -u origin add-five-day-forecast
Branch 'add-five-day-forecast' set up to track 'origin/add-five-day-forecast'.

这个推送将你的分支发送到 GitHub。还没有合并任何东西,甚至还没有提议进行审核。下一步,打开拉取请求,是在 GitHub 网站上进行的。

打开拉取请求时发生的事情

在 GitHub 上,进行上述推送后,你通常会看到一个横幅,提供将你的新分支与 main 进行比较的选项。点击它,写下标题和简短描述说明了什么以及为什么改变,然后打开它创建的页面。那个页面就是一个**拉取请求**,通常缩写为 PR:一个将一个分支合并到另一个分支的请求,并附有一个持续的对话。

拉取请求提议进行一次合并并等待批准。有人(可能是你,也可能是队友)阅读这个更改,留下评论,只有当它看起来合适时才合并它。在此之前,你的分支和 main 保持完全不变。

你不必等到功能完成才打开 PR。许多团队会提前打开一个并将其标记为草稿,这表示"还未准备好进行审核",同时仍然为审核者提供一个地方来评论工作方向。良好的描述会链接任何相关问题并说明你测试的内容,这样审核者就不必仅从 diff 来重构这些信息。

GitHub 为每个拉取请求保留一个 ref,即使在你将 PR 的分支添加到自己的存储库之前也是如此:refs/pull/<number>/head。直接获取它以在你的机器上检出队友的 PR,而无需先将他们的 fork 添加为远程。

bash
$ git fetch origin pull/42/head:pr-42
From github.com:mara-chen/weather-app
 * [new ref]         refs/pull/42/head -> pr-42
$ git switch pr-42
Switched to branch 'pr-42'

现在 pr-42 是从拉取请求 42 构建的真实本地分支,可以在你留下任何评论之前运行和测试。

Juno打开拉取请求时发生的事情 拉取请求是在推送后在 GitHub 上打开的、请求将你的分支合并到另一个分支的请求。有人先审核这个更改,只有那样它才会合并。日常循环是:创建分支、推送、打开拉取请求、获取审核、合并。
Juno打开拉取请求时发生的事情 PR 提议进行一次合并并围绕它进行对话,你可以提前将其打开为草稿,以便在工作完成前获得反馈。编写描述时要像审核者真的会读它一样:什么改变了、为什么改变了,以及你测试了什么。
Juno打开拉取请求时发生的事情 GitHub 为每个拉取请求保留一个隐藏的 ref,refs/pull/<number>/head,所以你可以用 git fetch origin pull/42/head:pr-42 直接将队友的 PR 获取到你的机器上,而不是将他们的 fork 添加为远程。在你留下第一条评论之前在本地测试这个更改。

Fork vs 分支

当你对存储库有写入权限时(这是你自己团队项目的常见情况),从分支打开拉取请求是可行的。对你无法写入的项目做出贡献需要额外的一步:一个**fork**,即你在自己的 GitHub 账户下的存储库副本。

你在你的 fork 中分支和提交的方式完全相同,就像在你拥有的项目中一样,推送到你的 fork,然后从你的 fork 分支打开拉取请求回到原始存储库。GitHub 在两个存储库之间进行比较的方式与它在一个存储库内比较两个分支的方式相同,所以审核和合并流程看起来是相同的。

如果你计划向已 fork 的项目发送多个 PR,值得添加原始存储库作为第二个远程,通常命名为 upstream,而 origin 继续指向你自己的 fork。运行 git fetch upstream 并将 upstream/main 合并到你的分支中,以赶上自 fork 后已向前推进的项目,使用 Remotes and GitHub 章节涵盖的任何远程的相同步骤。

JunoFork vs 分支 当你对存储库有写入权限时,直接在分支上工作,你自己团队的项目。当你没有时则 fork:一个 fork 是你对他人存储库的副本,你在这个副本内分支和推送,然后打开拉取请求回到原始分支。
JunoFork vs 分支 Fork 是你账户下存储库的完整副本,用于对你无法写入的地方做出贡献。你在你的 fork 中像平常一样分支、提交和推送,GitHub 以与它在一个项目内比较两个分支相同的方式比较你的 fork 分支与原始存储库。
JunoFork vs 分支 在 forked 项目上保持两个远程:origin 用于你自己的 fork,upstream 用于原始存储库,这样你可以获取并合并其更改。PR 本身仍然将你的 fork 分支与原始存储库的基础分支进行比较。

通过审核获得你的拉取请求

打开 PR 后,它就成为关于 diff 的持续对话。审核者阅读更改,在特定行留下评论,然后批准或请求更改。当请求更改时,继续提交和推送到同一分支:每次推送都会更新同一个 PR,而不是创建一个新的。一旦审核者批准并且所有必需的检查通过,PR 就可以合并了。

GitHub 将审核跟踪为三种状态之一:已批准、请求更改或已评论(没有任何决议的反馈)。在解决评论时回复评论,并将对话标记为已解决,以便审核者一眼就能看到还剩下什么。如果你推送针对特定评论的修复,在你的提交消息或回复中说明这一点会很有帮助,因为审核长 PR 的人会扫描正是这个。

PR 需要多少个批准,以及是否每个评论线程都必须标记为已解决才能解锁合并按钮,通常是在良好运营的项目上附加到基础分支本身的规则。

Juno通过审核获得你的拉取请求 审核者在你的拉取请求上留下评论,可能要求进行更改。向同一分支提交并推送更多工作来解决它,PR 会自动更新自己。一旦有人批准,就会进行合并。
Juno通过审核获得你的拉取请求 审核落在三种状态之一:已批准、请求更改或已评论。推送修复到同一分支以更新 PR,在解决评论时回复,并在长线程中保持审核者的方向感。
Juno通过审核获得你的拉取请求 审核状态驱动合并按钮是否解锁,但确切的标准、需要多少个批准、每个评论线程是否必须解决,通常是在良好运营的项目上附加到基础分支本身的规则。

为什么拉取请求不会合并

有时 PR 上的合并按钮呈灰色,GitHub 显示一条消息来代替它:"This branch has conflicts that must be resolved"(此分支有必须解决的冲突)或"This branch is out of date with the base branch"(此分支与基础分支不同步)。两条消息都描述了一个正常的、可修复的状态:main 自你分支以来已经向前推进,通常是因为其他人的 PR 在此期间合并了,你的分支需要赶上才能让 GitHub 干净地合并两者。这种情况会发生在任何有多个贡献者的项目上,所以要预料到它而不是害怕它。

用相同的方式在本地修复它,就像你会将任何两个分支合并在一起的方式:

bash
$ git switch add-five-day-forecast
$ git fetch origin
From github.com:mara-chen/weather-app
   9f8e7d6..b7c9e21  main       -> origin/main
$ git merge origin/main

如果没有重叠,合并会自动完成,你推送,PR 更新,按钮解锁。如果两边的相同行改变了,Git 会在文件中标记冲突,你需要解决它,完全就像 Merging and conflicts 中涵盖的那样:打开文件,编辑掉冲突标记到你想要的结果,然后 git addgit commit 来完成合并,并再次推送。

卡住的拉取请求需要赶上或解决其冲突。

在保持打开超过一两天的 PR 上,不时将 main 合并到你的分支,而不是等待按钮阻止你,是值得的。早一天过期的分支通常完全没有冲突地合并;三周过期的分支有更多的表面让两个更改在同一行上碰撞。GitHub 自己在 PR 页面上的"Update branch"(更新分支)按钮可以为你执行这个相同的合并,当没有任何东西需要手动解决时。

一些团队将功能分支 rebase 到 main 上,而不是合并它,以保持最终历史线性,而不是为每个 PR 显示合并提交。那个权衡就是 Merging and conflicts 中涵盖的相同的:rebasing 重写你的分支提交,只要你是唯一一个有它们副本的人,这是安全的,所以一个只存在于你的机器上及其 PR 的单独功能分支是一个合理的候选。Rebase 其他人已经 pull 并构建在其上的分支是不行的,因为它重写了他们依赖的历史。

Juno为什么拉取请求不会合并 灰显的合并按钮意味着你的分支落后于 main 或与它有冲突。没有任何损坏的。切换到你的分支,获取,并将 main 合并进来,像在其他地方一样解决任何冲突,然后再次推送。
Juno为什么拉取请求不会合并 每隔一段时间将 main 合并到长期运行的分支中,而不是等待 GitHub 阻止合并按钮。更小、更频繁的赶上意味着更小的冲突,GitHub 的"Update branch"按钮可以为你处理没有冲突的情况。
Juno为什么拉取请求不会合并 合并或 rebase 你的分支到 main 都可以修复卡住的 PR,一个单独的功能分支是安全 rebase 的,因为没有人有它的提交副本。一旦其他人已经 pull 了一个分支,坚持合并它。

拉取请求在内部是什么

拉取请求不是 Git 对象,也没有 git 命令创建它。Git 和 GitHub 不是同一件事:Git 只知道提交、分支和其他 ref(ref 是指向提交的名称)。GitHub 在它比较的两个 ref 之上构建整个 PR 页面、评论和合并按钮:你的分支(head)和你要合并到其中的分支(base,通常是 main)。这就是为什么你在 GitHub 的网站上(或通过 GitHub 自己的命令行工具或 API)打开拉取请求,而从不使用普通 git 命令。

因为 PR 存在于 GitHub 上,与存储库的历史分离,它的评论和审核对话不会随着提交一起传播。如果你曾经将项目移到不同的主机,每个提交都会与你保持不变,但拉取请求及其讨论会留在 GitHub 上,因为它们从未是 Git 数据的一部分。

GitHub 通过找到合并基础来计算 PR 的 diff,即 head ref 和 base ref 共享的最近提交,并将 head 与那个点进行比较,而不是与 main 现在的样子进行比较。每次推送到分支都会向同一个 PR 添加一个新版本,而不是创建一个新的比较。**分支保护**是坐在所有这一切之上的策略层:你(或你的组织)附加到分支的规则,通常是 main,可以要求通过状态检查、要求一定数量的批准审核,并阻止直接推送,以便每个更改都被强制通过拉取请求。Git 本身不强制这些任何事情。它会让你直接从你的终端推送到 main,直到 GitHub 的规则拒绝请求。

Juno拉取请求在内部是什么 拉取请求是分层在 Git 之上的 GitHub 功能:Git 跟踪提交和分支,GitHub 通过比较其中两个来添加 PR 页面和合并按钮。在你的头脑中分开 Git 和 GitHub 可以解释很多看起来像魔法的东西。
Juno拉取请求在内部是什么 PR 将你的分支与基础分支进行比较,并完全存在于 GitHub 上,所以它的评论和审核历史如果存储库曾更改主机就不会移动。提交本身是真正 Git 的唯一部分。
Juno拉取请求在内部是什么 PR 是 GitHub 将 head ref 与来自其共享合并基础的 base ref 进行比较,并通过每次推送进行更新。分支保护是在顶部的强制策略,要求检查或审核才能合并。Git 本身不强制这些任何事情:它会让你直接推送到 main,直到 GitHub 的规则停止它。