为什么在合并后,GIT会说“已经是最新的”,但是分支之间仍然存在差异?

42
我最开始在“newfeature”分支上工作,我被紧急叫去修复一个在主分支(live branch)上的bug。于是我创建了一个名为“generalmaintenance”的分支,完成了任务,并切换到develop分支将其合并。现在我想回到“newfeature”分支,并合并之前合并进该分支的更改。
当我切换到“newfeature”分支并将“develop”合并时,有3个文件存在冲突。
我解决冲突时遇到了问题,最终决定使用Aptana Studio 3(我的IDE)中“Team”菜单中的“Revert”命令。我期望这会使我回滚到合并之前的状态,似乎确实如此。
无论如何,当我再次合并“develop”时,它显示“Already-up-to-date”,但在比较这两个分支之间的文件时,它们非常不同,并且我在另一个分支中添加的更改未合并。
请问我现在该如何合并这两个分支?
3个回答

78

撤销合并与重置合并

我猜你实际上已经是最新版本了。

问题在于git revert不会撤销合并,它只会撤销合并所带来的更改。当你创建一个合并提交时,你正在组合这两个分支的提交历史。

合并

     develop
        |
A---B---C
 \       \
  E---F---M
          |
      newfeature

在上述情况中,develop 分支被合并到 newfeature 分支中,创建了 M 提交。如果你运行 git log newfeature 命令,你将会看到来自两个分支的所有提交记录,但是从 newfeature 分支的角度来看,所有这些变更都是由 M 提交完成的。 撤销操作 git revert 命令不会删除任何提交记录,而是创建一个新的提交记录,用于撤消先前提交所包含的更改。例如,如果你有一个包含以下差异的提交记录...
-This is the old sentence.
+This is the new sentence.

如果撤销这个提交,撤销命令将创建一个新的提交,只执行相反的差异,它只是翻转符号。

-This is the new sentence.
+This is the old sentence.

这对于撤销其他开发人员已提交的更改非常有用。它可以推进历史记录而不是更改历史记录。 撤销合并 然而,在非快进式合并的情况下,这可能会产生不良影响。
     develop
        |
A---B---C
 \       \
  E---F---M---W
              |
         newfeature

假设 W 是一个还原提交,您可以看到运行命令 git log newfeature 仍将包括来自 develop 分支的所有提交。因此,从 develop 进行额外的合并将不起作用,因为它没有发现您的分支中存在任何缺少的内容。
使用 git reset 而不是 revert。
在以后,如果该合并尚未与其他开发人员共享,您可能需要考虑使用命令 git reset --hard <ref>(其中 <ref> 是合并的提交哈希值)撤消合并。在上面的示例中,在创建了合并提交 M 后,运行命令 git reset --hard F 将得到以下结果。
     develop
        |
A---B---C
 \       \
  E---F---M
      |
  newfeature

正如您所看到的,这种技术并不会像某些人想象的那样消除提交,而是将您的分支移回到您选择的提交。现在,如果您运行git log newfeature,您只会得到提交FEA。现在合并实际上已经从您的分支历史中删除了,因此稍后尝试重新合并到develop将不会引起任何问题。
这种方法并不是没有其复杂性。请意识到,您现在正在修改历史记录,因此如果newfeature分支在M合并后被推送到远程分支,则git会认为您仅仅是过时了,并告诉您需要运行git pull。如果只有您在远程分支上工作,则可以放心地进行强制推送-git push -f <remote> <branch>。这将具有相同的重置效果,但是在远程分支上。
如果该分支被多个开发人员使用,并且他们现在已经从中拉取了内容-那么这是一个坏主意。这正是git revert有用的原因,因为它可以撤消更改而不更改实际历史记录。

在历史记录中使用重置选项仅适用于未共享的提交。

解决方案 - 恢复还原。

如果合并提交已经共享,那么最好的方法可能是在该合并上使用git revert。但是正如我们之前所说的,你不能简单地将分支合并回去并期望从该分支中重新出现所有更改。答案是撤销还原提交。

