推送其他人打开的拉取请求的便捷方式

Convenient way to push to a pull request opened by someone else

我是 GitHub 上一个项目的维护者,我们不断获得 PR。通常我使用 GitHub 显示的命令来获取和测试 PR:

git checkout -b USER-master master
git pull https://github.com/USER/REPO.git master

但是,当我想推送一个提交时,我需要输入:

git push https://github.com/USER/REPO.git USER-master:master

我正要创建一个可以像

一样使用的别名
git pr https://github.com/USER/REPO.git master

它创建了一个新分支(如 GitHub 所建议的那样)并设置该分支跟踪 PR 的上游。它允许简单调用 git push.

设置跟踪分支我试过:

git branch -u https://github.com/USER/REPO.git/master

得到:

error: the requested upstream branch 'https://github.com/USER/REPO.git/master' does not exist
hint: 
hint: If you are planning on basing your work on an upstream
hint: branch that already exists at the remote, you may need to
hint: run "git fetch" to retrieve it.
hint: 
hint: If you are planning to push out a new local branch that
hint: will track its remote counterpart, you may want to use
hint: "git push -u" to set the upstream config as you push.

我假设遥控器名称中的许多 / 混淆了 git,因为分支也由 /.

分隔

如果我用

添加一个新的遥控器,它就可以工作了
git remote add USER-REPO https://github.com/USER/REPO.git

但我想避免添加这么多遥控器。

git branch -u有什么想法吗?

TL;DR

您可能想要设置多个遥控器。但是,一旦这样做,事情就会变得一团糟。何时以及是否设置你从 PR 中创建的本地分支的上游由你决定;你可能根本不想这样做。您想要制作任何脚本的花哨程度也取决于您。您最终可能会得到一个 so-called 三角工作流 .

这有点复杂,所以让我们从这个开始:git push-u 选项设置一个 upstream。您自己的存储库中的每个分支名称都可以有一个 (1) 上游,或者根本没有上游;这些是你唯一的选择。

Git 的术语(以及 GitHub 的术语)在这里变得混乱。我上面说的upstream是一个branch设置; git branch --set-upstream-to 设置此设置; git branch --unset-upstream 删除此设置。在所有情况下,此设置都适用于您的存储库中的分支名称,例如,在您笔记本电脑上的存储库中。这是一个本地存储库,而不是 GitHub 上的存储库。

当我们使用 GitHub 时,我们会得到称为 拉取请求 的东西。这些不是 Git 本身的一部分! Git 有一个命令,git request-pull,但它只生成一封电子邮件。它甚至不发送电子邮件,它只是打印出一个,适合你放入一些email-generating软件。因此 GitHub 拉取请求 特定于 GitHub

当我们 get 拉取请求时,我们通常希望自己的 Git 在我们的笔记本电脑上与 我们的 [=311] 一起工作=] 存储库,连接 Git 集线器。我们可能会在 GitHub:https://github.com/me/my.git 或其他任何地方存储 我们的 存储库的副本。我们倾向于称之为 origin,因为这是第一个标准 remote.

我们已经有了三四位术语:

  • 分支名称: 这是提交的名称。每个存储库,无论它存储在哪里,都有 自己的 分支名称。 您的 存储库中的一个分支名称在您的笔记本电脑上有一个特殊的功能,即当您将它与 git checkoutgit switch 一起使用时,您会结束 在树枝上。这意味着当您进行新提交时,Git 将 branch-name 存储 new 提交哈希 ID。新的提交以通常的提交方式向后链接到 最新的提交,在你刚刚创建新提交之前。

  • upstream: 这是一个与我们(笔记本电脑)存储库中的分支名称关联的字符串。它实际上是一个 two-part 字符串——我们稍后会看到更多——但我们通常将其视为一个简单的字符串,例如 origin/main 代表 mainorigin/master 代表 masterorigin/feature/tall feature/tall

  • 远程: 这只是一个 URL 的名称。 origin 这个词出现在我们分支机构的上游设置中,是第一个标准遥控器。存储在 origin 下的 URL 通常是我们一些 Git 存储库(我们直接拥有或维护的)的副本,这些天通常在某些托管站点上(GitHub、GitLab、Bitbucket 等等)。

  • 拉取请求或对于Git实验室,合并请求:一个hosted-site作为存储库的所有者或维护者,有人可以要求我们将新提交放入我们托管的存储库的方法。此处的详细信息因托管站点而异。因为您正在使用 GitHub,所以我将在此处的示例中使用 GitHub。

