我合并后只提交了一些文件。我如何重新合并其他人?

时间:2017-09-04 07:37:41

标签: git merge commit github-for-mac

我在一个功能部门工作。从主分支运行git merge后,我发生了两次冲突。

我手动解决了这些问题,然后仅提交了这两个文件,并运行了git reset --hard以删除其他更改

结果 - git认为其他文件已合并,而实际上,它们是100%来自我的功能分支。

如何修复此问题并与主分支“重新合并”?

1 个答案:

答案 0 :(得分:0)

问题陈述本身只是一点点,因为git commit所有文件,而不仅仅是其中一些文件。我先调整一下,但可以跳到下一部分。 : - )

更具体地说 - 这可以帮助你考虑未来的Git-Git写索引中的任何内容(必须完全合并)。 git merge命令在索引中构建合并两个(或更多但允许两个)提交的结果。如果存在冲突,Git会将冲突的文件存储在工作树和索引中。

然后,您可以编辑工作树中的文件。您可以使用git add将工作树文件复制到索引中,将冲突的版本替换为单个解析版本。 1 您也是这样做的。您可以使用git reset格式git reset pathspathsHEAD提交内容复制到索引中,替换当前的内容索引。

这是造成问题的最后一步:Git已经成功合并了其他文件(至少在Git'微小的小脑中:-))。然后你告诉Git它应该将这些文件的HEAD(当前提交,合并前)版本复制到索引中,撤消合并对这些文件的影响。

1 这里的细节是,当存在冲突时,索引最终只保留一个文件的三个版本。例如,假设您已运行git merge other,并且conflict.txt从基本提交到HEAD与基本提交之间的冲突更改为other。这些冲突出现在工作树副本中,但与此同时,Git实际上将所有三个版本 - HEADother - 放入索引中,名称为{{1} }。例如,您可以使用conflict.txtgit show :1:conflict.txtgit 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 featureL,因此我会坚持使用RM。通过跟踪S和{{1}后面的所有连接(向左,包括向左和向下),我们找到MS的合并基础同时:

  • 它不是MS本身,我们必须从M返回然后转发。
  • 它不是S之前的M
  • 但是o看起来非常不错:我们可以从SR向后退一步;我们可以从M返回两步到达R

因此合并基础为S,Git在RR上运行git diff。然后Git看到R M - 到 - R SR代码中获取功能,而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进行比较。

选项1:完全删除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天后自动删除它,现在有了没有我们可以看到/到达它的名字。)

选项2:保留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 N2mainbranch,所以我们现在想要的是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 。)这确实意味着我们已经重写了历史记录,但我们以一种简单的方式做到了:我们重新做了SN2的合并,从提交L中抓取正确合并的文件,然后我们抛弃了提交RM而支持MN。这实际上与选项1相同,只是我们保持错误分支,直到我们完成它。