假设你在 newfeature 中撤销了合并后,在 develop 分支上进行了一些工作。你的历史记录将类似于以下内容。

         develop
            |
A---B---C---D
 \       \
  E---F---M---W
              |
         newfeature

如果你现在将develop合并到newfeature中,你只会得到D,因为它是唯一一个不在newfeature分支历史记录中的提交。你还需要撤销那个W提交-git revert W应该可以解决问题,然后再执行git merge develop
                 develop
                    |
A---B---C-----------D
 \       \           \
  E---F---M---W---M---G
                      |
                 newfeature

这将恢复原始合并提交所做的所有更改 - 实际上是由 CB 进行的,但在 W 中被撤销,然后通过新的合并提交 G 引入了 D。我建议在合并最近的更改到 develop 之前撤销撤销,我怀疑按照这个顺序做会更少地触发冲突。
TL;DR
撤销会创建一个“撤销提交”。当撤销撤销时,您需要在第一次撤销时创建的撤销提交上运行撤销命令。应该很容易找到,git倾向于自动注释撤销,以便它们以“Reverted”单词开头。 git revert <commit>

2
FYI:我进行了一些次要但重要的编辑,以纠正一些问题。最重要的是,我更正了有关还原合并提交的示例。它应该是 git revert W 而不是 git revert M。此外,第二个差异是错误的,它解释了一个还原提交。我修复了 +/- 符号。 - eddiemoya
我们吸取了教训 - 如果从生成撤销的 PR 的同一分支合并,则要小心进行 git diff 和审查提交! - f01
@AstitvaSrivastava 你合并的分支没有新的提交,但你仍然需要合并它?我对这些陈述感到困惑。如果没有新的提交,那么就没有什么可以合并的了。然而,你说的话给了我一个可能的线索。"将develop合并到newfeature"。你可能在错误的方式下进行合并。如果newfeature是基于develop的,并且develop没有改变,它对newfeature来说没有任何新的内容。尝试将newfeature合并到develop中 - 如果这是你需要的话。我不能确定。 - eddiemoya
@eddiemoya 实际上,发生的情况如下。最初,“develop”有更改,我将其合并到“newfeature”,但在此过程中遇到了冲突,我错误地解决了它们。然后我回滚以修复错误的冲突。然后当我再次尝试合并时,它说没有要合并的内容。然后我阅读了你的答案,并尝试撤销回滚。但是我仍然无法成功合并。后来我进行了硬重置并删除了远程分支,因为强制推送被禁用了。 - Astitva Srivastava
1
这个答案对我来说很有意义,直到它建议撤销还原提交。这样做如何允许特性分支从开发分支合并?在我的测试中,它并没有像这里解释的那样工作。 - paulvs
显示剩余3条评论

11

找到了一个折中的解决办法。但它有效。

无论eddiemoya回答的内容都非常有帮助。感谢你的解释。我遇到了相似的情况。在git diff <branch>中能看到很多内容,但git merge却说已经是最新的了。

而且由于日志中有很多撤销操作,我无法找到确切的还原提交。(是的,这很糟糕。一开始就不应该发生这种事情)

解决方案

git checkout branchX -- .

这将把branchX的所有更改推到我的当前分支。使用您喜欢的git客户端,取消暂存并还原不需要的更改。然后进行新提交,愉快地完成 :)

2
我也遇到了同样的情况。我的做法是创建一个新分支,然后从不同的提交中挑选所有需要的文件,再将这个分支合并。
操作步骤如下:
- 创建一个新分支 - 使用git cherry-pick -n xxxxxxx命令从不同的提交中获取所需的文件 - 然后提交这些文件 git commit -m '你的提交信息' - 最后将这个挑选出来的分支合并到目标分支

制作一个全新的代码仓库克隆使我可以完成合并操作。我认为Git在本地分支中存储了一些错误的信息。 - Emile Sonneveld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接