【问题标题】:git shows it's merging the same branch into itselfgit 显示它正在将同一个分支合并到自身中
【发布时间】:2019-06-27 01:03:16
【问题描述】:

我不确定我执行了哪种 git 命令,但是当我运行 git log 时,我收到以下消息

https://github.com/aaa/my_repo_name的分支'xxx-2222'合并到xxx-2222中

我想我做了以下事情。

  1. 我有一个功能分支 (xxx-1111),并从中创建了一个新功能分支 (xxx-2222)。
  2. 然后,我从我的开发分支中重新设置了我的 xxx-1111 分支,然后将所有提交压缩,然后合并到开发中。
  3. 我开始在我的新分支 xxx-2222 上工作
  4. 我从开发分支变基 xxx-2222 并且也做了 git pull。

我想在此之后我会在日志中收到以下消息。

谁能告诉我这是什么意思,以及为什么会这样。如何追踪它。最后,在将其合并到开发之前,我是否需要做任何事情来修复它。

【问题讨论】:

  • 不确定您看到的奇怪提交背后的确切原因,但重新定位分支不是一个好主意。也不推荐。如果您要从另一个分支创建新分支,您需要做的就是分支合并以使它们保持同步。
  • @ChetanRanpariya - 你说“不推荐”在 git 中重新设置分支的来源是什么?我倾向于认为 rebase 是一个强大的工具,可以保持干净和可读的 git 历史,并且经常使用它,没有任何问题。
  • @Chetan 并不是说​​变基不是一个好主意。实际上这是的想法....一般规则是不要重新设置一个分支已经发布并且其他人可能已经开始工作......那么它不是那么好。但是考虑一下您正在处理的功能分支....如果该分支是您的,并且在它被合并到 master(或任何其他公共分支,就此而言)之前,您可以随意对其进行多次变基,即使它已经发布。
  • @GuarangShah 没有看到你的回购,我的直觉是你在这里遇到了问题:4。我从开发分支 rebase xxx-2222 并且也做了 git pull。 如果你 rebase 分支然后从 origin 拉,你基本上改变了本地历史然后试图合并旧的历史。只是一种预感。通常在变基之后你不会pull 而是强制push 以便用“新”分支覆盖旧分支(如果你想安全,你可以在变基之前放置一个标签,这样你就可以恢复原始状态)。
  • xxx-2222 of https://github.com/aaa/my_repo_namexxx-2222 不是同一个分支。前者在远程存储库上,后者在本地存储库上。 git pull origin -r xxx-2222 可以避免此类提交。使用-r,它会尝试重新定位到远程分支而不是合并它。

标签: git github


【解决方案1】:

TL;DR

您已运行git pullgit pull 命令的意思是:

  1. 为我运行git fetch
  2. 获取完成后立即为我运行第二个 Git 命令。

第二个命令默认为git merge,正是这个git merge导致了你的问题。通读下面的长篇讨论,了解原因。

我建议 Git 新用户避免git pull。自己运行git fetch。然后,如果合适的话,运行第二个 Git 命令——git merge,或git rebase,或者你可能想要使用的任何第二个命令——你自己。这让您有机会停下来看看 git fetch 获取了什么,然后再开始运行可能不合适的第二个命令。

首先,这不是同一个分支。这是一个不同的分支,具有相同的名称

类比是糟糕的推理方式,但有时它们很有用。想象一下你在一个聚会上,那里的每个人都叫鲍勃。他们都有相同的名字。这是否意味着他们都是同一个人?当然不是——但他们都是“鲍勃”。您只需要使用其他名称来区分它们。

这就是 Git 在这里尝试的:

merge branch ... of ... into xxx-2222

这里的两个空格被填上:

  • 他们​​他们的分支,并且
  • 用来和他们交谈的名字

以便您以后可以意识到:哦,那不是 我的 xxx-2222,那是他们的 xxx-2222。这有点像意识到你不是在和 Bob Jones 说话,而是在和 Bob Smith 说话。

那么,让我们来谈谈在 Git 中命名的事情吧。

