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

问题描述 投票:1回答:3

我熟悉GIT并将其用于我的项目的版本控制。

我想知道的问题很少。我用Google搜索但无法得到好的答案。

所以问题是我有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分支,并将checkout到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 version-control dvcs
3个回答
3
投票

我认为,你对分支名称和git pull的使用有一些广泛的误解。让我将其分为几个部分,并为您提供此执行摘要概述:

  • push的对手不是pull,它是fetch;
  • git pull只运行git fetch,然后是第二个Git命令,通常是git merge,我相信新Git用户最好避免使用git pull,而是使用两个单独的命令;
  • pushfetch使用名称来传输哈希ID,这是由哈希ID识别的提交,这很重要;和
  • 对于git mergegit rebase,您当前的分支确实很重要。在推送或获取期间,当前分支无关紧要,但如果您使用git pull,则运行git mergegit rebase,现在当前分支很重要。

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

A branch name is only a pointer to a (single) commit

Git就是提交。从某种意义上说,如果我们仅仅是人类不需要分支名称,那么Git就会“喜欢它”,并且一直只是通过哈希ID来讨论提交。我可能会问你,如果你使用提交95ec6b1b3393eb6e26da40c565520a8db9796e9f,你会说“是”或“不,但我有那个”或“不,我还没有听说过那个”。

你提到:

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

但是在Git的眼里,树枝不会从另一个树枝上分枝。相反,每个提交都链接到先前或父提交。你画了这个:

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

这告诉我你认为提交只属于(或“在”上)只有一个分支。不过,这并不是Git看到他们的方式。相反,大多数提交同时在许多分支上。例如,考虑一下我们可能会绘制的图形:

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

每轮o代表一个提交。中间行的所有提交都在master上,但大多数提交也在br1br2上。关于master的最后一次(最新和最右边)提交仅限于master;其余的也在其他分支上。

这都是因为,在Git中,像master这样的分支名称只指向一个提交。如果我们以这种方式绘制提交图,那么名称指向的提交是最右边的提交,向左(先前)向右(稍后)。 Git将此提示称为提示,或者有时称为分支的头部(请注意这里的小写)。要查找您或Git可以通过此提示提交到达的其他提交,Git将通过其大丑陋的哈希ID(例如95ec6b1...)查找提交。同样,它是让Git找到提交的哈希ID。这个名字让Git找到这个哈希ID!

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

如果您运行:

git checkout br1

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

git add -a && git commit

并做一个新的提交 - 让我们把它画成*而不是o,以便我们可以看到它 - 这里的分支名称br1会发生什么:

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

我们用(HEAD)(注意:全大写)来绘制这个,以记住我们给git checkout的名字。新的提交*进入图表,并向后指向任何提交是br1的提示。与此同时,Git更改了名称br1,以便它指向我们刚刚创建的新提交。这就是分支在Git中的增长方式:我们在图中添加了新的提交,Git更新了HEAD附加的名称。

当然,如果我们从br1master的提示开始并向后工作,我们最终会回到单一的会议点提交并不是偶然的。但这些提交的汇合并不是由名字引起的。我们如何选择从br1的提示提交和master的提示开始并不重要;重要的是这两个特定的提交,然后我们在整个过程中找到的每个提交。

换句话说,分支名称让我们从提交图开始。这是最重要的提交图。名字只是起点!

The opposite of push is fetch

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

这意味着我们想要共享提交。正如我们上面所看到的,Git是关于提交和提交图的全部 - 但我们仅仅是人类需要名字才能让我们开始。这就是git fetch及其对手git push进来的地方。运行任一命令将我们的Git连接到其他一些Git。

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

最后,这意味着只需查看这些哈希ID,这两个Gits中的每一个都可以告诉我们其中一个提交的另一个提交。这就是我们的Git可以给他们的提交,他们不会-git push-或者他们的Git可以给我们的Git提交我们没有:git fetch

一旦他们发送了提交对象(以及完成这些提交所需的其他相关Git对象),那么两个Gits就需要为任何新的提示提交设置名称。这是分支名称开始重要的第二个地方。

