当 Git 拉或推到其他分支中的不同分支时会发生什么

What happens when Git pull or push to different branch being in some other branch

我熟悉 GIT 并使用它来控制我的项目。

我想知道的问题很少。我用谷歌搜索,但没有得到好的答案。

所以问题是我有 master、module1、feature1 个分支。

master
 ---------- 
         | module1
         ----------
                  | feature1
                  ------------

module1 从 master 分支出来,feature1 从 module1 分支出来。

查询 1:在 feature1 分支中,如果我进行少量更改并提交并将其推送到 module1 会怎么样。

git add .
git commit -m "Changes of feature1"
git push origin module1 //Being in feature1 branch

feature1 到 module1 分支的代码发生了什么,module1 分支如何处理它。

我的理解:根据我的理解,feature1 更改将被推送到 module1 分支。后来我意识到我应该推送到 feature1 分支,然后我将把它推送到 feature1 分支,然后检出到 module1 分支并恢复我最近推送的代码。

查询 2 : 如果在 feature1 分支中,我通过以下方式拉取该分支中 module1 的代码怎么办

git pull origin module1 //Being in feature1 branch

我的理解 : module1 代码的更改将合并到我的 feature1 分支,与命令中的以下内容相同

git checkout moduel1
git pull origin module1
git checkout feature1
git merge module1

如果有任何冲突将会显示。我需要解决这个问题。

谁能帮我看看我的理解对不对。如果不是,请帮助我正确理解这个概念。提前致谢。

您大体上是正确的。我认为遵循一些关于 git 分支的教程,或者使用简单的存储库进行一些一般性实验,将有助于巩固您对所描述情况下发生的事情的理解。

对于您的具体问题,如果我们假设您已经分支两次,并且没有做出任何提交:

Master  |  A -> B 
--------        ^ 
Module  |       | (B)  
--------        ^ 
Feature |       | (B) 

问题 1:如果您在 Feature 分支上工作,提交了一些更改并将它们推送到模块会怎么样?

所以首先你做了一些新的提交,假设在 Feature:

上有 2 个额外的提交
Master  |  A -> B 
--------        ^ 
Module  |       | (B) 
--------         \
Feature |         C -> D

然后您将该更改推送到 Module,假设没有对模块提交任何其他更改,您可以像这样表示更改图:

Master  |  A -> B 
--------         \ 
Module  |         C -> D
--------               ^
Feature |              | (D)

你从 Feature 提交的内容只是应用在 Module 分支之上(假设是快进合并);更准确地说,Module 分支指针被移动到提交 'D',因此它基本上指向与 Feature 相同的提交系列。 N.B。我有意不提及强制合并提交、变基或其他潜在的并发症。

然后您决定不想在 Module branch 上进行这些更改,这里的潜在并发症是您使用了 revert 这个词。我相信您所描述的是您使用 git reset <commit hash for B>Module 分支指针移回 B,这实际上是 resets 您的状态到推送更改之前的状态至 Module

Master  |  A -> B 
--------        ^ 
Module  |       | (B) 
--------         \
Feature |         C -> D

请注意,如果您实际上 reverted 使用 git revert <commit hash to revert> 提交,您将在 Module 分支中创建新的提交,这将撤消引入的更改。我很确定这不是您所描述的。

疑问二:你的理解基本正确。 git pull 是一个 git fetch 后跟一个 git merge,因此它可以被认为等同于:

git fetch origin module1
git merge origin/module1

正如您正确陈述的那样,Module 中不存在于您当前分支中的任何更改都将被合并(N.B。故意假设没有变基)。如果存在 git 无法解决的合并冲突,您将需要手动解决冲突。

一些方便的链接:

我认为您对 b运行ch 名称和 git pull 的使用存在一些广泛的误解。让我把它分成几个部分,并给你这个执行摘要概述作为开始:

  • push对应的不是pull,而是fetch
  • git pull 只需 运行s git fetch 后跟第二个 Git 命令,通常是 git merge,我相信它最适合新的 Git 用户避免 git pull,而是使用两个单独的命令;
  • 虽然 pushfetch 使用 t运行 提供的哈希 ID 的名称,但是由哈希 ID 标识的 提交 事情;和
  • 对于 git mergegit rebase,您当前的 b运行ch 很重要。 push或fetch时,当前的b运行ch无所谓,但如果你用git pull,那运行s git mergegit rebase,而now 当前 b运行ch 很重要。

现在,让我们深入了解细节。

A b运行ch 名称只是指向(单个)提交的指针

Git 就是关于 提交 。 Git 会,在某种意义上,"like it" 如果我们只是人类不需要 b运行ch 名称,并且一直谈论通过哈希 ID 提交。我可能会问你是否使用提交 95ec6b1b3393eb6e26da40c565520a8db9796e9f,你会说 "yes" 或 "no, but I have that one" 或 "no, and I have not heard of that one yet".

你提到:

module1 is branched from master and feature1 is branched from module1.

但在Git眼里,b运行不会运行ch 来自另一个 b运行ch。相反,每个提交都链接到前一个或 parent,提交。你画了这个:

master
 ---------- 
         | module1
         ----------
                  | feature1
                  ------------

这对我来说表明您认为提交属于(或 "on")只有一个 b运行ch。不过,Git 并不这么看他们。相反,大多数提交同时在许多b运行ches上进行。例如,考虑一个我们可能会画成这样的图:

          o--o--o   <-- br1
         /
...--o--o--o   <-- master
         \
          o--o   <-- br2

其中每一轮 o 代表一次提交。中间行下方的所有提交都在 master 上,但是 大多数 这些提交 alsobr1br2.在 master 上的 last(最新和 right-most)提交在 master;其余的也在其他 b运行ches 上。

这都是因为,在Git中,一个b运行ch name like master 只指向one 提交。名称指向的提交是最右边的提交,如果我们这样绘制提交图,从左(较早)到右(较晚)。 Git 称其为 tip 提交,或者有时是 b运行ch 的 head(注意小写)。要从这个提示提交中找到您或 Git 可以到达的其余提交,Git 将通过其丑陋的大哈希 ID 查找提交,例如 95ec6b1...。同样,是 哈希 ID 让 Git 找到提交。 name 只是让 Git 找到这个哈希 ID!

提交本身存储父提交哈希 ID,因此 Git 将通过 哈希 ID 查找父提交,向后查找提交。该提交有另一个父 ID,依此类推。例如,向后遍历这个父哈希 ID 序列,一次提交一个,从晚到早,就是我们看到的 git log

如果你运行:

git checkout br1

然后做一些工作然后运行:

git add -a && git commit

并进行 new 提交——让我们将其绘制为 * 而不是 o 以便我们可以看到它——这是 b 发生的情况运行通道名称br1:

          o--o--o--*   <-- br1 (HEAD)
         /
...--o--o--o   <-- master
         \
          o--o   <-- br2

我们用(HEAD)(注:all-uppercase)画这个,以记住我们给git checkout取的名字。 new 提交 * 进入图表,并向后指向任何提交 br1 的尖端。同时 Git 更改 名称 br1 以便它指向我们刚刚进行的新提交。这就是 b运行ches 的增长方式,在 Git 中:我们向图中添加新的提交,并且 Git 更新 name HEAD 是 attached-to.

当然,如果我们从 br1master 的提示开始并向后工作,我们最终会回到单个 meeting-point 提交,这绝非偶然.但是这种提交的汇合并不是来自 names我们如何选择从br1的tip commit和master的tip commit开始并不重要;重要的是这两个特定的提交,然后我们一路上找到的每个提交

b运行ch 名称,换句话说,让我们在提交图中开始。 提交图最重要。名称只是起点!

push的反义词是fetch

