GitHub CLI 三角工作流全面解析

本文详细介绍了GitHub CLI如何支持三角工作流程,包括Git基础概念、分支配置方法、多远程仓库设置以及gh pr命令的改进,帮助开发者更高效地管理代码协作和版本控制。

GitHub CLI 如何支持三角工作流程

大多数开发者都熟悉标准的 Git 工作流程:创建分支、进行更改,然后将这些更改推送回主仓库的同一分支。Git 称之为集中式工作流程。这种方式简单直接,适用于许多项目。

然而,有时您可能希望将不同分支的更改直接拉取到您的特性分支,以帮助保持分支更新,而无需不断合并或变基。但您仍希望将本地更改推送到自己的分支。这就是三角工作流程的用武之地。

可能有些人已经在使用三角工作流程,甚至没有意识到。当您分叉一个仓库,为您的分叉做出贡献,然后向原始仓库打开拉取请求时,您就在使用三角工作流程。虽然这在 github.com 上可以无缝工作,但使用 GitHub CLI 时,这个过程并不总是顺畅。

GitHub CLI 团队最近进行了改进(在 v2.71.2 版本中发布),以更好地支持这些三角工作流程,确保 gh pr 命令与您的 Git 配置顺畅配合。因此,无论您是使用集中式工作流程还是更复杂的三角工作流程,GitHub CLI 都能更好地满足您的需求。

如果您已经熟悉 Git 如何处理三角工作流程,可以跳过前面的内容,直接了解如何使用 gh pr 命令处理三角工作流程。否则,让我们深入了解 Git 和 GitHub CLI 历史上的差异,以及在我们首次请求四年半后,我们终于解锁了在 GitHub CLI 中使用三角工作流程管理拉取请求的功能。

Git 基础课程

为了为我们设定的目标提供框架,首先了解一些 Git 基础知识非常重要。Git 的核心是一种在仓库上存储和编目更改并在该仓库的副本之间通信这些更改的方式。这种工作流程通常如下所示:

图 1:典型的 git 分支设置

该图的构建块说明了两个您可能每天使用的重要 Git 概念:引用(ref)和推送/拉取(push/pull)。

引用(Refs)

引用是指向仓库和分支的指针。它有两个部分:远程(remote),通常是像 origin 或 upstream 这样的名称;和分支(branch)。如果远程是本地仓库,则为空。因此,在上面的示例中,紫色框中的 origin/branch 是一个远程引用,指向名为 origin 的仓库上一个名为 branch 的分支;而绿色框中的 branch 是一个本地引用,指向本地机器上一个名为 branch 的分支。

在使用 GitHub 时,远程引用通常是您托管在 GitHub 上的仓库。在上图中,您可以将紫色框视为 GitHub,绿色框视为您的本地机器。

推送和拉取(Pushing and pulling)

推送和拉取指的是相同的操作,但从两个不同的角度。您是推送还是拉取取决于您是发送还是接收更改。我可以将一个提交推送到您的仓库,或者您可以从我的仓库拉取该提交,并且对该操作的引用是相同的。

为了消除歧义,我们将不同的引用称为 headRef 或 baseRef,其中 headRef 是发送更改(推送它们),baseRef 是接收更改(拉取它们)。

图 2:为推送/拉取操作消除 headRef 和 baseRef 的歧义

在处理分支时,我们通常将其拉取操作的 headRef 称为其 pullRef,将其推送操作的 baseRef 称为其 pushRef。这是因为在这些情况下,工作分支是拉取的 baseRef 和推送的 headRef,所以它们已经消除了歧义。

@{push} 修订语法

事实证明,Git 有一个方便的内置工具来引用分支的 pushRef:@{push} 修订语法。您通常可以通过运行以下命令来确定分支的 pushRef:

1
git rev-parse --abbrev-ref @{push}

如果能够确定,这将产生一个人类可读的引用,如 origin/branch

拉取请求(Pull Requests)

在 GitHub 上,拉取请求是将更改从一个引用集成到另一个引用的提议。特别是,它们在实际执行集成操作(通常称为合并)之前充当一个简单的“暂停”,当更改从一个引用推送到另一个引用时。这个暂停允许人类(代码审查)和机器人(GitHub Copilot 审查和 GitHub Actions 工作流程)在更改集成之前检查代码。拉取请求的名称 specifically 来自这种语言:您是在请求一个引用将您的更改拉取到自身中。

