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

Kol*_*kov 0 git version-control merge

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

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

图形化(右侧是更晚的时间)

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

          \                   /

           B------------------
Run Code Online (Sandbox Code Playgroud)

所以现在我们正在合并m,git默默地选择保持ch1的变化.

UPDATE

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

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

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

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

我们使用git错了吗?有什么我们想念的吗?从用户的角度来看,这对我来说毫无意义.

我们如何确保不会发生这种情况并且工作不会丢失?感谢您的任何帮助.

tor*_*rek 5

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

我可以,不过,告诉你如何找出原因.

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

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

合并为动词

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

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

       A1--A2--A3   <-- branch-A
      /
...--*
      \
       B1--B2--B3   <-- branch-B
Run Code Online (Sandbox Code Playgroud)

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

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

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

但现在,Git将自己选择合并基础.它会做到这一点通过查看提交图:提交大号[R ,父提交(S)的大号[R ,父母的父母,依此类推,直到它找到这里提交链迎了上来.

第2步:找到合并基础

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

$ git merge-base --all L R    # you can use names or hash IDs here
Run Code Online (Sandbox Code Playgroud)

Git将通过其哈希ID打印出它找到的所有合并库.理想情况下,将只有一个合并基础.如果存在多个合并基础,则会出现一个复杂的情况(这些结果来自"交叉融合"),我们不得不深入研究; 但几乎总是,只有一个哈希ID.

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

第3步:找到自基数以来的变化

现在,看看Git将会看到"我们改变了什么":

$ git diff --find-renames <merge-base> L
Run Code Online (Sandbox Code Playgroud)

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

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

$ git diff --find-renames <merge-base> R
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

注意,组合更改的结果是对称的:哪个提交是L而哪个是R是无关紧要的.无论你git checkout branch-Agit merge branch-B或者git checkout branch-B,然后git merge branch-A,在源代码树,你得到的将是相同的.有什么不同之处在于哪个分支获得合并提交,哪个LR是第一个或第二个父级.

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


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

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