Featured image of post GitHub CLI 现支持三角工作流:提升开发效率的新方式

GitHub CLI 现支持三角工作流:提升开发效率的新方式

本文详细介绍了GitHub CLI最新版本如何支持三角工作流配置,包括分支管理和多远程仓库操作,让开发者能够更高效地处理代码合并和拉取请求,提升团队协作效率。

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),通常是像 originupstream 这样的名称;和分支(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 工作流)在代码集成之前检查代码。拉取请求(pull request)这个名字正是来源于这种语言:您是在请求一个引用拉取您的更改到自身。

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

常见的 Git 工作流

既然您已经了解了基础知识,让我们谈谈我们通常每天使用 Git 的工作流。

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

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

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

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

图 5:三角工作流

我们可以更进一步,使用不同的远程设置三角工作流。这最常发生在您在分叉(fork)上进行开发时。在这种情况下,您通常为分叉和源远程赋予不同的名称。我将使用 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,以便我们可以在其他地方通过该名称引用它。我们可以在 defaultbranch 分支的特定 [branch] 配置中的 remote 键中看到该引用。此键与分支名称结合,通常构成分支的 pushRef:在此示例中,它是 origin/branch

remotemerge 键组合构成分支的 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 配置

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

然而,在 upstreamorigin 之间切换分支的 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,由 remotemerge 键组合确定,而 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 命令集解析 pushRef 和 pullRef 的方式与 Git 不同。这是由于技术设计决策使得此更改既困难又复杂。我们不讨论这种复杂性——这本身就是一个足够大的话题,足以单独写一篇文章——我将重点介绍您现在可以使用更新后的 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 设计