TL;DR
您可能需要设置多个遥控器。但是,一旦你这样做了,事情就会变得一团糟。何时以及是否设置您从 PR 创建的本地分支的上游取决于您;你可能根本不想这样做。您想制作任何脚本的花哨程度也取决于您。您最终可能会得到一个所谓的三角工作流。
长
这有点复杂,所以让我们从这个开始:git push 的-u 选项设置了一个上游。您自己的存储库中的每个分支名称都可以有一 (1) 个上游,或者根本没有上游;这些是您唯一的选择。
Git 的术语(以及 GitHub 的术语)在这里变得令人困惑。我上面说的上游是一个 branch 设置; git branch --set-upstream-to 设置此设置; git branch --unset-upstream 删除此设置。在所有情况下,此设置都适用于 您的存储库 中的 分支名称,例如,在您笔记本电脑的存储库中。这是一个本地存储库,而不是 GitHub 上的存储库。
当我们使用 GitHub 时,我们会得到称为 pull requests 的东西。这些不是 Git 本身的一部分! Git 有一个命令,git request-pull,但它只是生成一封电子邮件。它甚至不发送电子邮件,它只是打印一个,适合你放入一些电子邮件生成软件。所以 GitHub 拉取请求是特定于 GitHub 的。
当我们执行 get 拉取请求时,我们经常希望我们自己的 Git,在我们的笔记本电脑上,与 我们的 存储库一起工作,以连接 到 em> GitHub。我们可能会在 GitHub 上存储 我们的 存储库的副本:https://github.com/me/my.git 或其他。我们倾向于称之为origin,因为这是第一个标准remote。
我们已经有了三四个术语:
-
分支名称: 这是提交的名称。每个存储库,无论它存储在哪里,都有自己的 分支名称。笔记本电脑上您的 存储库中的分支名称有一个特殊功能,即当您将其与git checkout 或git switch 一起使用时,您会在分支上 .这意味着当您进行新提交时,Git 会将 new 提交哈希 ID 存储到此分支名称中。新的提交以提交的通常方式向后链接到您刚刚创建新提交之前的最新提交。
-
upstream: 这是与我们(笔记本电脑)存储库中的分支名称相关联的字符串。它实际上是一个由两部分组成的字符串——我们稍后会看到更多内容——但我们通常将其视为一个简单的字符串,例如 origin/main 代表 main,origin/master 代表 master,origin/feature/tall 代表 @ 987654336@.
-
remote: 这只是一个 URL 的名称。 origin 一词出现在我们分支的上游设置中,是第一个标准遥控器。存储在origin 下的 URL 通常用于我们的某个 Git 存储库(我们直接拥有或维护的那个)的副本,现在这些存储库通常位于某个托管站点(GitHub、GitLab、Bitbucket 等)。
-
pull request 或对于 GitLab,merge request: 一种托管站点方法,作为存储库的所有者或维护者,有人可以通过该方法要求我们将新的提交到我们托管的存储库中。此处的详细信息因托管站点而异。因为你用的是 GitHub,所以我这里的例子就用 GitHub。
现在我们要添加更多术语。由于为开源软件做出贡献的人往往没有维护者存储库的推送访问权限,因此他们将使用 GitHubforks。 GitHub fork 是一种服务器端 git clone 操作,它秘密地1 在 GitHub 上节省了大量磁盘空间。同时,使用分叉为您,作为分叉的用户,提供了您无法获得的功能。请注意,这里的代词 you 实际上是指 其他人; 假设它是“他”并称他为 Fred。 您是维护者,因此 Fred 创建了 您的 GitHub 端存储库的一个分支(您称为 origin 的那个),然后 Fred 使用 他的分支 em> 做他的工作。
最终,Fred 将一些新的提交推送到 Fred 的 GitHub 存储库 ssh://git@github.com/fred/repo.git 或其他任何地方。 您现在想要获取这些提交,以便您可以仔细检查它们。2
您需要您的 laptop Git 才能引用 Fred's fork。这不是origin——那是 你的 分支——所以你需要使用另一个 URL。您可以像往常一样每次输入原始 URL:https://github.com/fred/repo.git 或其他任何内容。
不要那样做!这是一种痛苦。你会打错字。好吧,如果它是一次性的,也许可以这样做,但是如果你发现你必须经常参考 Fred 的 fork,那就创建另一个 remote。
现在,出于某种原因,GitHub 鼓励我们3在此处使用名称 upstream 作为第二个标准远程名称。这个词已经有点术语了。您可以使用它,但我建议您不要这样做,因为它会让人感到困惑。既然我们在这里谈论 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
失败是分支的上游由两部分组成:
- 首先是 remote 部分。这必须是一个实际的遥控器。
- 之后是分支部分。这是在远程 Git 上看到的分支的名称。
这两个部分用斜线分隔。将这两个部分放在一起,我们得到了看起来像一个远程跟踪名称的东西。由于我们倾向于复制 他们的 分支(Fred 的 feature/tall)作为我们的远程跟踪名称(fred/feature/tall),我们可以这样做:
git branch --set-upstream-to fred-pr-123 fred/feature/tall
例如。
上游有点疯狂
不过,系统中有一个问题。幸运的是,您通常不需要关心它。不幸的是,GitHub 的 Pull Requests 命名方案可能会让你关心它,这取决于你关心它的程度。 (咳咳)无论如何:在上游看到的分支名称不需要与您使用的远程跟踪名称匹配。
这一切意味着什么?好吧,我们必须去这里的机制。运行时:
git fetch fred
您的 Git 将读取您的存储库的配置以找到 remote.fred.fetch 设置。这些控制获取的内容,标准设置是:
+refs/heads/*:refs/remotes/fred/*
这是一个参考规范。它以前导加号 + 开头,这意味着 force: 您的远程跟踪名称将被强制更新,因此如果 Fred 重新设置他的分支之一,您将获得重新设置,例如。然后它将 refs/heads/* 列为 refspec 的 source 部分,告诉你的 Git 获取他的所有分支。它将 refs/remotes/fred/* 列为 refspec 的 destination 部分,告诉你的 Git 将这些分支名称更改为我的远程跟踪名称。这会在每个名称前添加fred/,并将它们放在refs/remotes/ 中,以便它们是远程跟踪名称,并且与您的远程fred 关联,而不是与任何其他远程关联。这样,如果合适的话,您可以将 barney、wilma 和 betty 添加为另外三个遥控器,并且您将在您的远程跟踪名称中保持它们的所有分支不同。
这就是正常设置。但是remote.fred.fetch refspec(s) 不必是正常的。如果我们愿意,我们可以写:
+refs/heads/master:refs/remotes/fred/scooby
+refs/heads/hairy:refs/remotes/fred/shaggy
这是一个“双分支远程”(类似于单分支远程,但列出了两个分支):我们将 Fred 的 master 复制到我们的远程跟踪名称 fred/scooby,并将他的 hairy 复制到我们的远程跟踪名称fred/shaggy。为什么?真的只是为了illustration。引用 Fred 的 hairy 的 upstream 设置是 fred/shaggy,但它由两部分组成:fred 和 hairy。
如果您使用git branch --set-upstream-to,则不必考虑所有这些向前和向后的映射。如果您使用git config 或git config --edit 直接访问您的.git/config 文件以修改上游设置,您将看到所有这些疯狂。但是,如果您保持 fetch refspec 简单,那么疯狂就会消失。
为什么我完全掩盖了疯狂
如果你坚持使用branch@{upstream} 来引用给定分支的上游,并使用git branch --set-upstream-to 来设置上游,你隐藏了任何疯狂。如果你没有制造任何疯狂,那么就没有任何疯狂可以隐藏。但问题是:当有人通过 GitHub 向您发送拉取请求时,它会出现在 您的 存储库中使用 PR 编号构建的 ref 下:
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) 的 分支,该分支命名 Fred 希望您放入的 commit。 commit 可能在 Fred 的 分支中具有名称 refs/heads/feature/tall,但在 您的 分支中具有名称 refs/pull/123/head。
有些人喜欢根据在他们的 GitHub 分支上创建的 PR 在他们自己的存储库中自动创建 pr/<em>number</em>分支,您可以通过添加另一个 fetch refspec 轻松做到这一点:
+refs/pull/*/head:refs/heads/pr-*
您的 Git 将获取您的 GitHub fork 的 refs/pull/*/head,将 * 部分与 PR 编号匹配,并将其复制到您自己的存储库中作为 refs/heads/pr-whatever。
(我不喜欢自己自动执行此操作,因为它会创建很多分支。请注意,旧版本的 Git 无法在此处处理 pr-* 并需要 pr/* 或类似的。无论哪种方式你如果您还没有这样做,您可能还想将fetch.prune 设置为true。)
如果你这样做这样做,你就会让一些疯狂表现出来。你确实不能推送到refs/pull 命名空间,所以这不是一个大问题,但你会看到奇怪。由于这种双重映射技巧,这也意味着您无法命名您自己的 fork 的分支pr-*:试试吧,疯狂的光芒闪耀着耀眼的光芒。 (映射不再是一对一的/双射/可逆的,但它们必须是。)
多个遥控器和三角工作流
如果您选择直接从 Fred 的 fork 获取,并且您现在有多个遥控器(fred 和 origin),您将:
- 从 Fred 的 fork 中获取;
- 也许可以做一些调整提交;和
- 推到你的叉子上。
根据定义,这就是 Git 所说的三角工作流。为了更容易,Git 允许您设置单独的 fetch 和 push 遥控器。您可以整体执行此操作,也可以针对每个分支执行此操作。
请注意,您可能会丢失某些功能。例如,如果同时存在origin/feature/tall 和 fred/feature/tall,而您自己的存储库中还没有feature/tall 分支,那么您运行:
git checkout feature/tall
您可能希望 DWIM 功能在这里发挥作用:您的 Git 会注意到您没有 feature/tall,因此它将从 origin/feature/tall创建它,上游预放。但是当有 两个 匹配时这不起作用,现在有两个匹配。
现代 Git 有一个新的解决方法,checkout.defaultRemote 中的 首选远程 设置。如果你的 Git 较旧,可以升级,也可以使用git checkout -t 或git switch -t:
git checkout -t origin/feature/tall
将通过从上游剥离远程部分从origin/feature/tall 创建feature/tall。上游是根据您列出的名称设置的。
您可能还会发现git push 的push.default 默认simple 成为约束。 current 设置可能更适合您。无论如何,这都是口味问题。