在另一个分支上从远程分支拉取 - “拉原点主”与“拉主”与“更改为主然后拉”

Roy*_* Ca 1 git

我在另一个分支上从远程分支拉取时有点困惑。例如,如果我更改为 main,则拉取:

git checkout main git pull 我得到了远程更改的更新。好的。

但是,如果我在另一个分支上并且我想更新 main 而不更改为 main,我总是会得到令我困惑的结果(除非我更改为 main,否则我不会真正获得更新)。

假设我在分支“feature”上,我尝试: git pull maingit pull origin/maingit pull origin main,我得到了一些我没有预料到的东西,但从来没有更新过的分支。

一个具体的例子,git pull origin main在分支上运行时feature,将输出以下内容:

remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (1/1), done.
From <repo name>
 * branch            master     -> FETCH_HEAD
   8a84194..d00d469  master     -> origin/main
Updating 340286a..d00d469
Fast-forward
Run Code Online (Sandbox Code Playgroud)

但是当我更改为 main 时git checkout main,然后git pull,我得到了实际的更新:

git pull
Updating 8a84194..d00d469
Fast-forward
 Pipfile      |    2 +-
 Pipfile.lock | 1583 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------
 setup.py     |    2 +-
 3 files changed, 863 insertions(+), 724 deletions(-)
Run Code Online (Sandbox Code Playgroud)

你能帮我理解我做错了什么吗?谢谢你!

罗伊.

tor*_*rek 5

首先,除了您现在签出的分支之外,您实际上无法合并到任何其他分支中。1

\n

其次,2的git pull含义是:

\n
    \n
  1. 运行git fetch,然后
  2. \n
  3. 运行第二个 Git 命令。
  4. \n
\n

第二个 Git 命令是您的选择,但如果没有做出明确的选择,您通常会得到git merge. 所以pull= fetch+ merge,并且merge仅适用于当前分支

\n

(你的另一个正常选择是git rebase。但是有同样的限制,即只能在你现在git rebase签出的任何分支上工作。它也有一些脚注,因为 Git 不能忍受让任何事情变得简单,但我们再次忽略这些.)

\n

因此,初步近似:

\n
    \n
  • git pull意味着选择该分支的上游,然后拉入该分支(我们稍后将定义上游);
  • \n
  • git pull origin main意味着复杂的事情,我稍后会描述;和
  • \n
  • git checkout main然后git pull意味着选择 的上游main,并拉入,main因为“这个分支”是main
  • \n
\n

除非您已经打开main否则这三个命令都不能与其他任何命令交换

\n
\n

1对于这个 \xe2\x80\x94 特殊情况有许多注意事项,例如,这些特殊情况看起来像是合并,而即将推出的未来 Git 软件可能最终会逃脱这个约束 \xe2\x80\x94 但现在只是将其视为真理。

\n

2在过去(如 2015 年之前,Git 2.6 之前),它git pull实际上是一个运行 的 shell 脚本git fetch,然后运行相应的第二个命令。C 版本更快、更高效,但内部逻辑仍然相同。

\n
\n

必要的背景,没有这些背景,Git 就没有意义

\n

您可能使用 Git 来维护一堆文件。但 Git 并不是真正与文件有关的。您也可以使用分支,但 Git 也与分支无关。最后,Git 就是提交。确实,提交包含文件,我们使用分支名称来帮助我们(和 Git)查找提交,但 Git 与文件或分支名称无关:它存在的理由\'\xc3\xaatre提交

\n

因此,您需要了解 Git 提交是什么以及它为您做什么。幸运的是,这部分非常简单。Git 提交:

\n
    \n
  • 已编号。每次提交都会获得一个唯一的哈希 ID。哈希 ID 巨大、丑陋且随机,人类不可能记住。它必须是,因为它必须是唯一的:当您进行新的提交时,它必须获得一个以前从未使用过的数字,并且现在该数字永远不能再用于任何其他提交。

    \n

    (由于鸽巢原理,这部分在数学上是不可能的,但是由于哈希 ID 很大,因此将不可避免的失败推迟到未来,到那时我们都计划死掉,所以我们不在乎。)

    \n

    因为每个提交都有git fetch一个唯一的编号,所以我们可以使用任意两个 Git 存储库并使用或简单地相互介绍它们git push。一个 Git 是发送者,一个是接收者。发送者列出他的提交哈希 ID,接收者检查他是否有这些哈希 ID。如果他这样做了,他就有了这些承诺。如果没有,他可以知道他已经拥有哪些提交以及他需要哪些提交,然后接收者让发送者发送接收者需要的任何提交。现在两个 Git 存储库共享提交。(你必须将它们连接两次,每个方向一次,才能获得完全共享,并且有很多方法可以限制共享,但这是一般原则。)

    \n
  • \n
  • 完全只读:任何提交一旦完成就不能更改。这是使哈希 ID 技巧发挥作用所必需的。

    \n
  • \n
  • 包含两件事:所有源文件的完整快照,以您(或任何人)提交时的形式永久冻结,以及一些元数据。元数据包括您的姓名、电子邮件地址以及提交时的日期和时间戳等内容。但是元数据中还有很多其他内容,特别是 Git 添加了自己的早期提交哈希 ID 列表

    \n
  • \n
\n

这个早期提交哈希 ID 列表位于每个提交的元数据内,向后连接提交。大多数提交(到目前为止)都有一个这样的哈希 ID,这些是我们首先要关注的。当我们确实有一个哈希 ID 时,Git 将这一哈希 ID 称为提交的父级。我们说提交指向其父级。这为我们提供了一种绘制提交的方法。

\n

假设您的存储库中的最新提交有一些丑陋的大哈希 ID,我们将其称为H“哈希”。CommitH存储一些早期提交的哈希 ID:一个不同的、看起来又大又丑、随机的东西,我们称之为G。这意味着提交H 指向较早的提交G

\n
          G <-H\n
Run Code Online (Sandbox Code Playgroud)\n

但它是一次提交,因此它的G元数据中也有一个哈希 ID 。它指向一些较早的提交;我们称其为:F

\n
... <-F <-G <-H\n
Run Code Online (Sandbox Code Playgroud)\n

提交F具有元数据,因此它指向另一个较早的提交,并且这将永远持续\xe2\x80\x94,或者至少,直到我们回到有史以来的第一个提交,它实际上无法指向任何内容早些时候。所以它没有:它有一个的先前提交列表。如果我们调用该提交A(并声称我们正好有 8 个提交),则整个链是:

\n
A--B--C--D--E--F--G--H\n
Run Code Online (Sandbox Code Playgroud)\n

我懒得把箭头画箭头。它们都是提交的一部分,永远不会改变:H 总是向后指向G、永远,以及G向后指向F永远,等等。

\n

为了让这一切发挥作用,Git 需要一种快速的方法来查找commit H。通常,Git 使用由哈希 ID索引的简单键值存储,通过哈希 ID 记录其所有内部对象\xe2\x80\x94 提交及其支持内容\xe2\x80\x94。那么Git快速查找的方法H就是知道它的哈希ID。

\n

现在,您可以记住每个 Git 提交哈希 ID。但这样才是疯狂所在。与其让我们记住哈希 ID,为什么不让计算机来记忆呢?来记忆呢?这是分支和其他名称的用武之地:

\n
...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

分支名称 main\xe2\x80\x94 是一个可更改的指针,与卡在提交元数据\xe2\x80\x94 中的指针不同,它告诉我们哪个提交 main. 如果我们进行更新的提交I,Git 会安排I向后指向H

\n
...--G--H   <-- main\n         \\\n          I\n
Run Code Online (Sandbox Code Playgroud)\n

然后会重新指向名称 main指向较新的提交:

\n
...--G--H\n         \\\n          I   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

现在最新的提交main是 commit I,而不是 commitH

\n

更多分支名称=更多指针

\n

