我在一个功能部门工作。从主分支运行git merge
后,我发生了两次冲突。
我手动解决了这些问题,然后仅提交了这两个文件,并运行了git reset --hard
以删除其他更改。
结果 - git认为其他文件已合并,而实际上,它们是100%来自我的功能分支。
如何修复此问题并与主分支“重新合并”?
答案 0 :(得分:0)
问题陈述本身只是一点点,因为git commit
写所有文件,而不仅仅是其中一些文件。我先调整一下,但可以跳到下一部分。 : - )
更具体地说 - 这可以帮助你考虑未来的Git-Git写索引中的任何内容(必须完全合并)。 git merge
命令在索引中构建合并两个(或更多但允许两个)提交的结果。如果存在冲突,Git会将冲突的文件存储在工作树和索引中。
然后,您可以编辑工作树中的文件。您可以使用git add
将工作树文件复制到索引中,将冲突的版本替换为单个解析版本。 1 您也是这样做的。您可以使用git reset
格式git reset paths
将paths
从HEAD
提交内容复制到索引中,替换当前的内容索引。
这是造成问题的最后一步:Git已经成功合并了其他文件(至少在Git'微小的小脑中:-))。然后你告诉Git它应该将这些文件的HEAD
(当前提交,合并前)版本复制到索引中,撤消合并对这些文件的影响。
1 这里的细节是,当存在冲突时,索引最终只保留一个文件的三个版本。例如,假设您已运行git merge other
,并且conflict.txt
从基本提交到HEAD
与基本提交之间的冲突更改为other
。这些冲突出现在工作树副本中,但与此同时,Git实际上将所有三个版本 - HEAD
和other
- 放入索引中,名称为{{1} }。例如,您可以使用conflict.txt
,git show :1:conflict.txt
和git show :2:conflict.txt
来查看它们。
运行git show :3:conflict.txt
告诉Git抛出三个额外的索引版本,只存储一个git add
文件作为正确合并的索引版本,为下一个:0:conflict.txt
命令做好准备。只要索引只有零编号版本,Git就会允许另一次提交。无论当时索引是什么,都成为新提交的内容。 (如果这些内容与当前或git commit
提交完全匹配,Git要求您添加HEAD
标记。这使得索引似乎是空的,但它不是:它&#39 ; diff 是空的。)
有几种方法可以解决这个问题。为了找到最好和最简单的,我们真的需要绘制一些图表。
具体来说,在你想要重做的合并之前,你有一系列的提交:
--allow-empty
你在...--o--B--o--L <-- mainbranch (HEAD)
\
o--o--R <-- feature
(因此它是mainbranch
)。这意味着HEAD
表示提交HEAD
(对于本地或左侧)。您运行了L
,它标识了提交git merge feature
(对于远程或右侧或其他R或者功能;无论如何,我通常将其称为R
)。然后Git计算了合并基础提交R
,这是两个选定的左右提交的历史记录首次加入。
Git然后跑了,实际上:
B
并结合两个差异。您解决了一些冲突,以及您git diff --find-renames B L
git diff --find-renames B R
离开git reset
版本的一些非冲突。由于冲突,Git让你自己运行L
,当你这样做时,你得到了这个:
git commit
新的合并提交...--o--B--o--L---M <-- mainbranch (HEAD)
\ /
o--o--R <-- feature
作为其内容,具有运行M
时索引中的内容。
我的猜测是你之前没有发现这个问题。你可能做了另一个git commit
并写了更多的提交,给出了:
git checkout feature
然后你又回到...--o--B--o--L---M <-- mainbranch
\ /
o--o--R--o--S <-- feature
并再次运行mainbranch
。我无法再调用新提示git merge feature
和L
,因此我会坚持使用R
和M
。通过跟踪S
和{{1}后面的所有连接(向左,包括向左和向下),我们找到M
和S
的合并基础同时:
M
或S
本身,我们必须从M
返回然后转发。S
之前的M
。o
看起来非常不错:我们可以从S
向R
向后退一步;我们可以从M
返回两步到达R
。因此合并基础为S
,Git在R
和R
上运行git diff
。然后Git看到R M
- 到 - R S
从R
代码中获取功能,而M
- 到 - feature
添加或修改功能。 Git试图将这些变化结合起来,结果很糟糕。如果合并本身有效,我们得到这个:
R
虽然由于&#34;撤消&#34;中的冲突可能无法正常工作从S
到...--o--B--o--L---M-----M2 <-- mainbranch (HEAD)
\ / /
o--o--R--o--S <-- feature
,与&#34;做&#34;从R
转到M
。
但我们甚至可能有这个,取决于主要分支上发生的事情:
R
(在这种情况下,合并基础仍为S
,但第一个差异是将...--o--B--o--L---M--N--M2 <-- mainbranch
\ / /
o--o--R--o--S <-- feature
与R
进行比较。
R
如果我们没有完全提交N
,和,如果它可以安全地删除&#34 ;完全提交M
,我们可以使用N
来做到这一点。我们可以M
(将git reset
附加到其中),git checkout mainbranch
提交HEAD
,然后执行此操作:
git reset --hard
然后L
将在...--o--B--o--L <-- mainbranch (HEAD)
\
o--o--R--o--S <-- feature
上运行差异,在git merge feature
上运行第二个差异,并尝试组合差异。您与B L
vs B S
的合并冲突大多相同,加上可能还有更多。
您可以采用相同的方式解决问题(重复早期工作)。或者,您可以保存提交B L
的标识,并从B R
中提取分辨率。虽然我们已从图纸中删除M
,但它实际上仍在存储库中。它只是隐藏;如果你在大约一个月内没有做任何其他事情,它会在以后真的消失。由于M
仍在那里,我们可以从中提取文件:
M
我们甚至可以在M
之前为git show <hash-id>:<path>
添加名称。例如,使用标签名称:
M
我们可以:
git reset
获取保存的文件。完成后,我们可以git tag temp-save-merge <hash-id>
删除提交git show temp-save-merge:<path>
的特殊名称(这将像以前一样让Git在30天后自动删除它,现在有了没有我们可以看到/到达它的名字。)
git tag -d temp-save-merge
如果已将提交M
推送到其他地方,和/或如果存在和/或已推送提交M
,则尝试删除可能不是一个好主意 M
存在。我们可能会成功将其从我们的存储库中删除,但它可能会在以后从其他存储库返回。我们必须强制推送以使其远离任何中央存储库,并说服使用该存储库的所有其他人放弃他们的 N
副本。如果我们需要M
,我们也必须将其恢复。
相反,我们可能希望重建我们希望不是M
的文件,并将这些更改置于N
之上。我将在此处的图纸中留下提交git reset
;如果N
实际上并不存在,我们只是建立在N
之上,结果完全相同。
首先,让我们摆脱N
(如果我们还没有实际那么,运行M
,这可能是最不成功的,并且可能没有被推到任何地方已经 M2
,或者git merge --abort
,如果我们有,那么我们有一个干净的索引和工作树,并有这个图:
M2
接下来,让我们创建一个 new 分支,指向提交git reset --hard
:
...--o--B--o--L---M--N <-- mainbranch
\ /
o--o--R--o--S <-- feature
给出了这个图表:
L
我们暂时不停画git checkout -b temp-merge <hash-of-L>
,因为它变得一团糟。我们现在可以运行:
----M--N <-- mainbranch
/ /
...--o--B--o--L <-- temp-merge (HEAD)
\ /
o--o--R--o--S <-- feature
重复我们之前错误做的相同合并。这将使我们得到一个新的和改进的#34;合并,虽然它将停止与以前相同的冲突。我们将从mainbranch
中获取正确合并的文件,添加它们并提交:
git merge feature
现在我们有了这个:
M
现在让我们使用git show <hash-of-M>:<path1> > <path1>
git show <hash-of-M>:<path2> > <path2>
git add <path1> <path2>
git diff --cached # inspect carefully
git commit
添加...--o--B--o--L---M2 <-- temp-merge (HEAD)
\ /
o--o--R--o--S <-- feature
的副本,然后尝试全部绘制:
N
此最终提交git cherry-pick mainbranch
是我们喜欢在分支 ----M--N <-- mainbranch
/ /
...--o--B--o--L---/-M2--N2 <-- temp-merge (HEAD)
\ //
o--o---R--o--S <-- feature
中拥有的内容。但是,我们正在做所有这些毛茸茸的事情的原因是保留现有的哈希ID N2
和mainbranch
,所以我们现在想要的是Git没有提供的东西:&# 34;他们的策略&#34;合并。 (见Is there a "theirs" version of "git merge -s ours"?)
但是我们可以使用其他答案中的一种方法获得我们所需要的东西。这是最明显的一个,假设您坐在工作树的顶层:
M
N
步骤开始合并,但使用git checkout mainbranch
git merge -s ours --no-commit temp-merge
git rm -rf .
git checkout temp-merge -- .
git commit
时,永远不会完成它。我们使用git merge
只是为了让它快速进行:这意味着&#34;保留--no-commit
&#34;的内容,这是非常错误的。然后我们删除一切!该指数现在真的是空的。
现在我们使用真正的技巧:我们从--ours
提交重新填充索引。 N
命令将我们拥有的所有内容替换为名称N2
指向的提交内容,即git checkout temp-merge -- .
。这在index和work-tree中都会发生,所以现在我们都设置为提交最终的合并结果。当我们这样做时,我们得到这个图:
temp-merge
其中内容 - 树提交N2
与 ----M--N---M3 <-- mainbranch (HEAD)
/ / /
...--o--B--o--L---/-M2--N2 <-- temp-merge
\ //
o--o---R--o--S <-- feature
完全相同,后者是更正后的树。
我们现在可以删除分支名称M3
。历史充满了我们的双重合并,因此绘制图形总是很乱。这是因为我们选择了选项2,其中我们不重写历史记录以消除我们之前的错误。这个错误一直保留,但这也意味着我们只添加了新的提交(N2
及其曾经被称为temp-merge
的侧链) ,因此使用此存储库克隆的其他人可以正常方式工作。
如果你可以使用选项1(重写历史,假装从未发生过错合并),你可能应该这样做。
你甚至可以使用一种混合:如果存在M3
,则从临时合并分支方法开始,将commit temp-merge
和cherry-pick M2
开始到N
。但是,不要将其与N2
合并,重命名旧的N
,并将临时合并重命名为mainbranch
,并提供:
mainbranch
您可能必须强制推送重命名的临时分支,这会让其他用户感到头疼,但如果我们稍后删除mainbranch
分支并绘制我们的图表,我们会得到:
----M--N <-- mistake
/ /
...--o--B--o--L---/-M2--N2 <-- mainbranch (HEAD)
\ //
o--o---R--o--S <-- feature
看起来很正常。 (并且,请注意,我们现在可以按照通常的方式在mistake
上合并...--o--B--o--L--M2--N2 <-- mainbranch (HEAD)
\ /
o--o-R--o--S <-- feature
。)这确实意味着我们已经重写了历史记录,但我们以一种简单的方式做到了:我们重新做了S
和N2
的合并,从提交L
中抓取正确合并的文件,然后我们抛弃了提交R
和M
而支持M
和N
。这实际上与选项1相同,只是我们保持错误分支,直到我们完成它。