提交具有唯一(但丑陋)的名称;人类使用分支名称

在任何和每个 Git 存储库中,您每次都可以依赖一个名称。该名称是一个哈希 ID。哈希 ID 是由git log 打印的大而难看的十六进制数字字符串,例如8dca754b1e874719a732bc9ab7b0e14b21b1bc10。这些 ID 是唯一的且从不重复,因此您要么确实 有提交 8dca754b1e874719a732bc9ab7b0e14b21b1bc10(这是 Git 存储库中的 Git 提交),要么没有(可能是因为你从来没有将您的 Git 存储库与 Git 本身的存储库混合在一起:除非您要编写一些代码来修改 Git,否则为什么要这样做?)。

每次提交都会存储您所有文件的快照以及一些元数据。元数据包括提交者姓名、电子邮件地址和日期和时间戳,还包括新提交之前的提交的原始哈希 ID。这意味着每个提交都会通过原始哈希 ID 记住其直接前任或

那个的意思是我们可以使用向后的箭头绘制提交的图片,像这样:

... <-F <-G <-H

这里,H 代表我们所做的 last 提交的哈希 ID。它会记住上一次提交的哈希 ID GG 依次记住提交 F 的哈希 ID,以此类推。

要使用commit H,我们必须记住它又大又丑的哈希ID。我们不需要再记住G,因为那是in H。我们不需要记住F的,因为我们可以使用H找到G,而GF的哈希ID。这种模式一直持续下去:我们需要做的就是记住 last 提交的哈希 ID。

但是当我们有一台电脑时,为什么要我们记住它呢?我们可以让计算机记住哈希ID。例如,让我们有名称 master 记住提交 H 的哈希 ID,如下所示:

...--F--G--H   <-- master

现在,当我们进行 new 提交时,我们将从 git checkout master 开始——这让我们提交 H 继续工作,并记住我们是“在分支 master 上” ”,正如git status 所说——我们会做一些工作,git addgit commit。 Git 将保存我们所有文件的新快照,并提供一些新的且独特的大丑陋哈希 ID,我们将其称为 I。新提交 I 会记住提交 H 的哈希 ID:

...--F--G--H
            \
             I

然后,作为提交的最后步骤,Git 会将新的哈希 ID 存储到我们的名称 master 中,因此 master 指向I 而不是 H

...--F--G--H--I   <-- master

I 记得 H,它记得 G,等等,所以我们的 master 只记得 I 的新哈希 ID 是可以的。

Git 是分布式的,这意味着有很多独立的存储库

这里有两个不同的 Git 存储库。一种是您自己本地机器上的 Git 存储库,您可以在其中运行 git checkoutgit addgit commit 等等。该存储库真正属于您:您对其拥有 100% 的完全控制权。

另一个在 GitHub 上结束了。从技术上讲,这是 他们的(GitHub 的)存储库。他们已将其大部分控制权交给了您,因此在最有用的方面它也是您的,但将其称为 他们的 存储库更方便(而且在技术上更准确),所以让我们在此处执行此操作.

仓库可以共享

这两个存储库不需要有相同的提交,但一般来说,您可能希望您在您的存储库中所做的任何新提交进入他们的 存储库。如果他们有任何您没有的新提交,您可能希望将它们放入您的提交中。为此,您需要将您的 Git 连接到他们的 Git,并让他们交换提交。

他们将通过哈希 ID 进行这种交换,因为哈希 ID 是真正通用的。他们的 Git 要么有一些哈希 ID,要么没有。您的 Git 有或没有一些哈希 ID。他们拥有的任何你没有的哈希 ID 都是你可以从他们那里得到的一些对象,之后你们都拥有它。您拥有的任何他们没有的 ID,您都可以提供给他们,然后你们俩都拥有它。

Git 很大程度上是围绕这个添加到存储库的想法构建的。很容易将他们的东西添加到您的 Git 中,并将您的东西添加到他们的 Git 中。要将他们的东西放入您的 Git,请运行 git fetch(或 git pull,首先运行 git fetch)。要将你的东西放到他们的 Git 中,你可以运行 git push

