因此我们的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是错误的吗?我们有什么东西不见了吗?从用户的角度来看,这对我没有任何意义。
我们如何确保这不会发生并且工作不会丢失?谢谢你的帮助。
答案 0 :(得分:5)
我不能告诉你为什么你得到的结果。
但是,我可以告诉您
当你运行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 checkout
或git 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;父母等等,直到找到提交链聚会的地方。
这是您需要命令行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变量中,将其写下来,无论如何)。你需要两次。
现在,看看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处理这种方式的方式因命令而异。我们在这里假装它没有发生。 : - )