以上所有内容都是关于在单个 Git 存储库中工作的。但是当我们使用 Git 时,我们会使用多个存储库。特别是,我们有 our 存储库我们在这里做自己的工作,但随后我们经常需要与存储在其他机器上的另一个 Git 存储库交谈,例如 GitHub 或雇主、朋友或同事提供的存储库。

这意味着我们想要分享 提交。正如我们在上面看到的,Git 是关于提交和提交图的——但我们人类需要 names 来让我们开始。这就是 git fetch 及其对应物 git push 发挥作用的地方。运行 任一命令都将我们的 Git 连接到其他一些 Git。

我们的 Git 拥有我们所有的提交,其中一些可能是我们所做的提交。我们 Git 的一些提交可能是我们从其他地方获得的提交。同样,他们的 Git 拥有他们拥有的所有提交,其中一些与我们拥有的提交相同。有些可能是不同的提交。但无论如何,所有 这些提交都由它们唯一的哈希 ID 标识。这些 ID 在世界各地的 every Git 中都是相同的,如果它们有相同的提交的话。如果他们没有我们的提交(因为我们刚刚提交),我们新提交的 ID 与他们拥有的每个提交 ID 都不同! (这看起来很神奇,但它只是密码数学——例如,其中一些类似于比特币背后的东西,尽管 Git 使用一组较弱的哈希。)

最后,这意味着这两个 Git 中的每一个都可以通过查看这些哈希 ID 来判断我们其中一个提交了哪些提交而另一个没有提交。这就是我们的 Git 可以提供我们拥有而他们没有的提交的方式——git push——或者他们的 Git 可以提供我们拥有但我们没有的 Git 提交: git fetch.

一旦他们发送了提交对象(以及完成这些提交所需的其他相关 Git 对象),这两个 Git 就需要设置 names 对于任何新的 tip 提交。这是 b运行ch 名称开始变得重要的第二个地方。

获取方向更简单。您的 Git 调用了其他 Git。通常我们用名字origin来代表一些URL,而另一个Git正在监听呼叫,所以我们运行git fetch origin。您的 Git 调用那个 Git,并询问它:您的 b运行ch tip 名称是什么?那些是什么哈希 ID? 他们的 Git 告诉你的 Git 它的 b运行ch 提示和哈希 ID,你的 Git 要么说: [​​= 254=]啊,我有那个哈希 ID 或 嗯,我没有那个哈希 ID,请把那个提交发给我,顺便说一句,它的父哈希 ID 是什么,因为也许我也需要那个,等等。

最终,您的 Git 拥有他们建议的所有提交和其他散列对象。现在,您的 Git 采用他们的 b运行ch 名称,就像他们的 master 一样,并且 保存这些名称。但是你有自己的b运行ches。您的 Git 无法将这些名称保存为 您的 b运行ches。它 重命名 所有这些名称。他们的名字运行像master一样成为你Git的remote-tracking名字,像origin/master。请注意,您的 Git 只是使用您的 short-hand 名称 origin,在他们的 b运行ch 名称前加上一个斜杠。