获取方向更简单。你的Git打电话给其他一些Git。通常我们使用名称origin代表某个URL,其他Git正在侦听调用,因此我们运行git fetch origin。你的Git打电话给那个Git,然后问:你的分支机构名称是什么?那些哈希ID是什么?他们的Git告诉你的Git关于它的分支提示和哈希ID,你的Git要么说:啊,我有哈希ID或嗯,我没有那个哈希ID,请发给我那个提交,顺便说一下它是什么父哈希ID因为也许我也需要那个,等等。

最终,您的Git具有他们建议的所有提交和其他散列对象。现在你的Git将他们的分支名称,如他们的master,并保存这些名称。但是你有自己的分支机构。您的Git无法将这些名称保存为您的分支。它重命名所有这些名称。他们的分支,如master,成为你的Git的远程跟踪名称,如origin/master。请注意,您的Git只是在其分支名称前面使用您的简写名称origin和斜杠。

一旦git fetch完成,你的Git现在会记住他们的Git的分支提示,使用你的origin/*远程跟踪名称。您有他们的提交,以及任何所需的相关内容,以便您可以检查这些提交并获取与它们一起使用的文件,但任何新的提交只能通过这些远程跟踪名称找到。如果他们在分支提示提交时有一些旧的提交,那么您可能已经有其他方法可以找到它们。

git fetch的对应物是git push,但它不是完全对称的。例如,要对git push执行origin,你就像以前一样让你的Git调用他们的Git,但这一次,你想要发送它们。此发送操作的第一部分是移交您拥有的,他们不需要的任何提交。你通过让你的git push采取一些额外的论点来识别这些提交:

git push origin module1:module1

请注意,我在这里两次使用相同的名称。左侧的名称module1:用于查找要发送给它们的特定提交哈希。这是你的module1提示。右边的名称:module1部分是您希望它们使用的名称。 (这些名称不必相同!但如果你在每一方使用不同的名字,它会变得棘手,所以尽可能避免使用。)

当你运行git fetch origin时,你通常想要拿起你没有的所有东西。这是非常安全的,因为无论提交他们的master提示,你的Git会打电话给你的origin/master。无论他们有什么提示作为他们的module1,你的Git会称你的origin/module1。你的远程跟踪名称是你自己的私人条目,都是为那个名为origin的遥控器保留的,并且让它们立即更新是无害的,甚至是好事.1

git push不会这样工作。您向他们发送提交哈希ID,然后要求他们将其分支mastermodule1feature1设置为该哈希ID。他们让你的Git发送提交对象(以及他们需要的许多父提交和其他对象,所有对象都由哈希ID标识),如果他们还没有那个提交ID,然后他们自己评估是否允许你设置他们的分支名称。有时他们会,你的推动成功;有时他们会发现一条规则,说“不允许这样”,你的推动就会失败。

请注意,他们的规则取决于他们!您发送请求(“请将您的module1设置为a9fc312...”);他们可以查看当前的module1分支哈希ID,以及新发送的提交,并选择是否接受请求。您可以使用--force将其作为命令发送,但即使您这样做,他们仍然可以选择是否遵守命令。您所能做的就是发出请求(或强制命令),看看他们是否接受了它。但是大多数Gits在大多数情况下都会使用标准规则:如果只做一次添加新提交,则允许请求。

看看上面添加了对br1的提交时发生了什么。从原始提示提交中可以访问的现有提交仍然可以从新提示提交到达。新提交“成长了分支”。它没有改变任何现有的 - 没有新的提交可以改变任何现有的提交 - 但新提交的父提交是旧提示。如果我们从新的提示开始并向后工作,就像Git那样,我们就会得到旧提示。

相同的规则适用于git push:如果您发送给其他Git的请求将保留其所有现有提交可从新分支提示到达,则另一个Git可能会允许该请求。

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

git push origin module1 //Being in feature1 branch

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

我们没有给出两个部分。我们只给了一个部分。对于git push,如果你只给出一个部分,Git只是假设你的意思是“两次使用相同的名字”。所以git push origin module1毕竟意味着git push origin module1:module1

git pull is git fetch followed by a second Git command

然后你问了这个问题:

git pull origin module1 //Being in feature1 branch

git pull的作用很简单:

  1. 它运行git fetch与您传递的大多数参数: git fetch origin module1
  2. 如果可以,它会运行第二个Git命令,您可以提前选择它。要运行的默认命令是git merge。第二个命令的确切参数取决于git fetch期间发生的事情,但是在你看到进来之前你仍然需要选择git mergegit rebase

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

git fetch origin module1

这只是限制了获取:它找出了他们的module1,如果有必要通过ID提交提交,然后设置你的origin/module1.2它忽略了所有其他的名字。这里的module1很像git push,除非你不使用两个名字 - 如果你不写module1:module1-你的Git只会更新你的远程跟踪名称。 (你应该很少使用两个用冒号分隔的名字。它确实有用,可以用于某些目的,但你需要了解更多细节。)

git fetch期间,您检查了哪个分支并不重要。但是第二个Git命令是git mergegit rebase,对于这两个命令,你检查哪个分支确实很重要。

git pull运行的第二个命令在不更改当前分支的情况下运行。假设第二个分支是git merge,参数是:

  • 从远程Git获得的分支提示哈希ID3,和
  • 消息“合并分支'名称'的网址”

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

git merge对此做了什么本身有点复杂,因为git merge有时什么都不做,有时会做快进而不是合并,有时做真正的合并,有时会出现合并冲突,使合并停在中间并得到帮助您。我将把所有这些留给其他答案。

我们在这里总结一下:

  • 你的git pushgit fetch操作总是转移整个提交。他们从不处理单个文件:他们将提交从一个Git存储库复制到另一个存储库。复制提交的过程通常涉及在接收Git中设置一些名称,以便记住任何调整过的提示。在大多数情况下,这些并不关心您当前的分支(尽管可以告诉git push默认推送当前分支:请参阅push.default)。
  • 你的git pull只运行两个单独的Git命令。如果您还不熟悉这两个命令,我建议您分别运行每个命令。
  • 你的git merge命令是最复杂的命令,应该是一个单独的问题。如果你认为你没有运行git merge命令,请参阅上面的git pull

获得所有东西的一个缺点是它可能需要很长时间,如果有很多“一切”并且你的网络连接很慢。另一方面,如果你今天得到了所有东西,明天你几乎什么都得不到,所以接下来的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
投票

你是大致正确的。我想一下关于git分支的一些教程,或者对一个简单的存储库进行一些一般性实验将有助于巩固你对所描述情境中发生的事情的理解。

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

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

查询1:如果您正在使用Feature分支,提交了一些更改并将其推送到Module,该怎么办?

所以首先你做了一些新的提交,让我们说Feature上另外两个提交:

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

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

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

你在Feature的提交只是应用在Module分支的顶部(假设快速合并);更准确地说,Module分支指针被移动到提交'D',因此它基本上指向与Feature相同的一系列提交。注:我故意不提及强制合并提交,变基或其他潜在的复杂情况。

然后你决定不想在Module branch上进行这些更改,这里潜在的复杂因素是你使用了revert这个词。我相信你所描述的是你使用ModuleB分支指针移回git reset <commit hash for B>,这基本上是resets你的状态,然后将你的变化推送到Module

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

注意,如果您实际使用reverted进行git revert <commit hash to revert>提交,那么您将在Module分支中创建新的提交,这将撤消引入的更改。我有理由相信这不是你所描述的。

问题2:您的理解基本上是正确的。 git pull是一个git fetch,然后是git merge,所以可以认为它等同于:

git fetch origin module1
git merge origin/module1

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

一些方便的链接:


-1
投票

两个查询的答案应根据不同的情况而有所不同:

Situation 1: feature1 branch is branched off from the latest module1 branch (no commits made on module1 branch after created feature1 branch).

提交历史如下:

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

Situation 2: there has new commits on module1 branch after branched off feature1 branch.

提交历史如下:

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

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

© www.soinside.com 2019 - 2024. All rights reserved.