Git在没有引发冲突的情况下破坏了新的变化,但没有引发冲突

时间:2017-12-22 23:58:14

标签: git merge version-control

因此我们的git存储库存在问题,我无法想象这是非常常见的(或者为什么有人会使用git?)

我们有一个主分支(分支A),文件已被更改。作为发布过程的一部分,我们创建了一个发布分支(分支B)。没有人触及分支B中的文件,而更改是在分支A中进行的。随着发布完成(错误和什么不是),我们将分支B合并回分支A,并发现分支A中所做的所有旧更改都已丢失,并采用分支B版本(将文件恢复为合并前完成的操作)

以图形方式(右侧是稍后时间)

A--ch1---*---ch2---ch3--ch4----m---

          \                   /

           B------------------

现在我们正在合并m,git正默默地选择保持ch1更改。

更新

此外,我们尝试在点m处将A合并到B中,但它的行为相同(git尝试使ch1成为最新的更改)。

更新2 根据下面的torek的答案,我按照步骤发现合并基础在上图中的ch3和ch4之间。

对我来说,提出的问题多于答案,因为为什么不仅仅根据合并基础之后发生的情况应用ch4更改。

为什么它认为ch1(在我们的例子中是初始提交)应该应用于所有后续更改。

我们使用git是错误的吗?我们有什么东西不见了吗?从用户的角度来看,这对我没有任何意义。

我们如何确保这不会发生并且工作不会丢失?谢谢你的帮助。

1 个答案:

答案 0 :(得分:5)

我不能告诉你为什么你得到的结果。

但是,我可以告诉您

第1步:忘记分支,只看提交

当你运行git merge时,Git并没有真正"合并分支":这是我们告诉自己更容易思考的更高级别的小说。相反,Git的git merge有两个主要部分。第一个是合并为动词,它包括查找一些更改集并将它们组合在一起。第二个是合并为名词(或形容词),它包括在大多数情况下进行提交,就像任何其他提交一样。这个合并提交的特别之处在于:合并为形容词,在单词前面#34;提交" - 它有两个提交,而不是通常的单亲。这个"有两个父母"影响 future 合并动作的方式。

合并为动词

为了执行合并操作,Git需要运行两个git diff命令。 1 这两个git diff命令比较一些特定的提交,Git称之为合并基础提交,针对另外两个特定提交,Git通常称之为分支提示

分支提示正是您所期望的,给出了一个图形的图形,其中后面的提交是向右的,而分支名称充当最尖端提交的指针:

       A1--A2--A3   <-- branch-A
      /
...--*
      \
       B1--B2--B3   <-- branch-B

我在这里用*标记的合并基础,松散地说,是两个分支重新加入的最右边的提交。当分支机构非常简单地分叉时,很明显哪个提交是合并基础。一旦你做了几次早期的合并,它就不那么明显了。

在任何情况下,Git都会让您通过运行git checkoutgit commit来选择一个提交。无论你现在检查了什么提交,例如branch-A的提示,当前提交。 2 当前提交还有几个名称,包括HEAD@,但一般情况下总会有一些当前提交。按名称检出分支会使该分支的提示成为当前提交。让我们为Left或Local或--ours调用此提交 L ,以便我们有一个方便的单字母名称。

Git会让选择其他提交。您可以运行git merge <hash-id>git merge <name>。无论你使用哪个,Git都会找到提交的哈希ID,因此能够直接使用其他提交。我们称之为 R ,对于Right或Remote或--theirs

但是现在,Git将自己选择合并基础。它将通过查看提交图来实现:提交 L R L R的父提交,父母&#39;父母等等,直到找到提交链聚会的地方。

第2步:找到合并基础

这是您需要命令行Git的地方。 GUI工具在这里没用。为了了解Git正在做什么,您必须自己找到合并基础提交。您可以使用命令行界面执行此操作:

$ git merge-base --all L R    # you can use names or hash IDs here

Git将通过其哈希ID打印出它找到的所有合并基础。理想情况下,将只有一个合并基础。如果存在多个合并基础,则会出现一个复杂的情况(这些结果来自&#34; criss cross merges&#34;)我们必须深入研究;但几乎总是,只有一个哈希ID。

保存此哈希ID(使用鼠标复制,将其保存在shell变量中,将其写下来,无论如何)。你需要两次。

第3步:找到自基数

以来的变化

现在,看看Git会看到什么&#34;我们改变了什么&#34;:

$ git diff --find-renames <merge-base> L

这将合并基础与 L 提交进行比较。无论出现什么差异,这都是Git认为我们所做的。您可能希望将此diff输出保存到文件中,以便您可以更轻松地阅读它。

然后,对 R 提交做同样的事情:

$ git diff --find-renames <merge-base> R

这就是Git在他们的分支中认为他们所做的事情。同样,您可能希望将此diff输出保存到文件中。

Git现在将组合这两组更改。无论我们改变什么,Git都会将这些更改应用到基础。无论他们发生了什么变化,Git也会将这些变化应用到基地。

如果这些变化符合您的预期,并且您将它们组合在一起,那么您应该得到您期望的组合。可能这些变化并不是您所期望的,并且您已找到问题的根源。

在任何情况下,在Git已经 - 或者认为它已经成功地组合了所有更改之后,Git将提交此组合结果。新提交将像任何其他提交一样在您当前的分支上进行,但将有两个父进程。第一个父项将提交 L ,第二个父项将提交 R

请注意,组合更改的结果是对称的:哪个提交是 L 并且 R 并不重要。无论您是git checkout branch-A还是git merge branch-B,还是git checkout branch-B然后git merge branch-A,您获得的源代码树都将是相同的。不同之处在于哪个分支获得合并提交,以及 L R 中的哪个是第一个或第二个父级。

现在,您还可以运行git log --all --decorate --oneline --graph并在输出中搜索运行git merge-base时找到的合并基础提交哈希(或其缩写部分)。您可以查看--graph绘制的图表,并尝试找出为什么您选择的两个提交的正确合并基础 L R 。它正确的合并基础,即使你不这么认为,你也无法做出选择:你只能选择 L R 。这些图表经常变得非常混乱,因此很难弄清楚为什么它是合适的合并基础;但它总是如此。

1 在很多情况下,Git在内部设法跳过很多实际的git diff,以使所有内容更快(更快);但这是在逻辑过程之上的所有优化,通过它我们可以推断出Git会做什么。

2 这在任何时候都是如此:Git总是有一些当前的提交。好吧,几乎所有时间:在一个新的,空的存储库中,根本没有提交,所以也没有当前的提交。在git checkout --orphan <newbranch>之后还有一个特例:在这种情况下,你是一个未出生的分支&#34;,目前还没有提交。 Git处理这种方式的方式因命令而异。我们在这里假装它没有发生。 : - )