一旦 git fetch 完成,您的 Git 现在会记住 他们的 Git 的 b运行ch 提示在哪里,使用你的 origin/* remote-tracking 名字。你有他们的提交,加上任何必需的相关内容,这样你就可以检查这些提交并获取它们附带的文件,但是任何 new 提交只会是 通过这些 remote-tracking 个名字找到。如果他们有,如 b运行ch 提示提交,你的一些 older 提交,你可能已经有其他方法可以找到它们。

git fetch 的对应项是 git push,但它并不完全对称。做一个 git pushorigin,例如,你有你的 Git 像以前一样调用他们的 Git,但是这次,你想 发送 他们的事。此发送操作的第一部分是移交您拥有的、他们不需要的、他们将需要的任何提交。你通过让你的 git push 接受一些额外的参数来识别这些提交:

git push origin module1:module1

请注意,我在这里输入了两次相同的名称。左边的名字,module1:,是为了找到你想要发送给他们的特定的提交哈希。那是你 module1 提示提交 。右侧的名称 :module1 部分是您希望他们使用的名称。 (这些名称不必相同!但是如果您在每一侧使用不同的名称会变得很棘手,因此请尽可能避免。)

当你 运行 git fetch origin 时,你通常想要他们拥有而你没有的一切。这很安全,因为无论他们有什么提示提交作为他们的 master,你的 Git 都会调用你的 origin/master。无论他们作为 module1 提交什么小费,您的 Git 都会称其为您的 origin/module1。你的 remote-tracking 名字是你自己的私人名字条目,全部保留给那个名为 origin 的遥控器,立即让它们全部更新是无害的,甚至是一件好事。1

但是git push不是这样的。您向他们发送一个提交哈希 ID,然后要求他们设置他们的 b运行chmastermodule1feature1 , 到那个散列 ID。他们让你的 Git 发送提交对象(以及他们需要的尽可能多的父提交和其他对象,全部由哈希 ID 标识)如果他们还没有那个 commit-ID,然后他们自己评估他们是否会让您设置他们的 b运行频道名称。有时他们会,并且您的推送成功了;有时他们发现一条规则说 "don't allow this",而您的推送失败。

请注意,他们的规则由他们决定!您发送请求 ("please set your module1 to a9fc312...");他们可以查看他们当前的 module1 b运行ch 哈希 ID,以及您发送的提交(如果是新的),以及 选择 是否接受要求。你可以使用 --force 来作为命令发送,但即使你这样做了,他们仍然可以选择是否服从命令。您所能做的就是提出要求(或强制命令),然后看他们是否接受。但是有一个大多数 Git 大部分时间都使用的标准规则:如果请求唯一做的是 添加新提交 .

,则请求被允许

看看当您向上面的 br1 添加提交时发生了什么。现有提交都可以从 原始提示提交中 访问,但仍然可以从新提示提交中访问。新提交 "grew the branch"。它没有改变任何现有的——没有新的提交可以改变任何现有的提交——但新提交的父提交是旧的提示提交。如果我们从新的尖端开始并向后工作,就像 Git 所做的那样,我们就会到达旧的尖端。

相同的规则适用于 git push:如果您发送给另一个 Git 的请求将保持其所有现有提交可从新的 b运行ch 提示访问,则other Git 可能会允许该请求。

请注意,我们一直在使用 git push origin module1:module1,但您建议我们 运行:

git push origin module1 //Being in feature1 branch

我们(或on,因为git status会说on branch feature1).这里重要的是 git push 命令的两个 module1:module1 部分。这告诉我们的Git:使用我们的名称module1找到要推送的提交,并要求他们的Git设置他们的 module1 名字.

我们没有给出两个部分。我们只给了一部分。对于git push,如果你只给出一部分,Git只是假定你的意思是"use that same name twice"。所以git push origin module1毕竟是git push origin module1:module1

git pullgit fetch 后跟第二个 Git 命令

然后你问了这个:

git pull origin module1 //Being in feature1 branch

git pull 的作用很容易描述:

  1. 它 运行s git fetch 你传递给它的大部分参数:

    git fetch origin module1
    
  2. 如果可行,它 运行 是您提前选择的第二个 Git 命令。 运行 的默认命令是 git merge。第二个命令的确切参数有点取决于 git fetch 期间出现的内容,但您仍然必须选择 git mergegit rebase before ] 你会看到进来的是什么。

我们之前提到过,我们通常只是运行 git fetch origin,它会把origin所有的名字都带过来,然后你的Git再重命名。如果你 运行:

git fetch origin module1

这只是 限制了 获取:它找出他们拥有的东西作为他们的 module1,如有必要,通过 ID 带来提交,然后设置你的 origin/module1.2 它忽略了他们所有其他的名字。这里的 module1 很像 git push,除了如果你不使用两个名字——如果你不写 module1:module1——你的 Git 将只更新你的 remote-tracking 名字。 (而且你应该很少在这里使用用冒号分隔的两个名称。它确实有效并且可以用于某些目的,但你需要了解更多细节。)

git fetch 期间,您签出的 b运行ch 并不重要。但是 second Git 命令要么是 git merge 要么是 git rebase,对于这两个命令,它 确实 不管你签出的是哪个b运行ch。

第二条命令表示git pull运行s,运行s不改变当前b运行ch。假设第二个b运行ch是git merge,参数是:

  • 从远程Git获取的b运行ch tip hash ID3,以及
  • 留言"merge branch 'name' of url"

这很像运行ning git merge origin/<em>name</em>,虽然消息是有点不同。

git merge 的作用本身有点复杂,因为 git merge 有时什么都不做,有时做 fast-forward 而不是合并,有时会进行真正的合并,有时会发生合并冲突,导致合并在 mi 中停止dle 并得到你的帮助。我会把所有这些留给其他答案。

这里总结一下:

  • 您的 git pushgit fetch 操作总是 t运行sfer 整个提交 。他们从不处理单个文件:他们将 commits 从一个 Git 存储库复制到另一个。复制提交的过程通常涉及在接收 Git 中设置一些名称,以便记住任何调整后的提示提交。在大多数情况下,这些并不关心您当前的 b运行ch(尽管可以告诉 git push 默认推送当前的 b运行ch:请参阅 push.default ).

  • 您的 git pull 只是 运行 两个单独的 Git 命令。如果您还不是很熟悉这两个命令,我建议您 运行 每个命令分开。

  • 你的git merge命令是最复杂的一个,应该单独提问。如果您认为您 运行 不是 git merge 命令,请参阅上面的 git pull


1获得一切的一个缺点是它可能需要很长时间,如果有很多"everything" 并且您的网络连接很慢。另一方面,如果你今天什么都得到,明天你几乎什么也得不到,所以 next git fetch 会很快。如果你今天有选择地只得到一点,明天的git fetch可能会很慢。

2这假定您的 Git 版本至少为 1.8.4。在 1.8.4 之前的 Git 中,git fetch origin module1 带来了他们的哈希 ID,但随后丢弃了名称,未能更新您自己的 origin/module1.

3如果您 运行 git pull origin name1 name2、Git 将多个哈希 ID 传递给 git merge,它会产生什么 Git 调用 章鱼合并 。这几乎从来都不是你想要的,所以要避免这种情况。如果你避免 git pull 你就不会犯这个特别的错误!

根据不同的情况,两个查询的答案应该不同:

情况 1:feature1 分支从最新的 module1 分支分支出来(在创建 feature1 分支后没有在 module1 分支上进行提交)。

提交历史如下:

---A---B---…    master
        \
         C---D---E    module1
                  \
                   F---G---H   feature1
  • 如果你从本地feature1分支推送到远程module1分支,它可以让你成功,并且远程仓库中的module1分支将指向提交H.
  • 如果您将更改从远程 module1 拉取到本地 feature1 分支,它不会将任何提交拉取到您的本地 feature1 分支。它会说已经是最新的。

情况2:在feature1分支分支后module1分支上有新的提交。

提交历史如下:

---A---B---…    master
        \
         C---D---E---I---J    module1
                  \
                   F---G---H   feature1
  • 如果您将更改从本地 feature1 分支推送到远程 module1 分支,git 将停止推送并提示您先拉取,因为提交 I 和上图中的 J 不存在于 feature1 分支中。
  • 如果您将更改从远程 module1 拉到本地 feature1 分支,它会将更改从 origin/module1 分支合并到 feature1 分支(如您所想)。

即使pull/push到不同的分支也可以快进或合并,但是你最好在它自己的本地分支上执行pull/push。可以让不同分支的工作更干净