让我们回到这一设置,只有一个分支名称main

\n
...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

现在让我们创建一个新的分支名称,例如develop. 在 Git 中,分支名称必须恰好指向一次提交。我们可以选择八个提交中的任何一个,但这里最合适的一个可能是最新的一个,如果我们用来git branch develop创建它,那么 Git 会在这里选择一个。现在我们有:

\n
...--G--H   <-- develop, main\n
Run Code Online (Sandbox Code Playgroud)\n

目前,两个名称都选择 commitH,但是一旦我们开始进行新的提交,这种情况就会改变。因此我们需要知道使用哪个名称H来查找 commit 。为了在我们的绘图中标记这一点,让我们将名称HEAD(全部大写)附加到一个分支名称上:

\n
...--G--H   <-- develop, main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

H我们现在正在使用 commit ,但是通过name main来实现。如果我们运行:

\n
git checkout develop     # or git switch develop, which does the same\n
Run Code Online (Sandbox Code Playgroud)\n

我们得到:

\n
...--G--H   <-- develop (HEAD), main\n
Run Code Online (Sandbox Code Playgroud)\n

我们仍在使用commit ,但我们现在通过名称H来这样做。 develop

\n

如果我们I现在进行新的提交,它的制作方式与我们在 上时的方式相同main,但由于我们现在正在使用,所以更新的develop这个名称:

\n
...--G--H   <-- main\n         \\\n          I   <-- develop (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

该名称main没有移动,因为它不是我们正在使用的名称。

\n

这是 Git 中的一般规则:每当您进行新的提交时,Git 都会更新当前的分支名称。这就是HEAD所附的名称。因此,如果您绘制提交(在纸上或白板上,或者使用git log --graphGit 绘制它们),您将看到您现在所在的位置以及新提交将去往的位置。它会在您使用的任何提交之后通过您使用的任何名称添加 on 。

\n

让我们看看git merge现在看看

\n

假设我们从以下内容开始:

\n
...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

并创建两个新的分支名称,br1并且br2也指向H。我们也将很快停止以名字绘制 main只是为了稍微整理一下绘图。所以现在我们有:

\n
...--G--H   <-- br1, br2, main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

我们现在选择br1当前分支并进行新的提交:

\n
          I   <-- br1 (HEAD)\n         /\n...--G--H   <-- br2, main\n
Run Code Online (Sandbox Code Playgroud)\n

当我们进行第二次提交时,我们得到:

\n
          I--J   <-- br1 (HEAD)\n         /\n...--G--H   <-- br2, main\n
Run Code Online (Sandbox Code Playgroud)\n

然后我们切换到分支名称br2,以便我们返回到提交时保存的文件H,并开始进行一些不同的更改br2

\n
          I--J   <-- br1\n         /\n...--G--H   <-- main\n         \\\n          K--L   <-- br2 (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们git switch br1再次让 Git 用 commit-L文件替换 commit-J文件并“on”br1文件并再次

\n
          I--J   <-- br1 (HEAD)\n         /\n...--G--H\n         \\\n          K--L   <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们现在运行:

\n
git merge br2\n
Run Code Online (Sandbox Code Playgroud)\n

Git 会完成复杂的合并过程。跳过如何部分,让我们看看合并的结果:如果一切顺利,Git 会进行新的合并提交 M。合并提交的原因M在于,它不像任何新提交那样仅仅指向 commit J,而是指向 commit L,如下所示:

\n
          I--J\n         /    \\\n...--G--H      M   <-- br1 (HEAD)\n         \\    /\n          K--L   <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n

(现在你知道为什么我停止画名字了main:它仍然在那里,只是很难画它提交M)。

\n

这是真正的合并。为了实现这一目标,Git 必须结合工作: Git 必须弄清楚H和之间发生了什么变化J,以及H和之间发生了什么变化L。我们首先跳过了这一点以及Git 如何选择提交H,但事实是 Git确实在这里选择H。提交H是最好的共享提交分支和. 所有提交(包括在内)都在两个分支上。Git 调用合并基础来执行此合并操作。br1 br2HH

\n

然后 Git 必须为提交创建一个新的快照,将组合M工作应用于来自 的快照,从而保留您的更改并添加他们的更改,或者 \xe2\x80\x94(如果您想以相反的方式查看它)\xe2\x80 \x94保留他们的更改并添加您的更改(请注意,对于正常的日常合并,无论哪种方式,结果都是相同的)。H

\n

这种工作组合需要检查两个分支之一。合并提交M添加到该分支,这就是为什么br1现在指向M. 其他分支名称不会移动:只有当前名称会受到添加新提交的影响。

\n

并非所有合并都是真正的合并。 假设我们不是创建两个分支br1和,并两个分支上br2进行新的提交,而是从和开始,仅在 上进行两次提交,然后切换回mainbr1br1main

\n
          I--J   <-- br1\n         /\n...--G--H   <-- main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们现在运行git merge br1,Git 将执行以下操作:

\n
    \n
  • 它找出两个分支上的最佳共享提交。这就是承诺H再次提交。
  • \n
  • 对于真正的合并,Git 必须找到提交H和提交H(合并的“我们”一侧)之间完成了哪些工作,以及提交H和提交J(合并的“他们”一侧)之间完成了哪些工作。但提交H 就是提交H。“我们”这边根本没有工作!
  • \n
\n

由于合并基础提交当前提交(通过遵循HEAD提交的分支名称来选择),因此没有需要合并的不同工作。进行真正合并的结果将是一个合并提交,其快照与commitJ的现有快照完全匹配。

\n

因此Gitgit merge走了一条捷径。您可以使用 告诉它要这样做git merge --no-ff,但默认情况下,Git 会执行快进而 不是合并。(Git 将其称为快进合并,但不涉及实际的合并。)为此,Git 只需将当前分支名称向前滑动以指向另一个提交,然后检查该提交,就像通过git checkout

\n
          I--J   <-- br1, main (HEAD)\n         /\n...--G--H\n
Run Code Online (Sandbox Code Playgroud)\n

(没有理由再担心图表中的扭结,但我暂时将其保留)。

\n

请注意,与常规合并一样,此快进合并移动了分支名称。但它没有进行任何新的提交,这就是它的特别之处。 事实上,它没有进行任何新的提交,这意味着将来我们可能永远不知道 Git 做了这件事。 如果我们现在删除该名称 br1,我们会得到:

\n
...--G--H--I--J   <-- main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

看起来我们一直都 main做出承诺。

\n

让我们回到git fetch

\n

我已经git fetch在基本背景部分提到过。这就是你如何连接你的Git 软件,在你的 Git 存储库上工作/与其他一些Git 软件\xe2\x80\x94任何使用 Git 协议的东西,实际上\xe2\x80\x94\正在与其他一些工作一起工作存储库。如果我们将 your-software-plus-your-repository 称为“your Git”,将 other-software-plus-other-repository 称为“他们的 Git”,则:

\n
    \n
  • 从他们的 Git 中获取他们拥有而你没有的任何提交,并且
  • \n
  • 让您的 Git 创建和/或更新一些名称。
  • \n
\n

这里的技巧是 Git 创建和/或更新的名称根本不是分支名称。原因是你的分支名称是你的做了br1并且br2(或没有)。您main正在跟踪您的提交添加的提交。覆盖这些名称来跟踪其他一些 Git 的提交是很糟糕的!

\n

所以你的 Git 根本不这样做。相反,您的 Git 采用每个 Git 分支名称,例如maindevelopfeature/tallSticks origin(或其他名称,但让我们使用origin)以及每个分支前面的斜杠。他们main成为你的origin/main,他们develop成为你的origin/develop,等等。

\n

您的 Git 现在创建或更新这些名称,这是您的 Git 记住其 Git分支名称的方式。所以现在你可能有:

\n
          I--J   <-- main (HEAD)\n         /\n...--G--H\n         \\\n          K--L   <-- origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

您的存储库中,或者可能:

\n
          I--J   <-- br1 (HEAD)\n         /\n...--G--H   <-- main\n         \\\n          K--L   <-- origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

取决于您对分支名称所做的操作他们的 main\xe2\x80\x94your origin/main\xe2\x80\x94 已从指向H,您之前的指向 ,移动到指向L,因为他们添加了两个您没有的提交。你的 Git 从他们的 Git 中获取了这两个提交,并将它们放入你的存储库中,现在它们与 Git 的两个提交具有相同的哈希 ID,因为它们是相同的提交:在保存方面是逐位相同的快照和元数据,包括父哈希 ID。

\n

如果你跑去git checkout main得到这个:

\n
          I--J   <-- br1\n         /\n...--G--H   <-- main (HEAD)\n         \\\n          K--L   <-- origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

你现在可以运行了git merge origin/main。您的 Git 将找到此操作的合并基础commit H,并注意这不需要真正的合并,而是可以执行快进,您将得到:

\n
          I--J   <-- br1\n         /\n...--G--H--K--L   <-- main (HEAD), origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

在你自己的存储库中(这次我解决了这个问题)。

\n

请注意,这些origin/带有 - 前缀的名称不是分支名称。我称它们为远程跟踪名称。Git 的文档将它们称为远程跟踪分支名称,但它们根本不是分支名称:它们是其他人的分支名称的反映,但它们实际上并不是存储库中的分支名称。

\n

等一下,我们就快到了:在树枝的上游

\n

现在我们来谈谈分支的上游设置。存储库中的每个分支名称(但不是任何远程跟踪名称)都可以有一个上游集。这是可选的,因此每个分支名称不能有上游。设置上游不会以任何方式更改分支。它只是使某些操作更加方便:

\n
    \n
  • 如果您git merge在没有命名任何内容的情况下运行,Git 将使用当前分支的上游设置。也就是说,git merge origin/main表示查找由 命名的提交origin/maingit merge表示查找由当前分支的上游命名的提交
  • \n
  • 如果您git rebase在没有命名任何内容的情况下运行,Git 将使用当前分支的上游。
  • \n
  • 如果运行git pull,Git 将使用当前分支的上游。
  • \n
  • 如果运行git status,Git 会在状态中添加一些使用当前分支的上游获取的信息。
  • \n
\n

这些并不是唯一的事情,但可以说它们是亮点。设置上游使 Git 更容易使用。但是,唉,每个分支名称只能获得一个,而且您几乎应该始终将上游的那个名称设置相应的远程跟踪名称。即,main应该是 的上游,应该是origin/main的上游,依此类推。developorigin/develop

\n

(这里有一个小故障:feature/roy例如,当您编写自己的新分支名称 \xe2\x80\x94 或br1,或其他 \xe2\x80\x94时,还没有相应的分支origin所以有\'否origin/royorigin/br1,你还不能设置上游。Git 让你等待,直到你使用了git push。我们稍后会再讨论这一点。)

\n

把它们放在一起

\n

我们现在知道:

\n
    \n
  • git fetch从其他一些 Git(例如 at origin)获取新提交并更新相应的远程跟踪名称;和
  • \n
  • git merge,自行运行,与上游合并,根据需要进行快进或真正的合并。
  • \n
\n

首先运行git pull检查是否存在上游集(以便它可以执行该git merge步骤),然后为您执行这两个命令。这就是它真正所做的一切。

\n

但是等等,等等,那又怎样呢git pull origin main?好吧,如果您还没有上游集\xe2\x80\x94,或者即使您有\xe2\x80\x94,也git pull origin main会让您的命令运行。首先,git pull将行的其余部分传递 git fetch,以便您运行:

\n
git fetch origin main\n
Run Code Online (Sandbox Code Playgroud)\n

git fetch命令通常使用上游(origin/main例如)来知道要转到origin,但这里我们明确告诉它“使用 Git over at origin”。这里的最后一个main告诉它我们对哪个分支名称特别感兴趣:它限制了 fetch。通常,您的 Git 会让 Git 列出所有分支名称,并带来所有新提交。使用此命令,您的 Git 仅向其 Git 询问其main.

\n

(这通常根本没有节省,因为稍后您将需要来自其他分支的其他提交,如果您一次获得所有内容,那实际上比运行多个操作更有效git fetch。但它会加快速度有时, git fetch有点;只是它会在以后发生 git fetch速度比原来慢。可以说,现在保存,以后付款。除非您的网络连接速度非常慢或获取的数据异常巨大,否则它\很少值得做。)

\n

不管怎样,无论是否带来了任何新的提交main,你的 Git 都会更新你的origin/main. 然后你的git pull运行:

\n
git merge -m "merge branch \'main\' of <url> [into ...]" origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

(或多或少\xe2\x80\x94这里实际上使用了原始哈希ID,而不是名称origin/main,但效果是相同的)。因此,您会得到引用其他 Git 的 URL 及其分支名称的合并main

\n

由于不需要上游设置,这将获取他们的提交,更新您的origin/main,然后与您的origin/main. 您可以通过运行以下命令获得等效的结果:

\n
git fetch\ngit merge origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

尽管现在的消息是:

\n
merge branch \'origin/main\' [into ...]\n
Run Code Online (Sandbox Code Playgroud)\n

即只git fetch知道实际使用的URL;到运行时,git merge它并不知道origin/main刚刚从该 URL 更新了。代码git pull知道,因为git pull代码为您运行 ,所以代码提供了替代日志消息。(实际上,这两条日志消息都没有任何好处。 他们还不如直接说git fetchgit pullhere have codehaaaaaands

\n

一些注释git push

\n

在您的存储库中进行新的提交后,无论是使用git commit、 、git mergegit merge-as-run-by-git pull或其他方式,在某个时刻您应该将这些新提交发送其他一些 Git。这里通常的候选者是位于 的 Git origin。要推送一些提交,您将运行,例如:

\n
git push origin br1\n
Run Code Online (Sandbox Code Playgroud)\n

这里的部分origin表示联系该名称下存储的URL处应答的Gitorigin,即含义与 for 基本相同git fetch。这里的部分br1是如何提供两件事:

\n
    \n
  • 最后提交(他们将自动获得所有父母),并且
  • \n
  • 您希望他们在存储库中设置的分支名称
  • \n
\n

请注意,与在存储库中git fetch创建或更新远程跟踪git push名称不同,命令提供分支名称供它们在存储中设置。这是因为这里没有任何远程跟踪名称的概念。您通常会选择一个您希望他们设置的分支名称;当你使用时,git fetch你通常想要获取他们所有的分支名称,并且你不知道他们创建了哪些名称,所以我们需要一些奇特的东西,比如远程跟踪名称。

\n

无论如何,所有这一切的结果是你的 Git 将你的提交交给了他们的 Git(在你的 上br1,他们还没有;你br1可能还有数十或数千个提交,他们在他们的或其他地方main)您的 Git 不需要发送这些内容,因为两个 Git 已经交换了哈希 ID 并意识到这些 ID 已经共享)。然后,您的 Git 礼貌地要求他们的 Git 创建或更新它们 br1以合并新的提交。

\n

如果他们还没有br1他们可能会遵守。(一些托管站点,如 Bitbucket、GitHub 和 GitLab 添加了权限规则,但基础 Git 没有任何这样的东西。)或者也许他们确实有一个:br1在这种情况下,只有在添加这些提交导致对他们来说是快进操作。也就是说,假设您有:

\n
          I--J   <-- br1\n         /\n...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

然后你就跑git push origin br1。他们有:

\n
...--G--H   <-- br1, main\n
Run Code Online (Sandbox Code Playgroud)\n

如果他们将I-J其放入存储库中,他们现在可以向前滑动名称br1,以便他们拥有您拥有的所有提交,并且他们br1现在也指向J。但如果他们有:

\n
...--G--H   <-- main\n         \\\n          K   <-- br1\n
Run Code Online (Sandbox Code Playgroud)\n

他们的存储库中,然后你给他们I-J,他们这边的结果如下所示:

\n
          I--J   [polite request: move `br1` here]\n         /\n...--G--H   <-- main\n         \\\n          K   <-- br1\n
Run Code Online (Sandbox Code Playgroud)\n

如果他们这样做,他们就会失去 K他们的br1。这是因为 Git通过读取分支名称然后向后工作(通过那些将提交与早期提交连接起来的向后指向的箭头)来查找提交。Git 无法前进,因为箭头不指向前方(并且是提交的一部分并且无法更改:它们永远指向后方)。因此,对于这种情况,他们会说:不,我不能添加到 my 中,因为这会丢失一些提交,Git 将其报告为非快进错误。I-Jbr1

\n

注意,git push 运行git merge。它只是尝试按原样添加提交。如果您需要I-J与他们合并K,则必须运行git fetch才能提交Korigin/br1更新。然后您可以合并以便I-J 添加(在您的存储库中):

\n
          I--J-----------M   <-- br1 (HEAD)\n         /              /\n...--G--H   <-- main   /\n         \\ ___________/\n          K   <-- origin/br1\n
Run Code Online (Sandbox Code Playgroud)\n

因为新的合并提交M指向 K ,所以现在这会添加到 他们的(您的)J上,并且他们将接受发送给他们的请求并要求他们将其设置为指向。 br1origin/br1I-J-Mbr1M

\n

(您还可以选择变基而不是合并,但我们不会在这里讨论这个。)

\n

现在,对于他们之前没有的情况,br1您将拥有:

\n
          I--J   <-- br1\n         /\n...--G--H   <-- main, origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

在你的存储库中。你不会有一个origin/br1. 所以你运行:

\n
git push origin br1\n
Run Code Online (Sandbox Code Playgroud)\n

并派他们I-J去要求他们创造 br1,他们服从了。您的 Git 看到他们接受了礼貌的请求,现在您的 Git 在您的存储库中创建了您的origin/br1

\n
          I--J   <-- br1, origin/br1\n         /\n...--G--H   <-- main, origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

您现在可以将(your) 的上游br1设置为 (your) origin/br1

\n
git branch --set-upstream-to=origin/br1 br1\n
Run Code Online (Sandbox Code Playgroud)\n

例如。但是,您不需要将其作为单独的命令输入,而是git push允许您添加-u标志:

\n
git push -u origin br1\n
Run Code Online (Sandbox Code Playgroud)\n

-u标志告诉git push如果推送成功,则将刚刚推送的名称的上游设置为相应的远程跟踪名称。也就是说,在这种情况下,意味着将上游设置br1origin/br1。请注意,如果推送失败,该-u标志不起作用。

\n

-u即使现在有上游集,您也可以使用该标志。它只会运行该git branch --set-upstream-to命令并用新设置覆盖当前设置。(如果 的上游br1已经origin/br1,这会覆盖旧origin/br1origin/br1,所以你甚至无法知道发生了什么。)但是大多数人更喜欢origin在他们的头脑中将“创建新分支”与“更新现有分支”分开关于起源”。如果您也喜欢这个,您就会知道何时要添加-u(在那里创建分支并在此处设置上游)以及何时不添加(在那里更新分支,不必在这里做任何事情)。

\n

一旦您上游设置为br1to origin/br1,通常的 Git 设置意味着您可以在打开git push时运行。因此,当您创建它时,您只需要一次,之后您只需运行即可,无需额外输入。 br1git push -u origin br1 git push

\n

结论

\n

您概述的三个命令都非常不同。它们都是基于这样的事实构建的:git pull默认情况下,运行

  • @torek - 感谢您提供这个令人惊叹的详细答案。我想并不是所有的英雄都穿着斗篷。 (2认同)