现在我们要添加更多 术语。因为为 open-source 软件做出贡献的人往往没有 维护者存储库的推送访问权限,所以他们将使用 GitHub forks.一个 GitHub 分支是一个 server-side git clone 秘密操作 1 在 Git 上节省了大量磁盘 space中心。同时,作为 of 的用户,使用 fork 为 you 提供了一个您无法通过其他方式获得的功能。注意这里的代词you实际上是指some other person;假设是“他”,就叫他Fred吧。 是维护者,所以 Fred 创建了一个 你的 GitHub-side 存储库的分支——你称之为 origin 的那个——并且Fred 然后使用 他的叉子 来完成他的工作。

最终,Fred 将一些新提交推送到 Fred 的 GitHub 存储库,ssh://git@github.com/fred/repo.git 或其他任何地方。 现在想要获取这些提交,以便您可以仔细检查它们。2

您需要 笔记本电脑 Git 才能参考 Fred 的叉子。这不是 origin——那是 你的 分支——所以你需要使用另一个 URL。您可以像以前一样,每次都输入原始的 URL:https://github.com/fred/repo.git 或其他任何内容。

不要那样做!这很痛苦。你会拼写错误。好吧,如果它是 one-time 的东西,也许可以这样做,但是如果你发现你必须经常参考 Fred 的叉子,请创建另一个 remote.

现在,GitHub 鼓励我们,出于某种原因,3 使用名称 upstream 作为 second 此处为标准远程名称。这个词已经有点术语了。您 可以 使用它,但我建议您不要使用它,因为它会让人感到困惑。由于我们在这里谈论的是 Fred,因此让我们为这个遥控器使用名称 fred

git remote add fred https://github.com/fred/repo.git

您现在可以在之前使用 https://github.com/fred/repo.git 的任何地方使用 fred。这可能已经足够鼓励了——但这 也解决了您的问题


1这并不是真正的秘密,但你不必知道它。

2您可以使用 GitHub 的检查选项,但有时它们不够用。例如,查看 Linus Torvald 的任何投诉。有时它们已经足够好了,在那些情况下,使用它们确实可以简化您的工作,因此请考虑使用它们。但这里我们不会那样做。

3原因主要是“我们”GitHub 试图打通那些制作分叉并只回馈一件事的业余爱好者.不幸的是,他们在这里使用 upstream 这个词,对我们造成了很大的伤害。


使用多个遥控器允许设置上游

原因:

git branch -u https://github.com/USER/REPO.git/master

失败的是,一个分支的upstream由两部分组成:

  • 首先是远程部分。这必须是实际的 远程.
  • 之后是分支部分。这是在远程 Git.
  • 上看到的分支的 名称

两部分用斜杠隔开。将这两个部分放在一起给我们 看起来像 一个 remote-tracking 名称 。由于我们倾向于复制他们的分支(Fred的feature/tall)作为我们的remote-tracking同名(fred/feature/tall)的分支,我们将能够要做:

git branch --set-upstream-to fred-pr-123 fred/feature/tall

例如。

上游有点疯狂

不过,系统中存在一个问题。幸运的是,您基本上不需要关心它。不幸的是,GitHub 的 Pull Requests 命名方案可能会让你关心它,这取决于你关心它的程度。 (咳咳。)无论如何:上游 上看到的 分支名称不需要与您使用的 remote-tracking 名称相匹配

这是什么意思?好吧,我们要到这里机制了。当你 运行:

git fetch fred

您的 Git 将读取您的存储库配置以查找 remote.fred.fetch 设置。这些控制获取的内容标准设置为:

+refs/heads/*:refs/remotes/fred/*

这是一个 refspec。它以一个前导加号 + 开头,这意味着 force: 您的 remote-tracking 名称将被强制更新,因此如果 Fred 重新定位他的一个分支,您例如,将选择 rebase。然后它将 refs/heads/* 列为 refspec 的 source 部分,告诉你 Git get all his branches。它将 refs/remotes/fred/* 列为 refspec 的 destination 部分,告诉你 Git 将这些分支名称更改为我的 remote-tracking 名称。这会在每个名称前面添加 fred/,并将它们放在 refs/remotes/ 中,这样它们就是 remote-tracking 名称并且与您的遥控器 fred 关联,而不是任何其他遥控器.通过这种方式,您可以添加 barneywilmabetty 作为另外三个遥控器(如果合适),并且您将在 remote-tracking 名称中保持它们的所有分支不同。

这就是正常设置。但是 remote.fred.fetch refspec(s) 不必是正常的 。如果愿意,我们可以这样写:

+refs/heads/master:refs/remotes/fred/scooby
+refs/heads/hairy:refs/remotes/fred/shaggy

这是一个“two-branch 遥控器”(类似于 single-branch 遥控器,但列出了两个分支):我们将 Fred 的 master 复制到我们的 remote-tracking 名称 fred/scooby,他的 hairy 改为我们的 remote-tracking 名字 fred/shaggy。为什么?只为 illustration,真的。引用 Fred 的 hairyupstream 设置是 fred/shaggy,但它由两部分组成:fredhairy

如果您使用 git branch --set-upstream-to,则不必考虑所有这些向前和向后的映射。如果您使用 git configgit config --edit 通过上游设置直接进入 .git/config 文件到 fiddle,您 看到所有这些疯狂。不过,如果您保持 fetch refspec 简单,那么疯狂就会消失。

为什么我完全掩盖了疯狂

如果你坚持使用 branch@{upstream} 来引用给定分支的上游,并使用 git branch --set-upstream-to 设置 上游,你隐藏了任何疯狂.如果你没有制造任何疯狂,就没有任何疯狂可以隐藏。但问题在于:当有人通过 GitHub 向您发送拉取请求时,会出现在你的 存储库中,在使用 PR 编号构建的引用下:

refs/pull/123/head

这不是 分支 名称。这不是 remote-tracking 名称。这是一个名字 GitHub 组成的。如果您想从 您的 GitHub 分支而不是 Fred 获取此提交,您可以这样做:

git fetch origin refs/pull/123/head:refs/heads/pr-123

这将在您的本地存储库中创建一个名为 pr-123 (refs/heads/pr-123) 的 分支 ,它命名 commit[= Fred 希望您放入的 311=]。该提交在 Fred 的 分支中可能具有名称 refs/heads/feature/tall,但在 [=207] 中具有名称 refs/pull/123/head =]你的叉子。

有些人喜欢在自己的仓库中自动创建pr/<em>number</em>branches在他们的 GitHub fork 的 PR 上,你可以通过添加另一个 fetch refspec 轻松地做到这一点:

+refs/pull/*/head:refs/heads/pr-*

你的 Git 将使用你的 GitHub 分支的 refs/pull/*/head,将 * 部分匹配到 PR 编号,并将其复制到你自己的存储库中作为 refs/heads/pr-whatever.

(我不喜欢自己自动执行此操作,因为它会创建很多分支。请注意,旧版本的 Git 无法在此处处理 pr-*,而是需要 pr/* 或类似的。无论哪种方式,您可能也想将 fetch.prune 设置为 true,如果您还没有这样做的话。)

如果你这样做,你就是在让一些疯狂表现出来。你确实不能推送到refs/pull名字space,所以这不是什么大问题,但你会看到奇怪的地方。因为这个双重映射技巧,这也意味着你不能命名 你自己的 分叉的分支 pr-*:试试吧,疯狂闪耀着耀眼的光芒。 (映射不再 one-to-one 到/双射/可逆,但它们需要。)

多个遥控器和三角工作流

如果您选择直接从 Fred 的分支中获取,并且您现在有多个遥控器(fredorigin),您将:

  • 从 Fred 的叉子中获取;
  • 也许做些调整提交的事情;和
  • 推到你的叉子上。

根据定义,这就是 Git 所说的三角工作流。为了方便起见,Git 允许您设置单独的获取和推送遥控器。您可以整体执行此操作,或 per-branch.

请注意,您可能会失去一些功能。例如,如果同时存在 origin/feature/tall fred/feature/tall 而您自己的存储库中还没有 feature/tall 分支,并且您 运行:

git checkout feature/tall

您可能希望 DWIM 功能在这里发挥作用:您的 Git 会注意到您没有 feature/tall,因此它将 创建 它来自 origin/feature/tall,与上游 pre-set。但是当有两个个匹配项时,这不起作用,现在有两个匹配项。

现代 Git 有一个新的解决方法,即 checkout.defaultRemote 中的 首选远程 设置。如果你的Git比较老,可以升级,也可以用git checkout -t或者git switch -t:

git checkout -t origin/feature/tall

将通过从上游剥离远程部分从 origin/feature/tall 创建 feature/tall。上游是根据您列出的名称设置的。

您可能还会发现 git pushpush.default 默认值 simple 变得有约束力。 current 设置可能更适合您。不过,这完全是个人喜好问题。