有时,您根本不想添加 东西。有时你想摆脱一些提交!你可以做到这一点——但 Git 不是为此构建的,所以它更难。我们稍后会看到。

考虑到这一切,让我们来看看 rebase 的解剖结构

关于git rebase 要了解的是它复制 提交。也就是说,它需要一些现有的提交,提取它,进行一些更改,然后从中进行新的提交。这个新提交就是一个 new 提交,它使 old 提交完全不受影响。

Git 必须这样做,因为提交的哈希 ID(或任何 Git 对象,实际上)只是该提交内容的加密校验和。如果您取出一个提交并更改它并进行新的提交,您将获得一个新的、不同的提交。因为 Git 是为 add 东西而构建的,所以这只是将一个新对象添加到您的存储库中。如果你有:

...--F--G--H--I   <-- master

并且您制作了一个略有不同的 I 的新副本,其父级现有 H,您会得到:

...--F--G--H--I
            \
             I'

其中I' 是副本。根本没有触及任何现有的提交。

棘手的部分是:分支名称会发生​​什么变化?嗯,一般来说,我们使用git rebase的原因是:

  • 获取现有的一系列提交,这些提交可以按原样进行,但不是基于正确的起点,并复制它们,以便它们基于正确的起点;或
  • 采用现有的一系列不完全正确的提交,并对其进行改进。

(有时我们会同时做这两件事,还有其他一些可能性,但这是 rebase 的两个重要原因。)

也就是说,我们可能有:

...--F--G--H   <-- master
         \
          I--J--K   <-- feature

IK 的提交都很好,但我们希望它们在 H 之后而不是在 G 之后。提交的父节点本身就是冻结的、不可更改的提交的一部分,因此为了获得我们想要的,我们必须将这三个复制到新的提交中:

             I'-J'-K'   <-- feature
            /
...--F--G--H   <-- master
         \
          I--J--K   [abandoned in favor of the new improved feature]

但是假设,在我们这样做git rebase 之前,我们已经共享原始的三个提交(带有它们独特的大而丑陋的哈希 ID)与其他一些 Git,比如 GitHub 上的那个?我们向他们发送了这些提交并告诉他们设置他们的分支名称feature 以记住哈希ID K。他们这样做了,而且他们在他们的 Git 中仍然有这三个我们想要放弃和摆脱的旧提交。

(在我们的 Git 中,我们可能仍然有一个名称附加到提交K。那个名称是origin/feature。图表应该是:

             I'-J'-K'   <-- feature
            /
...--F--G--H   <-- master
         \
          I--J--K   <-- origin/feature

但这不是关键部分。)

假设我们现在让我们的 Git 调用他们的 Git——GitHub 上的那个,我们称之为 originhttps://github.com/... 或其他任何东西——然后说:嘿,你这个 Git,告诉我什么提交你有,以及你为他们使用的名字。他们会说:我有master,它是提交H。我有feature,这是提交K所以如果我们真的摆脱了K,我们现在将重新下载K(还有IJ,因为我们必须得到整个链条)。

如果我们运行git fetch,我们一定会得到I-J-K 并更新我们的origin/feature 标签,以便我们知道他们的 feature 名称提交K,这我们现在/再次/仍然分享。但是git pull 不只是运行git fetch

git pull 运行 git merge(无论如何默认)

git pull 的第二步默认是运行git mergegit merge 命令接受一些参数来告诉它要合并什么,git fetch 提供它们。在这种特殊情况下(如果上图与您的情况相符)git merge 的参数将是:

  • 合并提交K;
  • 将结果合并中的消息设置为merge branch 'xxx-2222' of https://github.com/aaa/my_repo_name into xxx-2222

这部分——git pullplease merge commit K now 部分——是因为在 git fetch 之后,你的Git,在 你的 repo,这些提交:

             I'-J'-K'   <-- xxx-2222
            /
...--F--G--H   <-- master
         \
          I--J--K   <-- origin/xxx-2222