图 3:演示 GitHub 拉取请求如何对应推送和拉取

常见的 Git 工作流程

现在您了解了基础知识,让我们谈谈我们通常每天使用 Git 的工作流程。

集中式工作流程是大多数人与 Git 和 GitHub 交互的方式。在这种配置中,任何给定的分支都推送和拉取自具有相同分支名称的远程引用。对于我们大多数人来说,这种类型的配置在克隆仓库和推送分支时默认设置。这是图 1 中所示的情况。

相比之下,三角工作流程推送到和拉取自不同的引用。这种配置的一个常见用例是直接从远程仓库的默认分支拉取到您的本地特性分支,消除了在您的特性分支上运行诸如 git rebase <default>git merge <default> 之类的命令以确保您正在处理的分支始终与默认分支保持最新的需要。然而,在推送更改时,此配置通常会推送到与特性分支同名的远程引用。

图 4:并置集中式工作流程和三角工作流程。

当考虑拉取请求时,我们完成了三角形:headRef 是本地引用的 pushRef,baseRef 是本地分支的 pullRef:

图 5:三角工作流程

我们可以更进一步,使用不同的远程设置三角工作流程。当您在分叉上进行开发时,这种情况最常见。在这种情况下,您通常为分叉和源远程指定不同的名称。我将为分叉使用 origin,为源使用 upstream,因为这些是在这些设置中常用的名称。这与上面的三角工作流程功能完全相同,但 pushRef 和 pullRef 上的远程和分支不同:

图 6:并置三角工作流程和具有不同远程(例如分叉)的集中式工作流程

使用 Git 配置文件设置三角工作流程

有两种主要方法可以使用 Git 配置(通常在 .git/config.gitconfig 文件中定义)设置三角工作流程。在解释这些之前,让我们看一下在集中式工作流程中,仓库的 .git/config 文件中相关部分的典型配置:

1
2
3
4
5
6
7
8
9
[remote "origin"]
    url = https://github.com/OWNER/REPO.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "default"]
    remote = origin
    merge = refs/heads/default
[branch "branch"]
    remote = origin
    merge = refs/heads/branch

图 7:在 .git/config 中找到的典型 Git 配置设置

[remote "origin"] 部分将位于 github.com/OWNER/REPO.git 的 Git 仓库命名为 origin,因此我们可以在其他地方通过该名称引用它。我们可以在特定 [branch] 配置中看到该引用用于默认分支和分支分支的 remote 键。此键与分支名称结合,通常构成分支的 pushRef:在此示例中,它是 origin/branch

remote 和 merge 键组合构成分支的 pullRef:在此示例中,它是 origin/branch

设置三角分支工作流程

组装三角工作流程的最简单方法是将分支的 merge 键设置为不同的分支名称,如下所示:

1
2
3
[branch "branch"]
    remote = origin
    merge = refs/heads/default

图 8:在 .git/config 中找到的三角分支的 Git 配置

这将导致分支 pullRef 为 origin/default,但 pushRef 为 origin/branch,如图 9 所示。

图 9:三角分支工作流程

设置三角分叉工作流程

使用三角分叉需要比三角分支更多的自定义,因为我们正在处理多个远程。因此,我们的 Git 配置中的远程看起来与之前在图 7 中显示的不同:

1
2
3
4
5
6
[remote "upstream"]
    url = https://github.com/ORIGINALOWNER/REPO.git
    fetch = +refs/heads/*:refs/remotes/upstream/*
[remote "origin"]
    url = https://github.com/FORKOWNER/REPO.git
    fetch = +refs/heads/*:refs/remotes/origin/*

图 10:在 .git/config 中找到的多远程 Git 设置的 Git 配置

Upstream 和 origin 是在这种构造中最常用的名称,所以我在这里使用了它们,但它们可以命名为任何您想要的名称¹。

然而,在 upstream 和 origin 之间切换分支的 remote 键实际上不会设置三角分叉工作流程——它只会设置具有这些远程之一的集中式工作流程,如图 6 中所示的集中式工作流程。幸运的是,有两个常见的 Git 配置选项可以改变这种行为。

设置分支的 pushremote

分支的配置有一个名为 pushremote 的键,它完全按照名称所示:配置分支将推送到的远程。使用 pushremote 的三角分叉工作流程配置可能如下所示:

1
2
3
4
[branch "branch"]
    remote = upstream
    merge = refs/heads/default
    pushremote = origin

图 11:在 .git/config 中找到的使用 pushremote 的三角分叉的 Git 配置

这组装了我们在图 12 中看到的三角分叉仓库。pullRef 是 upstream/default,由 remote 和 merge 键组合确定,而 pushRef 是 origin/branch,由 pushremote 键和分支名称组合确定。

图 12:三角分叉工作流程

设置仓库的 remote.pushDefault

要将仓库中的所有分支配置为具有与您在图 12 中看到的相同行为,您可以改为设置仓库的 pushDefault。其配置如下:

1
2
3
4
5
[remote]
    pushDefault = origin
[branch "branch"]
    remote = upstream
    merge = refs/heads/default

图 13:在 .git/config 中找到的使用 remote.pushDefault 的三角分叉的 Git 配置

这组装了与上面图 12 相同的三角分叉仓库,但这次 pushRef 由 remote.pushDefault 键和分支名称组合确定,结果是 origin/branch

当同时使用分支的 pushremote 和仓库的 remote.pushDefault 键时,Git 将优先解析分支的配置而不是仓库的配置,因此设置在 pushremote 上的远程将覆盖设置在 remote.pushDefault 上的远程。

更新 gh pr 命令集以反映 Git

以前,gh pr 命令集没有以与 Git 相同的方式解析 pushRefs 和 pullRefs。这是由于技术设计决策使得此更改既困难又复杂。我们不讨论这种复杂性——这本身就是一个足够大的话题,足以写一整篇文章——我将在这里重点介绍您现在可以使用更新后的 gh pr 命令集做什么。

如果您按照上述方式设置三角 Git 工作流程,我们将根据您的 Git 配置自动解析 gh pr 命令。

更具体地说,当尝试解析分支的拉取请求时,GitHub CLI 将首先尊重 @{push} 解析到的内容(如果它确实解析了)。然后它将回退到尊重分支的 pushremote,如果未设置,最后查找仓库的 remote.pushDefault 配置设置。

这意味着 CLI 假定您的分支的 pullRef 是拉取请求的 baseRef,而分支的 pushRef 是拉取请求的 headRef。换句话说,如果您配置了 git pullgit push 工作,那么 gh pr 命令应该就能工作。² 下面的图表,是图 5 的通用版本,很好地展示了这一点:

图 14:GitHub CLI 支持的关于分支 pullRef 和 pushRef 的三角工作流程。这是图 5 的通用版本

结论

我们不断努力改进 GitHub CLI,并希望 GitHub CLI 的行为能够合理地反映 Git 的行为。这是一个团队努力——每个人都为理解、审查和测试代码以启用此增强的 gh pr 命令集功能做出了贡献。

如果没有我们贡献者的支持,这也是不可能实现的,因此我们向他们表示感谢:

  • @Frederick888 打开了原始的拉取请求
  • @benknoble 为拉取请求审查和反馈提供支持
  • @phil-blain 在原始问题上强调了我们在本文中讨论的配置
  • @neutrinoceros 和 @rd-yan-farba 报告了团队在 v2.66.1 中修复的几个错误
  • @pdunnavant 报告了我们在 v2.71.1 中修复的错误
  • @cs278 报告了我们在 v2.71.2 中修复的错误

CLI 对三角工作流程的原生支持经过了 4.5 年的努力,我们很自豪能够为社区提供此更新。

GitHub CLI 团队 @andyfeller, @babakks, @bagtoad, @jtmcg, @mxie, @RyanHecht, and @williammartin


¹ gh 中的某些命令对远程名称有特定偏好,将按以下顺序解析远程:upstream, github, origin, <其他远程不稳定排序>。有一个便利命令您可以运行来覆盖此行为:* gh repo set-default [<repository>] 以覆盖上述默认行为并优先将 <repository> 解析为默认远程仓库。

² 如果您发现某个 git 配置不起作用,请在 OSS 仓库中打开一个问题,以便我们修复它。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计