在另一个分支上从远程分支拉取 - "pull origin main" vs "pull main" vs "change to main then pull"
pulling from remote branch while on another branch - "pull origin main" vs "pull main" vs "change to main then pull"
我在另一个分支上时从远程分支中拉出有点困惑。
例如,如果我更改为 main,则拉:
git checkout main
git pull
我得到了远程更改的更新。不错。
但是,如果我在另一个分支上并且我想更新 main 而不更改为 main,我总是得到让我感到困惑的结果(除非我更改为 main,否则我不会真正得到更新)。
假设我在分支 'feature',我尝试:
git pull main
或 git pull origin/main
或 git 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
但是当我切换到 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(-)
你能帮我理解我哪里错了吗?
谢谢!
罗伊
首先,您确实 不能 合并到除您现在签出的 b运行ch 之外的任何其他内容.1
二、git pull
是什么意思2是:
- 运行
git fetch
,然后
- 运行 第二个 Git 命令。
第二个 Git 命令是您的选择,但如果不做出明确的选择,您通常会得到 git merge
。所以 pull
= fetch
+ merge
和 merge
只适用于 current b运行ch.
(您的另一个正常选项是 git rebase
。但是 git rebase
具有相同的限制,即只处理您现在 签出的任何 b运行ch 。它也有一些脚注,因为 Git 无法忍受让任何事情变得简单,但我们将再次忽略这些。)
所以,对于第一个近似值:
git pull
表示选择上游这个b运行ch,然后拉入这个b运行 ch(稍后定义upstream);
git pull origin main
的意思比较复杂,后面会讲到;和
git checkout main
然后git pull
表示为main
挑上游,拉入main
,因为“这个b 运行ch" 是 main
。
除非你已经在main
,否则这三个命令中的none可以与其他任何命令交换.
1对此有一些警告——特殊情况 看起来像 合并,例如,即将到来的未来 Git 软件可能最终会摆脱这种限制——但现在就把它当作真理吧。
2在过去(如 2015 年之前,Git 2.6 之前),git pull
实际上是一个 shell 脚本运行 git fetch
,然后是 运行 适当的第二个命令。 C版本更快更高效,但是内部逻辑还是一样的。
必要的背景,没有它Git就没有任何意义
您可能使用Git来维护一堆文件。但是 Git 并不是真正的 about 文件。您也使用 b运行ches,但是 Git 也不是 b运行ches。最后,Git 就是关于 提交 。确实commitscontain文件,我们使用b运行ch名称来帮助我们(和Git)find提交,但 Git 与文件或 b运行ch 名称无关:它是 raison d'être 是 commit.
因此您需要知道 Git 提交是什么以及对您有什么作用。幸运的是,这部分非常简单。 Git 提交:
已编号。每个提交都会获得一个 唯一的 哈希 ID。 hash ID又大又丑random-looking,人类不可能记住。它 必须 ,因为它必须 唯一: 当你进行新提交时,它必须获得一个从未使用过的数字以前,现在这个号码再也不能用于任何 other 提交。
(由于 pigeonhole principle,这部分在数学上是不可能的,但是由于哈希 ID 很大,哈希 ID 将不可避免的失败推到了未来,到那时我们都计划死亡,所以我们不在乎。)
因为每个提交 都有一个 唯一编号,我们可以取任意两个 Git 存储库并简要介绍它们,使用 git fetch
或 git push
。一个 Git 是发送者,一个是接收者。发送者列出他的提交哈希 ID,接收者检查他是否有这些哈希 ID。如果他这样做,他有 那些提交 。如果没有,他可以告诉他已经有哪些提交和他需要哪些提交然后接收方让发送方发送接收方需要的任何提交.现在两个 Git 存储库共享提交。 (你必须连接它们两次,每个方向一次,以获得完整的共享,并且有很多方法可以限制共享,但这是一般原则。)
完全read-only:任何提交的任何部分一旦完成就不能更改。这是使哈希 ID 技巧起作用所必需的。
包含两件事:所有源文件的完整快照,以您(或任何人)制作时的形式永久冻结提交,以及一些 元数据 。元数据包括您的姓名和电子邮件地址以及您提交时的 date-and-time-stamp 等内容。但是元数据中还有很多其他内容,特别是 Git 添加了自己的 早期提交哈希 ID 列表 .
这 list-of-earlier-commit-hash-IDs,在每个提交的元数据中,连接 提交,向后。大多数提交(到目前为止)只有一个这样的哈希 ID,并且这些是我们首先要关注的。当我们确实只有一个哈希 ID 时,Git 将这个哈希 ID 称为提交的 parent。我们说提交 指向 它的 parent。这为我们提供了一种绘制提交的方法。
假设您存储库中的 最新 提交有一些丑陋的大哈希 ID,我们将其称为 H
表示“哈希”。提交 H
存储一些早期提交的哈希 ID:一个不同的、丑陋的 random-looking 东西,我们称之为 G
。这意味着提交 H
指向 之前的提交 G
:
G <-H
但是 G
是一个提交,所以它也有一个散列 ID 卡在 它的 元数据中。它指向一些较早的提交;我们称它为 F
:
... <-F <-G <-H
提交 F
有元数据,所以它指向另一个更早的提交,并且这会一直持续下去——或者至少,直到我们回到 有史以来第一次提交,字面上 不能 指向更早的任何内容。所以它没有:它有一个 empty 以前提交的列表。如果我们称该提交为 A
(并声称我们恰好有 8 次提交),则整个链为:
A--B--C--D--E--F--G--H
我懒得把箭头 画成 箭头了。它们都是提交的一部分,永远不会改变:H
总是 向后指向 G
,永远,G
向后指向F
永远,等等。
为了让所有这些都起作用,Git 需要一种快速的方法来 找到 提交 H
。 Git 一般文件所有内部 objects——提交和他们的支持的东西——通过它们的哈希 ID,使用一个简单的 key-value store 索引 by 哈希ID。所以 Git 找到 H
的快速方法是知道它的哈希 ID。
现在,您可以 记住每个 Git 提交哈希 ID。但是that way madness lies。为什么不让 us 记住哈希 ID,而让 computer 来做呢?这是 b运行ch 和其他名称的来源:
...--G--H <-- main
b运行ch name main
——这是一个可变的指针,不同于卡在提交元数据中的指针——告诉我们哪个提交是最新的 main
。如果我们进行 newer 提交 I
,Git 将 ar运行ge for I
向后指向 H
:
...--G--H <-- main
\
I
然后 re-point name main
到更新的提交:
...--G--H
\
I <-- main
现在“在”main
上的最新提交是提交 I
,而不是提交 H
。
更多 b运行ch 名称 = 更多指针
让我们回到这个设置,只有一个 b运行ch 名称 main
:
...--G--H <-- main
现在让我们创建一个新b运行通道名称,例如develop
。在 Git 中,b运行ch 名称 必须 指向 恰好一个提交 。我们可以选择八次提交中的任何一次,但这里最合适的可能是 最新的 一次,如果我们使用 git branch develop
来创建它,那就是 [=1022] =] 会选择这里。现在我们有:
...--G--H <-- develop, main
目前,两个名字select提交H
,但是一旦我们开始新提交,这会改变。所以我们需要知道我们正在使用哪个 name 来查找提交 H
。为了在我们的图纸中标记这一点,让我们将名称 HEAD
全部大写附加到一个 b运行ch 名称:
...--G--H <-- develop, main (HEAD)
我们现在正在使用提交 H
,但是通过 name main
来实现。如果我们 运行:
git checkout develop # or git switch develop, which does the same
我们得到:
...--G--H <-- develop (HEAD), main
我们仍然使用提交H
,但我们是通过名称develop
现在。
如果我们现在进行新的提交 I
,它的创建方式与我们在 main
上的方式相同,但是由于我们现在使用 develop
,更新的是这个名称:
...--G--H <-- main
\
I <-- develop (HEAD)
名称 main
没有移动,因为它不是我们 使用的名称 。
这是 Git 中的一般规则:每当您进行 新提交时 ,Git 将更新 当前 b运行通道名称。这是 HEAD
附加的名称。因此,如果您绘制您的提交(在纸上或白板上,或使用 git log --graph
获取 Git 来绘制它们),您将看到您现在的位置以及新提交的去向。它将 添加 ,在你使用的任何提交之后,通过你使用的任何名称。
让我们看看git merge
现在
假设我们开始于:
...--G--H <-- main
并创建两个新的 b运行ch 名称,br1
和 br2
,也指向 H
。我们也将很快停止在 name main
中绘图,只是为了 de-clutter 绘图一点。所以现在我们有:
...--G--H <-- br1, br2, main (HEAD)
我们现在选择 br1
作为 current b运行ch 并进行新的提交
I <-- br1 (HEAD)
/
...--G--H <-- br2, main
当我们进行第二次提交时,我们得到:
I--J <-- br1 (HEAD)
/
...--G--H <-- br2, main
然后我们切换到 branch-name br2
,以便我们返回到提交 H
时保存的文件,并开始对 br2
进行一些不同的更改:
I--J <-- br1
/
...--G--H <-- main
\
K--L <-- br2 (HEAD)
如果我们再次 git switch br1
我们将 Git 将提交-L
文件替换为提交-J
文件并“打开”br1
再次:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
如果我们现在 运行:
git merge br2
Git 会做复杂的合并过程。直接跳过 how 部分,让我们看一下合并的 result:如果一切顺利,Git 会生成一个新的 合并提交 M
。使 M
成为合并提交的原因在于,它 还 只是 指向提交 J
,而不是像任何新提交那样 =765=] 指向提交 L
,像这样:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
(现在你知道我为什么停止画名字了main
:它还在,只是很难画它and commit M
也是)。
这是 true 合并。为了实现它,Git 必须 合并工作: Git 必须弄清楚 H
和 J
之间发生了什么变化,以及什么在 H
和 L
之间更改。我们已经跳过了 和 Git 首先如何选择提交 H
,但事实是 Git 在这里选择 H
吗?提交 H
是最好的 shared 提交,在 both b运行ches br1
和br2
。 H
之前的所有提交都在两个 b运行 上。 Git 为这个合并操作调用 H
合并基础 。
然后 Git 必须为提交 M
创建一个新快照,将 组合 工作应用于 H
的快照,因此要么保留你的更改并添加他们的更改,要么——如果你想反过来看——保留他们的更改并添加你的更改(请注意,对于正常的日常合并,结果是相同的)。
这种work-combining 需要检查两个b运行ches 之一。合并提交 M
添加到 b运行ch,这就是 br1
现在指向 M
的原因。其他 b运行ch 名称不会移动:只有 current 名称会受到添加新提交的影响。
并非所有合并都是真正的合并。假设不是创建两个 b运行ches br1
和 br2
,而是创建新的提交 on 这两个 b运行ches,我们从 main
和 br1
开始,只在 br1
上提交两次,然后切换回到 main
:
I--J <-- br1
/
...--G--H <-- main (HEAD)
如果我们现在 运行 git merge br1
,这就是 Git 所做的:
- 它计算出两个 b运行 上的最佳共享提交。那是再次提交
H
。
- 对于真正的合并,Git 必须找到在提交
H
和提交 H
(合并的“我们的”一侧)之间完成了哪些工作,以及哪些工作在提交 H
和提交 J
之间完成(合并的“他们的”方面)。但是提交 H
是 提交 H
。 “我们”这边根本没有工作!
由于合并基础提交 是 当前 提交,因为 select 通过跟随 HEAD
编辑到提交的 b运行ch 名称,没有 不同的工作要合并 。进行真正合并的结果将是合并提交,其快照与现有快照 in 提交 J
.
完全匹配
Git 的 git merge
因此需要一个 short-cut。您可以使用 git merge --no-ff
告诉它 不要 这样做,但默认情况下, Git 会执行 fast-forward 而不是 合并。 (Git 将此称为 fast-forward 合并 ,但实际上并没有涉及合并。)为此,Git 只需滑动当前 b运行ch name 转发指向另一个提交,然后像 git checkout
:
一样检出该提交
I--J <-- br1, main (HEAD)
/
...--G--H
(没有理由再为图表中的扭结烦恼,但我暂时保留了它)。
请注意,与常规合并一样,此 fast-forward 合并 移动了 b运行ch 名称 。不过它没有做出任何 新提交,这就是它的特别之处。 它没有进行任何新提交的事实意味着将来,我们可能永远不会知道 Git 做了这件事。 如果我们删除 name br1
现在,我们得到:
...--G--H--I--J <-- main (HEAD)
看起来我们一直在 main
上提交。
让我们return到git fetch
我已经在基本背景部分提到了git fetch
。这就是您如何将您的 Git 软件连接到其他 Git 软件——任何使用 Git 协议的软件,真的。那正在/与其他一些存储库一起工作。如果我们称 your-software-plus-your-repository “你的 Git” 和 other-software-plus-other-repository “他们的 Git”,这:
- 从他们的 Git 获得他们拥有而你没有的任何提交,并且
- 让您的 Git 创建 and/or 更新一些名称。
这里的技巧是您的 Git 创建 and/or 更新的名称根本不是 b运行ch 名称。原因是您的 b运行ch 名称是 您的。 您 取得了 br1
和 br2
(或没有)。您的main
正在跟踪您您 添加的提交。覆盖这些名称以跟踪其他一些 Git 的提交,将是糟糕的!
所以你的 Git 根本不会那样做。相反,您的 Git 取了他们每个 Git 的 b运行ch 名称,例如 main
和 develop
和 feature/tall
,并在每个名称前加上 origin
(或其他名称,但让我们使用 origin
)和斜杠。他们的 main
变成你的 origin/main
,他们的 develop
变成你的 origin/develop
,依此类推。
你的Git现在创建或更新这些名字,这是你Git记住他们Git的b运行ch 个名字。所以现在你可能有:
I--J <-- main (HEAD)
/
...--G--H
\
K--L <-- origin/main
在您的 存储库中,或者:
I--J <-- br1 (HEAD)
/
...--G--H <-- main
\
K--L <-- origin/main
取决于您对您的 b运行ch 名称所做的操作。 他们的 main
——你的origin/main
——已经从指向H
,你之前指向L
,因为他们 添加了两个您没有的提交。您的 Git 从他们的 Git 获得了这两个提交,并将它们放入您的存储库中,现在它们具有与 Git 的两个提交相同的哈希 ID,因为它们是 相同 提交:bit-for-bit 在保存的快照和元数据方面相同,包括 parent 哈希 ID。
如果你运行git checkout main
得到这个:
I--J <-- br1
/
...--G--H <-- main (HEAD)
\
K--L <-- origin/main
您现在可以 运行 git merge origin/main
。您的 Git 将为该操作找到 合并基础 ,提交 H
,并注意这不需要真正的合并并且可以执行 fast-forward 相反,你会得到:
I--J <-- br1
/
...--G--H--K--L <-- main (HEAD), origin/main
在您自己的存储库中(这次我理清了问题)。
请注意,这些 origin/
前缀的名称不是 b运行ch 名称。我称他们为remote-tracking names。 Git 的文档称它们为 remote-tracking b运行ch names,但它们不是 b运行ch 名字:它们反映了其他人的 b运行ch 名字,但它们实际上并不是 b运行ch 名字您的 存储库。
稍等,我们快到了:b运行ch
的 upstream
现在说一下a b运行ch的上行设置。存储库中的每个 b运行ch 名称(但不是任何 remote-tracking 名称)都可以有 one 上游集。这是可选的,因此每个 b运行ch 名称可以有 no 上游。设置上游不会以任何方式改变 b运行ch。它只是使某些操作更方便:
- 如果你 运行
git merge
没有命名任何东西,Git 将使用当前 b运行 的 upstream 设置通道即git merge origin/main
表示找到origin/main
命名的commit,git merge
表示找到当前b命名的commit 运行ch 的上游.
- 如果你 运行
git rebase
不命名任何东西,Git 将使用当前 b运行ch 的上游。
- 如果你运行
git pull
, Git会使用当前b运行ch. 的上游
- 如果你运行
git status
, Git会在status中添加一些使用当前b运行ch的upstream得到的信息。
这些不是唯一的东西,但它们可以说是亮点。设置上游使得 Git 使用起来更 愉快 。但是,唉,每个 b运行ch 名称只能得到 one,而且你应该几乎总是将那个上游 设置为 相应的 remote-tracking 名称。即main
的上游应该是origin/main
,develop
的上游应该是origin/develop
,以此类推
(这里有一个小问题:当您创建自己的新 b运行ch 名称时——例如 feature/roy
,或 br1
,或其他任何东西——那里 在origin
上还没有相应的b运行ch。所以没有origin/roy
或origin/br1
,你不能set upstream yet。Git 让你等到你用完 git push
。我们稍后再谈。)
综合起来
我们现在知道:
git fetch
从其他 Git(例如,在 origin
)获取新提交并更新相应的 remote-tracking 名称;和
git merge
, 运行 单独合并在 上游 ,根据需要进行 fast-forward 或真正的合并。
运行 git pull
首先检查是否存在上游集(以便它可以执行 git merge
步骤),然后为您执行这两个命令。这就是它真正要做的。
但是等一下,等等,git pull origin main
呢?好吧,如果你还没有设置上游——或者即使你有——git pull origin main
会让你的命令 运行。首先,git pull
将行 的整个其余部分传递给 git fetch
,这样您就有了 运行:
git fetch origin main
git fetch
命令通常使用上游(例如origin/main
)知道转到origin
,但在这里我们明确告诉它“使用Git 在 origin
”。这里最后的 main
告诉它我们对 他们的 b运行ch 名称中的哪个特别感兴趣:它 限制了获取 .通常你的 Git 会让他们的 Git 列出 all 他们的 b运行ch 名字,然后把 all 他们的新提交。使用此命令,您的 Git 仅向他们的 Git 询问他们在 main
.
上的新提交
(这通常根本没有节省,因为稍后您需要他们的其他提交运行,如果您一次获得所有内容,那实际上 更有效率 比你 运行 多个 git fetch
操作。但它会加快 this git fetch
一点,有时;就是这样它会使 later git fetch
比原来慢。现在保存,以后付款,就像以前一样。除非你的网络连接速度慢得可怕或获取量异常大,很少值得这样做。)
无论如何,无论是否带来了他们在 main
上的任何新提交,您的 Git 都会更新您的 origin/main
。那么你的 git pull
运行s:
git merge -m "merge branch 'main' of <url> [into ...]" origin/main
(或多或少——它实际上在这里使用了原始哈希 ID,而不是名称 origin/main
,但效果是一样的)。所以你得到一个引用其他 Git 的 URL 和他们的 b运行ch 名称 main
.
的合并
由于不需要上游设置,这将获取他们的提交,更新您的 origin/main
,然后与您的 origin/main
合并。您可以通过 运行ning:
获得等价物
git fetch
git merge origin/main
虽然消息现在是:
merge branch 'origin/main' [into ...]
也就是说,只有git fetch
知道实际使用的URL;到 git merge
运行 时,它不知道 origin/main
刚刚从 URL 更新。 git pull
代码知道,因为git pull
代码运行git fetch
给你了,所以git pull
代码提供了一个替代日志消息。 (两条日志消息都不好,真的。They might as well just say here have code
or haaaaaands
.)
关于git push
的一些说明
在您的 存储库中进行了新提交,无论是使用git commit
、git merge
还是git merge
-as-run-by -git pull
,或者其他什么,在某些时候你应该发送这些新的提交到一些其他Git。这里通常的候选者是 origin
上的 Git。要推送一些提交,您将 运行,例如:
git push origin br1
此处的 origin
部分表示 联系 Git 在 URL 存储在名称 origin
下应答的 Git,即,含义与git fetch
基本相同。这里的 br1
部分是您提供两样东西的方式:
- last 提交您需要他们拥有(他们会自动获得所有 parent),并且
- 您希望他们在其存储库中设置的 b运行ch 名称。
请注意,与 git fetch
不同,它会在您的存储库中创建或更新 remote-tracking 名称,git push
命令提供 b运行ch 名称,供他们在 他们的 存储库中设置。这是因为这里没有任何 remote-tracking 名称的概念。您通常选择 one b运行ch 您希望他们设置的名称;当您使用 git fetch
时,您通常想要获得他们的 all 个 b运行ch 名称,并且您不知道他们创建了哪些名称,因此我们需要像 remote-tracking 名字这样的奇特的东西。
无论如何,所有这一切的结果是您的 Git 将您的提交交给他们 Git(在您的 br1
上,他们还没有;您的 br1
可能还有数十或数千次他们 do 对他们的 main
或其他任何东西的提交,并且您的 Git 不需要将它们作为两个发送Gits 已交换哈希 ID 并意识到这些已经共享)。然后你的 Git 礼貌地要求他们的 Git 创建或更新 他们的 br1
以合并新的提交。
如果他们还没有 br1
,他们可能会遵守。 (一些托管站点,如 Bitbucket 和 GitHub 和 GitLab 添加了权限规则,但基础 Git 没有任何这样的东西。)或者他们 做 有一个 br1
:在这种情况下,他们将遵守 只有当添加那些ommits 为它们fast-forward 操作。也就是说,假设您有:
I--J <-- br1
/
...--G--H <-- main
还有你运行git push origin br1
。他们有:
...--G--H <-- br1, main
如果他们将 I-J
放入他们的存储库,他们现在可以将名称 br1
向前滑动,以便他们拥有您拥有的所有提交,并且他们的 br1
现在指向 J
也是。但是 if 他们有:
...--G--H <-- main
\
K <-- br1
在他们的 存储库中,你给他们I-J
,他们这边的结果是这样的:
I--J [polite request: move `br1` here]
/
...--G--H <-- main
\
K <-- br1
如果他们这样做,他们将 失去 K
br1
。那是因为 Git finds 通过读取 b运行ch names 然后向后工作,通过那些 backwards-pointing 将提交连接到早期提交的箭头。 Git 不能前进,因为箭头不指向前方(并且是提交的一部分,无法更改:它们永远指向后方)。所以对于这种情况,他们会说:不,我不能将 I-J
添加到我的 br1
中,因为这会丢失一些提交 ,Git 报告作为 非 fast-forward 错误。
请注意 git push
不 运行 git merge
。它只是尝试添加提交 as-is。如果您需要将 I-J
与他们的 K
合并,您必须 运行 git fetch
才能提交 K
并更新您的 origin/br1
.然后你可以合并,以便 I-J
do 添加(在你的存储库中):
I--J-----------M <-- br1 (HEAD)
/ /
...--G--H <-- main /
\ ___________/
K <-- origin/br1
因为新的合并提交 M
指向 both K
and J
,这现在添加到他们的 br1
(你的origin/br1
),他们将接受向他们发送I-J-M
并要求他们设置他们的br1
指向 M
.
(您也可以选择变基而不是合并,但我们不会在这里讨论。)
现在,如果他们之前没有br1
,您将有:
I--J <-- br1
/
...--G--H <-- main, origin/main
在您的存储库中。你不会有一个origin/br1
。所以你 运行:
git push origin br1
并发送给他们 I-J
并要求他们 创建 br1
,他们服从。您的 Git 看到他们接受了礼貌的请求,并且 现在 您的 Git 在您的存储库中创建了您的 origin/br1
:
I--J <-- br1, origin/br1
/
...--G--H <-- main, origin/main
您现在可以将(您的)br1
的 上游 设置为(您的)origin/br1
:
git branch --set-upstream-to=origin/br1 br1
例如。但是,git push
不是让您将其作为单独的命令键入,而是允许您添加 -u
标志:
git push -u origin br1
-u
标志告诉git push
:如果推送成功,将just-pushed名称的上游设置为对应的remote-tracking名称。也就是说,在这种情况下,它意味着将br1
的上游设置为origin/br1
。请注意,如果推送 失败 ,则 -u
标志无效。
您可以使用 -u
标志,即使现在有上游设置。它将简单地 运行 git branch --set-upstream-to
命令和 覆盖 当前设置为新设置。 (如果 br1
的上游已经是 origin/br1
,这会用 origin/br1
覆盖旧的 origin/br1
,所以你甚至无法判断发生了什么。)但是大多数人更喜欢在他们的头脑中,将“在 origin
上创建新的 b运行ch”与“在原点上更新现有的 b运行ch”分开。如果你也喜欢这个,你会知道什么时候你想添加 -u
(在那里创建 b运行ch 并在此处设置上游)以及什么时候不需要(更新 b运行ch那里,不要在这里做任何事情)。
一旦你 将 br1
的上游设置为 origin/br1
,通常的 Git 设置意味着你可以 运行 git push
而你 在 br1
。所以你只需要git push -u origin br1
一次,当你创建它时,之后你只需要运行 git push
,不需要额外的输入。
结论
您概述的三个命令都非常不同。它们都是基于 git pull
表示 运行 git fetch
,然后 运行 第二个命令,默认情况下 git merge
。 second 命令,无论它是什么,总是在 current b运行ch.
上运行
我个人更喜欢 运行 git fetch
我自己。然后,对于像 main
这样我没有做任何工作的案例,我可能甚至懒得让 main
保持最新。我什至可以 删除名称 。我可以只使用名称 origin/main
代替:每个 git fetch
我 运行 更新我的 origin/main
。我不需要保留自己的 main
名字。
所以这里的问题是,你需要将分支main
合并到分支features
。当你 运行 git checkout features
和 运行 git pull origin/main
时,这不会用主提交更新分支 features
。它只会更新 main
但不会更新 feature
.
所以你需要的是。
再次结帐到主分支
git checkout main
从 main
拉取提交
git pull origin/main (or git pull)
改为feature
git checkout feature
合并 main
到 feature
git merge main
就是这样。您从 main
获得了对功能的提交。
我在另一个分支上时从远程分支中拉出有点困惑。 例如,如果我更改为 main,则拉:
git checkout main
git pull
我得到了远程更改的更新。不错。
但是,如果我在另一个分支上并且我想更新 main 而不更改为 main,我总是得到让我感到困惑的结果(除非我更改为 main,否则我不会真正得到更新)。
假设我在分支 'feature',我尝试:
git pull main
或 git pull origin/main
或 git 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
但是当我切换到 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(-)
你能帮我理解我哪里错了吗? 谢谢!
罗伊
首先,您确实 不能 合并到除您现在签出的 b运行ch 之外的任何其他内容.1
二、git pull
是什么意思2是:
- 运行
git fetch
,然后 - 运行 第二个 Git 命令。
第二个 Git 命令是您的选择,但如果不做出明确的选择,您通常会得到 git merge
。所以 pull
= fetch
+ merge
和 merge
只适用于 current b运行ch.
(您的另一个正常选项是 git rebase
。但是 git rebase
具有相同的限制,即只处理您现在 签出的任何 b运行ch 。它也有一些脚注,因为 Git 无法忍受让任何事情变得简单,但我们将再次忽略这些。)
所以,对于第一个近似值:
git pull
表示选择上游这个b运行ch,然后拉入这个b运行 ch(稍后定义upstream);git pull origin main
的意思比较复杂,后面会讲到;和git checkout main
然后git pull
表示为main
挑上游,拉入main
,因为“这个b 运行ch" 是main
。
除非你已经在main
,否则这三个命令中的none可以与其他任何命令交换.
1对此有一些警告——特殊情况 看起来像 合并,例如,即将到来的未来 Git 软件可能最终会摆脱这种限制——但现在就把它当作真理吧。
2在过去(如 2015 年之前,Git 2.6 之前),git pull
实际上是一个 shell 脚本运行 git fetch
,然后是 运行 适当的第二个命令。 C版本更快更高效,但是内部逻辑还是一样的。
必要的背景,没有它Git就没有任何意义
您可能使用Git来维护一堆文件。但是 Git 并不是真正的 about 文件。您也使用 b运行ches,但是 Git 也不是 b运行ches。最后,Git 就是关于 提交 。确实commitscontain文件,我们使用b运行ch名称来帮助我们(和Git)find提交,但 Git 与文件或 b运行ch 名称无关:它是 raison d'être 是 commit.
因此您需要知道 Git 提交是什么以及对您有什么作用。幸运的是,这部分非常简单。 Git 提交:
已编号。每个提交都会获得一个 唯一的 哈希 ID。 hash ID又大又丑random-looking,人类不可能记住。它 必须 ,因为它必须 唯一: 当你进行新提交时,它必须获得一个从未使用过的数字以前,现在这个号码再也不能用于任何 other 提交。
(由于 pigeonhole principle,这部分在数学上是不可能的,但是由于哈希 ID 很大,哈希 ID 将不可避免的失败推到了未来,到那时我们都计划死亡,所以我们不在乎。)
因为每个提交 都有一个 唯一编号,我们可以取任意两个 Git 存储库并简要介绍它们,使用
git fetch
或git push
。一个 Git 是发送者,一个是接收者。发送者列出他的提交哈希 ID,接收者检查他是否有这些哈希 ID。如果他这样做,他有 那些提交 。如果没有,他可以告诉他已经有哪些提交和他需要哪些提交然后接收方让发送方发送接收方需要的任何提交.现在两个 Git 存储库共享提交。 (你必须连接它们两次,每个方向一次,以获得完整的共享,并且有很多方法可以限制共享,但这是一般原则。)完全read-only:任何提交的任何部分一旦完成就不能更改。这是使哈希 ID 技巧起作用所必需的。
包含两件事:所有源文件的完整快照,以您(或任何人)制作时的形式永久冻结提交,以及一些 元数据 。元数据包括您的姓名和电子邮件地址以及您提交时的 date-and-time-stamp 等内容。但是元数据中还有很多其他内容,特别是 Git 添加了自己的 早期提交哈希 ID 列表 .
这 list-of-earlier-commit-hash-IDs,在每个提交的元数据中,连接 提交,向后。大多数提交(到目前为止)只有一个这样的哈希 ID,并且这些是我们首先要关注的。当我们确实只有一个哈希 ID 时,Git 将这个哈希 ID 称为提交的 parent。我们说提交 指向 它的 parent。这为我们提供了一种绘制提交的方法。
假设您存储库中的 最新 提交有一些丑陋的大哈希 ID,我们将其称为 H
表示“哈希”。提交 H
存储一些早期提交的哈希 ID:一个不同的、丑陋的 random-looking 东西,我们称之为 G
。这意味着提交 H
指向 之前的提交 G
:
G <-H
但是 G
是一个提交,所以它也有一个散列 ID 卡在 它的 元数据中。它指向一些较早的提交;我们称它为 F
:
... <-F <-G <-H
提交 F
有元数据,所以它指向另一个更早的提交,并且这会一直持续下去——或者至少,直到我们回到 有史以来第一次提交,字面上 不能 指向更早的任何内容。所以它没有:它有一个 empty 以前提交的列表。如果我们称该提交为 A
(并声称我们恰好有 8 次提交),则整个链为:
A--B--C--D--E--F--G--H
我懒得把箭头 画成 箭头了。它们都是提交的一部分,永远不会改变:H
总是 向后指向 G
,永远,G
向后指向F
永远,等等。
为了让所有这些都起作用,Git 需要一种快速的方法来 找到 提交 H
。 Git 一般文件所有内部 objects——提交和他们的支持的东西——通过它们的哈希 ID,使用一个简单的 key-value store 索引 by 哈希ID。所以 Git 找到 H
的快速方法是知道它的哈希 ID。
现在,您可以 记住每个 Git 提交哈希 ID。但是that way madness lies。为什么不让 us 记住哈希 ID,而让 computer 来做呢?这是 b运行ch 和其他名称的来源:
...--G--H <-- main
b运行ch name main
——这是一个可变的指针,不同于卡在提交元数据中的指针——告诉我们哪个提交是最新的 main
。如果我们进行 newer 提交 I
,Git 将 ar运行ge for I
向后指向 H
:
...--G--H <-- main
\
I
然后 re-point name main
到更新的提交:
...--G--H
\
I <-- main
现在“在”main
上的最新提交是提交 I
,而不是提交 H
。
更多 b运行ch 名称 = 更多指针
让我们回到这个设置,只有一个 b运行ch 名称 main
:
...--G--H <-- main
现在让我们创建一个新b运行通道名称,例如develop
。在 Git 中,b运行ch 名称 必须 指向 恰好一个提交 。我们可以选择八次提交中的任何一次,但这里最合适的可能是 最新的 一次,如果我们使用 git branch develop
来创建它,那就是 [=1022] =] 会选择这里。现在我们有:
...--G--H <-- develop, main
目前,两个名字select提交H
,但是一旦我们开始新提交,这会改变。所以我们需要知道我们正在使用哪个 name 来查找提交 H
。为了在我们的图纸中标记这一点,让我们将名称 HEAD
全部大写附加到一个 b运行ch 名称:
...--G--H <-- develop, main (HEAD)
我们现在正在使用提交 H
,但是通过 name main
来实现。如果我们 运行:
git checkout develop # or git switch develop, which does the same
我们得到:
...--G--H <-- develop (HEAD), main
我们仍然使用提交H
,但我们是通过名称develop
现在。
如果我们现在进行新的提交 I
,它的创建方式与我们在 main
上的方式相同,但是由于我们现在使用 develop
,更新的是这个名称:
...--G--H <-- main
\
I <-- develop (HEAD)
名称 main
没有移动,因为它不是我们 使用的名称 。
这是 Git 中的一般规则:每当您进行 新提交时 ,Git 将更新 当前 b运行通道名称。这是 HEAD
附加的名称。因此,如果您绘制您的提交(在纸上或白板上,或使用 git log --graph
获取 Git 来绘制它们),您将看到您现在的位置以及新提交的去向。它将 添加 ,在你使用的任何提交之后,通过你使用的任何名称。
让我们看看git merge
现在
假设我们开始于:
...--G--H <-- main
并创建两个新的 b运行ch 名称,br1
和 br2
,也指向 H
。我们也将很快停止在 name main
中绘图,只是为了 de-clutter 绘图一点。所以现在我们有:
...--G--H <-- br1, br2, main (HEAD)
我们现在选择 br1
作为 current b运行ch 并进行新的提交
I <-- br1 (HEAD)
/
...--G--H <-- br2, main
当我们进行第二次提交时,我们得到:
I--J <-- br1 (HEAD)
/
...--G--H <-- br2, main
然后我们切换到 branch-name br2
,以便我们返回到提交 H
时保存的文件,并开始对 br2
进行一些不同的更改:
I--J <-- br1
/
...--G--H <-- main
\
K--L <-- br2 (HEAD)
如果我们再次 git switch br1
我们将 Git 将提交-L
文件替换为提交-J
文件并“打开”br1
再次:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
如果我们现在 运行:
git merge br2
Git 会做复杂的合并过程。直接跳过 how 部分,让我们看一下合并的 result:如果一切顺利,Git 会生成一个新的 合并提交 M
。使 M
成为合并提交的原因在于,它 还 只是 指向提交 J
,而不是像任何新提交那样 =765=] 指向提交 L
,像这样:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
(现在你知道我为什么停止画名字了main
:它还在,只是很难画它and commit M
也是)。
这是 true 合并。为了实现它,Git 必须 合并工作: Git 必须弄清楚 H
和 J
之间发生了什么变化,以及什么在 H
和 L
之间更改。我们已经跳过了 和 Git 首先如何选择提交 H
,但事实是 Git 在这里选择 H
吗?提交 H
是最好的 shared 提交,在 both b运行ches br1
和br2
。 H
之前的所有提交都在两个 b运行 上。 Git 为这个合并操作调用 H
合并基础 。
然后 Git 必须为提交 M
创建一个新快照,将 组合 工作应用于 H
的快照,因此要么保留你的更改并添加他们的更改,要么——如果你想反过来看——保留他们的更改并添加你的更改(请注意,对于正常的日常合并,结果是相同的)。
这种work-combining 需要检查两个b运行ches 之一。合并提交 M
添加到 b运行ch,这就是 br1
现在指向 M
的原因。其他 b运行ch 名称不会移动:只有 current 名称会受到添加新提交的影响。
并非所有合并都是真正的合并。假设不是创建两个 b运行ches br1
和 br2
,而是创建新的提交 on 这两个 b运行ches,我们从 main
和 br1
开始,只在 br1
上提交两次,然后切换回到 main
:
I--J <-- br1
/
...--G--H <-- main (HEAD)
如果我们现在 运行 git merge br1
,这就是 Git 所做的:
- 它计算出两个 b运行 上的最佳共享提交。那是再次提交
H
。 - 对于真正的合并,Git 必须找到在提交
H
和提交H
(合并的“我们的”一侧)之间完成了哪些工作,以及哪些工作在提交H
和提交J
之间完成(合并的“他们的”方面)。但是提交H
是 提交H
。 “我们”这边根本没有工作!
由于合并基础提交 是 当前 提交,因为 select 通过跟随 HEAD
编辑到提交的 b运行ch 名称,没有 不同的工作要合并 。进行真正合并的结果将是合并提交,其快照与现有快照 in 提交 J
.
Git 的 git merge
因此需要一个 short-cut。您可以使用 git merge --no-ff
告诉它 不要 这样做,但默认情况下, Git 会执行 fast-forward 而不是 合并。 (Git 将此称为 fast-forward 合并 ,但实际上并没有涉及合并。)为此,Git 只需滑动当前 b运行ch name 转发指向另一个提交,然后像 git checkout
:
I--J <-- br1, main (HEAD)
/
...--G--H
(没有理由再为图表中的扭结烦恼,但我暂时保留了它)。
请注意,与常规合并一样,此 fast-forward 合并 移动了 b运行ch 名称 。不过它没有做出任何 新提交,这就是它的特别之处。 它没有进行任何新提交的事实意味着将来,我们可能永远不会知道 Git 做了这件事。 如果我们删除 name br1
现在,我们得到:
...--G--H--I--J <-- main (HEAD)
看起来我们一直在 main
上提交。
让我们return到git fetch
我已经在基本背景部分提到了git fetch
。这就是您如何将您的 Git 软件连接到其他 Git 软件——任何使用 Git 协议的软件,真的。那正在/与其他一些存储库一起工作。如果我们称 your-software-plus-your-repository “你的 Git” 和 other-software-plus-other-repository “他们的 Git”,这:
- 从他们的 Git 获得他们拥有而你没有的任何提交,并且
- 让您的 Git 创建 and/or 更新一些名称。
这里的技巧是您的 Git 创建 and/or 更新的名称根本不是 b运行ch 名称。原因是您的 b运行ch 名称是 您的。 您 取得了 br1
和 br2
(或没有)。您的main
正在跟踪您您 添加的提交。覆盖这些名称以跟踪其他一些 Git 的提交,将是糟糕的!
所以你的 Git 根本不会那样做。相反,您的 Git 取了他们每个 Git 的 b运行ch 名称,例如 main
和 develop
和 feature/tall
,并在每个名称前加上 origin
(或其他名称,但让我们使用 origin
)和斜杠。他们的 main
变成你的 origin/main
,他们的 develop
变成你的 origin/develop
,依此类推。
你的Git现在创建或更新这些名字,这是你Git记住他们Git的b运行ch 个名字。所以现在你可能有:
I--J <-- main (HEAD)
/
...--G--H
\
K--L <-- origin/main
在您的 存储库中,或者:
I--J <-- br1 (HEAD)
/
...--G--H <-- main
\
K--L <-- origin/main
取决于您对您的 b运行ch 名称所做的操作。 他们的 main
——你的origin/main
——已经从指向H
,你之前指向L
,因为他们 添加了两个您没有的提交。您的 Git 从他们的 Git 获得了这两个提交,并将它们放入您的存储库中,现在它们具有与 Git 的两个提交相同的哈希 ID,因为它们是 相同 提交:bit-for-bit 在保存的快照和元数据方面相同,包括 parent 哈希 ID。
如果你运行git checkout main
得到这个:
I--J <-- br1
/
...--G--H <-- main (HEAD)
\
K--L <-- origin/main
您现在可以 运行 git merge origin/main
。您的 Git 将为该操作找到 合并基础 ,提交 H
,并注意这不需要真正的合并并且可以执行 fast-forward 相反,你会得到:
I--J <-- br1
/
...--G--H--K--L <-- main (HEAD), origin/main
在您自己的存储库中(这次我理清了问题)。
请注意,这些 origin/
前缀的名称不是 b运行ch 名称。我称他们为remote-tracking names。 Git 的文档称它们为 remote-tracking b运行ch names,但它们不是 b运行ch 名字:它们反映了其他人的 b运行ch 名字,但它们实际上并不是 b运行ch 名字您的 存储库。
稍等,我们快到了:b运行ch
的 upstream现在说一下a b运行ch的上行设置。存储库中的每个 b运行ch 名称(但不是任何 remote-tracking 名称)都可以有 one 上游集。这是可选的,因此每个 b运行ch 名称可以有 no 上游。设置上游不会以任何方式改变 b运行ch。它只是使某些操作更方便:
- 如果你 运行
git merge
没有命名任何东西,Git 将使用当前 b运行 的 upstream 设置通道即git merge origin/main
表示找到origin/main
命名的commit,git merge
表示找到当前b命名的commit 运行ch 的上游. - 如果你 运行
git rebase
不命名任何东西,Git 将使用当前 b运行ch 的上游。 - 如果你运行
git pull
, Git会使用当前b运行ch. 的上游
- 如果你运行
git status
, Git会在status中添加一些使用当前b运行ch的upstream得到的信息。
这些不是唯一的东西,但它们可以说是亮点。设置上游使得 Git 使用起来更 愉快 。但是,唉,每个 b运行ch 名称只能得到 one,而且你应该几乎总是将那个上游 设置为 相应的 remote-tracking 名称。即main
的上游应该是origin/main
,develop
的上游应该是origin/develop
,以此类推
(这里有一个小问题:当您创建自己的新 b运行ch 名称时——例如 feature/roy
,或 br1
,或其他任何东西——那里 在origin
上还没有相应的b运行ch。所以没有origin/roy
或origin/br1
,你不能set upstream yet。Git 让你等到你用完 git push
。我们稍后再谈。)
综合起来
我们现在知道:
git fetch
从其他 Git(例如,在origin
)获取新提交并更新相应的 remote-tracking 名称;和git merge
, 运行 单独合并在 上游 ,根据需要进行 fast-forward 或真正的合并。
运行 git pull
首先检查是否存在上游集(以便它可以执行 git merge
步骤),然后为您执行这两个命令。这就是它真正要做的。
但是等一下,等等,git pull origin main
呢?好吧,如果你还没有设置上游——或者即使你有——git pull origin main
会让你的命令 运行。首先,git pull
将行 的整个其余部分传递给 git fetch
,这样您就有了 运行:
git fetch origin main
git fetch
命令通常使用上游(例如origin/main
)知道转到origin
,但在这里我们明确告诉它“使用Git 在 origin
”。这里最后的 main
告诉它我们对 他们的 b运行ch 名称中的哪个特别感兴趣:它 限制了获取 .通常你的 Git 会让他们的 Git 列出 all 他们的 b运行ch 名字,然后把 all 他们的新提交。使用此命令,您的 Git 仅向他们的 Git 询问他们在 main
.
(这通常根本没有节省,因为稍后您需要他们的其他提交运行,如果您一次获得所有内容,那实际上 更有效率 比你 运行 多个 git fetch
操作。但它会加快 this git fetch
一点,有时;就是这样它会使 later git fetch
比原来慢。现在保存,以后付款,就像以前一样。除非你的网络连接速度慢得可怕或获取量异常大,很少值得这样做。)
无论如何,无论是否带来了他们在 main
上的任何新提交,您的 Git 都会更新您的 origin/main
。那么你的 git pull
运行s:
git merge -m "merge branch 'main' of <url> [into ...]" origin/main
(或多或少——它实际上在这里使用了原始哈希 ID,而不是名称 origin/main
,但效果是一样的)。所以你得到一个引用其他 Git 的 URL 和他们的 b运行ch 名称 main
.
由于不需要上游设置,这将获取他们的提交,更新您的 origin/main
,然后与您的 origin/main
合并。您可以通过 运行ning:
git fetch
git merge origin/main
虽然消息现在是:
merge branch 'origin/main' [into ...]
也就是说,只有git fetch
知道实际使用的URL;到 git merge
运行 时,它不知道 origin/main
刚刚从 URL 更新。 git pull
代码知道,因为git pull
代码运行git fetch
给你了,所以git pull
代码提供了一个替代日志消息。 (两条日志消息都不好,真的。They might as well just say here have code
or haaaaaands
.)
关于git push
的一些说明
在您的 存储库中进行了新提交,无论是使用git commit
、git merge
还是git merge
-as-run-by -git pull
,或者其他什么,在某些时候你应该发送这些新的提交到一些其他Git。这里通常的候选者是 origin
上的 Git。要推送一些提交,您将 运行,例如:
git push origin br1
此处的 origin
部分表示 联系 Git 在 URL 存储在名称 origin
下应答的 Git,即,含义与git fetch
基本相同。这里的 br1
部分是您提供两样东西的方式:
- last 提交您需要他们拥有(他们会自动获得所有 parent),并且
- 您希望他们在其存储库中设置的 b运行ch 名称。
请注意,与 git fetch
不同,它会在您的存储库中创建或更新 remote-tracking 名称,git push
命令提供 b运行ch 名称,供他们在 他们的 存储库中设置。这是因为这里没有任何 remote-tracking 名称的概念。您通常选择 one b运行ch 您希望他们设置的名称;当您使用 git fetch
时,您通常想要获得他们的 all 个 b运行ch 名称,并且您不知道他们创建了哪些名称,因此我们需要像 remote-tracking 名字这样的奇特的东西。
无论如何,所有这一切的结果是您的 Git 将您的提交交给他们 Git(在您的 br1
上,他们还没有;您的 br1
可能还有数十或数千次他们 do 对他们的 main
或其他任何东西的提交,并且您的 Git 不需要将它们作为两个发送Gits 已交换哈希 ID 并意识到这些已经共享)。然后你的 Git 礼貌地要求他们的 Git 创建或更新 他们的 br1
以合并新的提交。
如果他们还没有 br1
,他们可能会遵守。 (一些托管站点,如 Bitbucket 和 GitHub 和 GitLab 添加了权限规则,但基础 Git 没有任何这样的东西。)或者他们 做 有一个 br1
:在这种情况下,他们将遵守 只有当添加那些ommits 为它们fast-forward 操作。也就是说,假设您有:
I--J <-- br1
/
...--G--H <-- main
还有你运行git push origin br1
。他们有:
...--G--H <-- br1, main
如果他们将 I-J
放入他们的存储库,他们现在可以将名称 br1
向前滑动,以便他们拥有您拥有的所有提交,并且他们的 br1
现在指向 J
也是。但是 if 他们有:
...--G--H <-- main
\
K <-- br1
在他们的 存储库中,你给他们I-J
,他们这边的结果是这样的:
I--J [polite request: move `br1` here]
/
...--G--H <-- main
\
K <-- br1
如果他们这样做,他们将 失去 K
br1
。那是因为 Git finds 通过读取 b运行ch names 然后向后工作,通过那些 backwards-pointing 将提交连接到早期提交的箭头。 Git 不能前进,因为箭头不指向前方(并且是提交的一部分,无法更改:它们永远指向后方)。所以对于这种情况,他们会说:不,我不能将 I-J
添加到我的 br1
中,因为这会丢失一些提交 ,Git 报告作为 非 fast-forward 错误。
请注意 git push
不 运行 git merge
。它只是尝试添加提交 as-is。如果您需要将 I-J
与他们的 K
合并,您必须 运行 git fetch
才能提交 K
并更新您的 origin/br1
.然后你可以合并,以便 I-J
do 添加(在你的存储库中):
I--J-----------M <-- br1 (HEAD)
/ /
...--G--H <-- main /
\ ___________/
K <-- origin/br1
因为新的合并提交 M
指向 both K
and J
,这现在添加到他们的 br1
(你的origin/br1
),他们将接受向他们发送I-J-M
并要求他们设置他们的br1
指向 M
.
(您也可以选择变基而不是合并,但我们不会在这里讨论。)
现在,如果他们之前没有br1
,您将有:
I--J <-- br1
/
...--G--H <-- main, origin/main
在您的存储库中。你不会有一个origin/br1
。所以你 运行:
git push origin br1
并发送给他们 I-J
并要求他们 创建 br1
,他们服从。您的 Git 看到他们接受了礼貌的请求,并且 现在 您的 Git 在您的存储库中创建了您的 origin/br1
:
I--J <-- br1, origin/br1
/
...--G--H <-- main, origin/main
您现在可以将(您的)br1
的 上游 设置为(您的)origin/br1
:
git branch --set-upstream-to=origin/br1 br1
例如。但是,git push
不是让您将其作为单独的命令键入,而是允许您添加 -u
标志:
git push -u origin br1
-u
标志告诉git push
:如果推送成功,将just-pushed名称的上游设置为对应的remote-tracking名称。也就是说,在这种情况下,它意味着将br1
的上游设置为origin/br1
。请注意,如果推送 失败 ,则 -u
标志无效。
您可以使用 -u
标志,即使现在有上游设置。它将简单地 运行 git branch --set-upstream-to
命令和 覆盖 当前设置为新设置。 (如果 br1
的上游已经是 origin/br1
,这会用 origin/br1
覆盖旧的 origin/br1
,所以你甚至无法判断发生了什么。)但是大多数人更喜欢在他们的头脑中,将“在 origin
上创建新的 b运行ch”与“在原点上更新现有的 b运行ch”分开。如果你也喜欢这个,你会知道什么时候你想添加 -u
(在那里创建 b运行ch 并在此处设置上游)以及什么时候不需要(更新 b运行ch那里,不要在这里做任何事情)。
一旦你 将 br1
的上游设置为 origin/br1
,通常的 Git 设置意味着你可以 运行 git push
而你 在 br1
。所以你只需要git push -u origin br1
一次,当你创建它时,之后你只需要运行 git push
,不需要额外的输入。
结论
您概述的三个命令都非常不同。它们都是基于 git pull
表示 运行 git fetch
,然后 运行 第二个命令,默认情况下 git merge
。 second 命令,无论它是什么,总是在 current b运行ch.
我个人更喜欢 运行 git fetch
我自己。然后,对于像 main
这样我没有做任何工作的案例,我可能甚至懒得让 main
保持最新。我什至可以 删除名称 。我可以只使用名称 origin/main
代替:每个 git fetch
我 运行 更新我的 origin/main
。我不需要保留自己的 main
名字。
所以这里的问题是,你需要将分支main
合并到分支features
。当你 运行 git checkout features
和 运行 git pull origin/main
时,这不会用主提交更新分支 features
。它只会更新 main
但不会更新 feature
.
所以你需要的是。
再次结帐到主分支
git checkout main
从 main
拉取提交git pull origin/main (or git pull)
改为
feature
git checkout feature
合并
main
到feature
git merge main
就是这样。您从 main
获得了对功能的提交。