因此,您的 Git 会尽职尽责地找到提交 KK'(即提交 G)的合并基础,并完成所有工作来执行和提交合并,从而为您提供:

             I'-J'-K'-------M   <-- xxx-2222
            /              /
...--F--G--H   <-- master /
         \       ________/
          I--J--K   <-- origin/xxx-2222

您现在将看到两个提交的副本IJK——因为K' 真的K 和@ 的副本987654435@ 确实是J 的副本,以此类推。

本质上,你已经告诉 Git:是的,我喜欢我的新提交和改进的提交......我也喜欢我的旧提交,所以让我将两个集合联系在一起并制作 我的分支名称xxx-2222指向新的合并提交M

你可能想要的是运行git push --force-with-lease

而不是运行git pull——或者它的两个组件,git fetchgit merge——你可能想做的事情:

             I'-J'-K'   <-- xxx-2222
            /
...--F--G--H   <-- master
         \
          I--J--K   <-- origin/xxx-2222

是让您的 Git 调用他们的(来源/GitHub 的)Git 并向他们提供您的新提交 I'J'K'。这些是您使用git rebase 进行的替换,它们以某种方式改进了原始I-J-K 序列。然后你想让你的 Git 告诉另一个 Git:现在,我认为你的 xxx-2222 记得 K。如果是这样,我命令你让你的名字xxx-2222记住提交K'!。

如果你让你的 Git 结束这个 git push 操作:我现在礼貌地请求你,GitHub-Git,设置你的名字 xxx-2222 以便它记住提交 K',他们会说:不,我不会那样做,因为如果那样做,我会放弃我的 I-J-K 提交。 当然,这正是你希望他们做的。 p>

这里的风险是他们现在可能在他们的存储库中拥有I-J-K-L。也就是说,他们的xxx-2222 可能会记住一些新的提交L,它会记住提交K 等等。您可以使用此--force-with-lease 选项来处理该风险。这使用你的origin/xxx-2222——你的 Git 对他们的 Git 的 xxx-2222 的记忆——说 我认为你的 xxx-2222 是......

您可以使用git push --force,它会删除命令的 I think ... if so... 部分。那是一种更危险,但也更有力的git push,它可能会让他们继续前进并服从你的命令。

结论

记住几件事总是很重要的:

  • 提交图是什么样的?
  • 谁(哪个 Git)记住了哪些提交哈希 ID,在哪些名称下?

git push 命令将提交从您的 Git 发送到另一个 Git,然后要求或命令他们设置一些他们的名称以记住某个提交哈希 ID(每个名称一个哈希 ID)。 git fetch 命令从另一个 Git 获取提交到您的 Git 中,然后根据您的 Git 从他们的 Git 中看到的内容设置您的 origin/* 或其他 remote-tracking 名称。

这些不是完全对称的!使用git fetch,您的远程跟踪名称会得到更新,但这对您的分支 名称没有影响。使用git push,他们的分支名称会得到更新——或者他们会拒绝你的礼貌请求,因为更新会丢失一些提交。

自从git rebase 复制 提交到新的和改进的那些你现在让你的 Git 放弃旧的和不太好的那些有利于新的和改进的,你需要强行告诉 他们的 Git 做同样的事情:放弃一些旧的不太好的提交,转而支持新的和改进的提交。

请注意,当你这样做时——当你使用git push --forcegit push --force-with-lease——你是在告诉一个 Git 存储库丢失一些提交。如果这些提交已经传播到 more Git 存储库中怎么办?向 GitHub 存储库运行 git fetch 的每个人都已经获取了他们可以从 那个 存储库获得的所有新提交。你的旧的和不太好的提交现在可能传播得很广,很难摆脱。确保所有可能使用它们的人都明白您打算撤销和替换它们!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-06
    • 1970-01-01
    • 2022-01-05
    • 2013-02-21
    • 2013-11-09
    • 2019-01-04
    • 2010-12-15
    • 2019-03-11
    相关资源
    最近更新 更多