如何解决 Git 中的合并冲突

如何解决 Git 中的合并冲突?

答案

尝试: git mergetool

它会打开一个 GUI,逐步引导您解决每个冲突,然后您可以选择如何合并。有时之后需要进行一些手动编辑,但通常仅此一项就足够了。这肯定比手工完成整个事情好得多。

根据 @JoshGlover 评论:

除非您安装一个 GUI,否则该命令不一定会打开 GUI。为我运行git mergetool导致使用了vimdiff 。您可以安装以下工具之一,而不是使用它: meldopendiffkdiff3tkdiffxxdifftortoisemergegvimdiffdiffuseecmergep4mergearaxisvimdiffemerge

以下是使用vimdiff解决合并冲突的示例过程。基于此链接

第 1 步 :在终端中运行以下命令

git config merge.tool vimdiff
git config merge.conflictstyle diff3
git config mergetool.prompt false

这会将 vimdiff 设置为默认的合并工具。

第 2 步 :在终端中运行以下命令

git mergetool

步骤 3 :您将看到以下格式的 vimdiff 显示

╔═══════╦══════╦════════╗
  ║       ║      ║        ║
  ║ LOCAL ║ BASE ║ REMOTE ║
  ║       ║      ║        ║
  ╠═══════╩══════╩════════╣
  ║                       ║
  ║        MERGED         ║
  ║                       ║
  ╚═══════════════════════╝

这 4 个视图是

本地–这是当前分支中的文件

BASE –共同祖先,两次更改之前文件的外观

远程–将文件合并到分支中

已合并 - 合并结果,这就是保存在存储库中的内容

您可以使用ctrl + w在这些视图之间导航。您可以使用ctrl + wj直接进入 MERGED 视图。

有关此处此处的 vimdiff 导航的更多信息

第四步 。您可以通过以下方式编辑 MERGED 视图

如果您想从 REMOTE 获得更改

:diffg RE

如果要从 BASE 获得更改

:diffg BA

如果您想从本地获得更改

:diffg LO

步骤 5 。保存,退出,提交和清理

:wqa保存并退出 vi

git commit -m "message"

git clean删除由 diff 工具创建的多余文件(例如 * .orig)。

从顶部开始,这是一个可能的用例:

您将进行一些更改,但是,哦,您不是最新的:

git fetch origin
git pull origin master

From ssh://gitosis@example.com:22/projectname
 * branch            master     -> FETCH_HEAD
Updating a030c3a..ee25213
error: Entry 'filename.c' not uptodate. Cannot merge.

因此,您可以及时了解最新信息并重试,但是会发生冲突:

git add filename.c
git commit -m "made some wild and crazy changes"
git pull origin master

From ssh://gitosis@example.com:22/projectname
 * branch            master     -> FETCH_HEAD
Auto-merging filename.c
CONFLICT (content): Merge conflict in filename.c
Automatic merge failed; fix conflicts and then commit the result.

因此,您决定看一下更改:

git mergetool

哦,我的,哦,我的上游更改了一些内容,但是只是为了使用我的更改... 没有... 他们的更改...

git checkout --ours filename.c
git checkout --theirs filename.c
git add filename.c
git commit -m "using theirs"

然后我们尝试最后一次

git pull origin master

From ssh://gitosis@example.com:22/projectname
 * branch            master     -> FETCH_HEAD
Already up-to-date.

-

我发现合并工具很少能帮助我理解冲突或解决方案。通常,在文本编辑器中查看冲突标记并使用 git log 作为补充通常会更成功。

这里有一些提示:

秘诀一

我发现最好的是使用 “diff3” 合并冲突样式:

git config merge.conflictstyle diff3

这将产生如下冲突标记:

<<<<<<<
Changes made on the branch that is being merged into. In most cases,
this is the branch that I have currently checked out (i.e. HEAD).
|||||||
The common ancestor version.
=======
Changes made on the branch that is being merged in. This is often a 
feature/topic branch.
>>>>>>>

中间部分是共同祖先的样子。这很有用,因为您可以将其与最高和最低版本进行比较,以更好地了解每个分支上的更改,从而更好地了解每个更改的目的。

如果冲突只有几行,那么通常会使冲突非常明显。 (知道如何解决冲突是非常不同的;您需要了解其他人的工作。如果您感到困惑,最好将这个人叫到您的房间,以便他们可以看到您的情况。在。)

如果冲突时间更长,则将这三个部分分别剪切并粘贴到三个单独的文件中,例如 “mine”,“common” 和 “theirs”。

然后,我可以运行以下命令来查看导致冲突的两个差异块:

diff common mine
diff common theirs

这与使用合并工具不同,因为合并工具也将包括所有不冲突的差异块。我发现这会分散注意力。

技巧二

有人已经提到了这一点,但是了解每个差异块背后的意图通常对于理解冲突的根源和如何处理很有帮助。

git log --merge -p <name of file>

这显示了在共同祖先和您要合并的两个头之间触及该文件的所有提交。 (因此,它不包括合并之前两个分支中已经存在的提交。)这可以帮助您忽略显然不是当前冲突因素的 diff 块。

秘诀三

使用自动化工具验证您的更改。

如果您有自动化测试,请运行这些测试。如果您有绒毛 ,请运行它。如果它是一个可构建的项目,则在提交之前进行构建,等等。在所有情况下,您都需要进行一些测试以确保所做的更改不会破坏任何内容。 (哎呀,即使没有冲突的合并也可能破坏工作代码。)

秘诀四

未雨绸缪; 与同事沟通。

提前计划并了解其他人正在做什么,可以帮助防止合并冲突和 / 或帮助及早解决它们 - 尽管细节仍在脑海中。

例如,如果您知道您和另一个人都在进行都会影响同一组文件的不同重构,那么您应该提前进行交谈,以便更好地了解每个人的更改类型制造。如果以串行方式而不是并行方式执行计划的更改,则可以节省大量时间和精力。

对于跨越大量代码的大型重构,您应该强烈考虑串行工作:每个人都停止工作在代码的那个区域,而一个人执行完整的重构。

如果您不能连续工作(也许是由于时间紧迫),那么就预期的合并冲突进行交流至少可以帮助您在细节仍在脑海中的同时早日解决问题。例如,如果某个同事在一个星期的时间内进行了一系列破坏性的提交,则您可以选择在该周中每天一次或两次在该同事分支上进行合并 / 重新设置。这样,如果您发现合并 / 重新设置冲突,则与等待几周一次将所有内容合并在一起相比,可以更快地解决它们。

秘诀五

如果不确定合并,请不要强行执行。

合并可能会让人感到不知所措,尤其是当存在大量冲突文件并且冲突标记覆盖数百行时。通常,在估算软件项目时,我们没有为处理诸如粗糙的合并之类的开销项目留出足够的时间,因此,花数小时来剖析每个冲突似乎是一种真正的拖累。

从长远来看,预先计划并了解其他人正在做什么是预测合并冲突并为自己准备在更少的时间内正确解决它们的最佳工具。

  1. 确定哪些文件有冲突(Git 会告诉您)。

  2. 打开每个文件并检查差异; Git 将它们划分边界。希望可以很清楚地看到每个块的版本。您可能需要与提交代码的其他开发人员进行讨论。

  3. 解决冲突后,在文件git add the_file

  4. 解决所有冲突后,请执行git rebase --continue或完成时 Git 所说的命令。

请查看 Stack Overflow 问题中的答案, 中止 Git 中的合并 ,特别是Charles Bailey 的答案 ,该答案显示了如何查看有问题的文件的不同版本,例如,

# Common base version of the file.
git show :1:some_file.cpp

# 'Ours' version of the file.
git show :2:some_file.cpp

# 'Theirs' version of the file.
git show :3:some_file.cpp

同时更改文件时,会发生合并冲突。这是解决方法。

git CLI

当您陷入冲突状态时,请执行以下简单步骤:

  1. 请注意以下冲突文件列表: git status (在 “未Unmerged paths部分下)。
  2. 通过以下方法之一分别解决每个文件的冲突:

    • 使用 GUI 解决冲突: git mergetool (最简单的方法)。

    • 要接受远程 / 其他版本,请使用: git checkout --theirs path/file 。这将拒绝您对该文件所做的任何本地更改。

    • 要接受本地 / 我们的版本,请使用: git checkout --ours path/file

      但是,您必须要小心,因为出于某种原因完成了冲突的远程更改。

      相关: git 中 “我们的” 和 “他们的” 的确切含义是什么?

    • 手动编辑有冲突的文件,并查找<<<<< / >>>>>之间的代码块,然后从上方或下方选择版本===== 。请参阅: 如何提出冲突

    • 路径和文件名冲突可以通过git add / git rm解决。

  3. 最后,使用git status查看准备提交的文件。

    如果在 “未Unmerged paths下仍然有任何文件,并且您确实手动解决了冲突,那么请让 Git 知道已通过以下方式解决了该问题: git add path/file

  4. 如果成功解决了所有冲突,请通过以下方式提交更改: git commit -a然后像往常一样推送到 remote。

另请参阅: GitHub 的命令行解决合并冲突

有关实际的教程,请检查: 场景 5 - 修复 Katacoda 的合并冲突

差异合并

我已经成功使用了DiffMerge ,它可以在 Windows,macOS 和 Linux / Unix 上直观地比较和合并文件。

它可以以图形方式显示 3 个文件之间的更改,并允许自动合并(在安全的情况下)并完全控制编辑结果文件。

差异合并

图像来源: DiffMerge (Linux 屏幕截图)

只需下载并以以下方式在回购中运行:

git mergetool -t diffmerge .

苹果系统

在 macOS 上,您可以通过以下方式安装:

brew install caskroom/cask/brew-cask
brew cask install diffmerge

并且可能(如果未提供)您需要在 PATH 中放置以下额外的简单包装器(例如/usr/bin ):

#!/bin/sh
DIFFMERGE_PATH=/Applications/DiffMerge.app
DIFFMERGE_EXE=${DIFFMERGE_PATH}/Contents/MacOS/DiffMerge
exec ${DIFFMERGE_EXE} --nosplash "$@"

然后,您可以使用以下键盘快捷键:

  • - Alt- / 跳到上一个 / 下一个更改。
  • - Alt- / 接受来自左或右的更改

或者,您可以使用opendiff (Xcode 工具的一部分),它可以将两个文件或目录合并在一起以创建第三个文件或目录。

如果您要进行频繁的小提交,请首先使用git log --merge查看提交注释。然后git diff将向您显示冲突。

对于涉及多行的冲突,更容易查看外部 GUI 工具中发生的情况。我喜欢 opendiff - Git 还支持 vimdiff,gvimdiff,kdiff3,tkdiff,meld,xxdiff,可以直接安装,也可以安装其他工具: git config merge.tool "your.tool"将设置您选择的工具,然后是git mergetool合并失败后,将显示上下文差异。

每次您编辑文件以解决冲突时, git add filename将更新索引,并且您的差异将不再显示它。当所有冲突都已解决并且其文件已被git add -ed 时, git commit将完成您的合并。

请参阅如何显示冲突,或者在 Git 中查看git merge文档,以了解什么是合并冲突标记。

此外,“ 如何解决冲突”部分介绍了如何解决冲突:

看到冲突后,您可以做两件事:

  • 决定不合并。您需要执行的唯一清除操作是将索引文件重置为HEAD commit 以反转 2。并清除 2 和 3 对工作树所做的更改。 git merge --abort可用于此目的。

  • 解决冲突。 Git 将在工作树中标记冲突。将文件编辑为 shape 并git add它们git add到索引中。使用git commit来完成交易。

您可以使用多种工具来解决冲突:

  • 使用 mergetool。 git mergetool启动图形化合并工具,它将通过合并工作。

  • 看差异。 git diff将显示三向差异,突出显示HEADMERGE_HEAD版本的更改。

  • 查看每个分支的差异。 git log --merge -p <path>将首先显示HEAD版本的差异,然后显示MERGE_HEAD版本。

  • 看原件。 git show :1:filename显示公共祖先, git show :2:filename显示HEAD版本,而git show :3:filename显示MERGE_HEAD版本。

您还可以在Pro Git书的 “ 基本合并冲突 ” 部分中了解有关合并冲突标记以及如何解决它们的信息。

对于想要半手动解决合并冲突的Emacs用户:

git diff --name-status --diff-filter=U

显示所有需要解决冲突的文件。

依次打开每个文件,或一次打开所有文件:

emacs $(git diff --name-only --diff-filter=U)

当访问需要在 Emacs 中进行编辑的缓冲区时,键入

ALT+x vc-resolve-conflicts

这将打开三个缓冲区(mine,他们的缓冲区和输出缓冲区)。通过按 “n”(下一个区域),“p”(预置区域)进行导航。按 “a” 和“b” 分别将地雷或其区域复制到输出缓冲区。和 / 或直接编辑输出缓冲区。

完成后:按 “q”。 Emacs 询问您是否要保存此缓冲区:是的。在完成缓冲区标记后,可通过从末尾运行将其标记为已解析:

git add FILENAME

完成所有缓冲区类型后

git commit

完成合并。

我要么想要我的完整版本,要么想要查看单个更改并决定每个更改。

完全接受我或他们的版本

接受我的版本(本地版本,我们版本):

git checkout --ours -- <filename>
git add <filename>              # Marks conflict as resolved
git commit -m "merged bla bla"  # An "empty" commit

接受他们的版本(远程的,他们的):

git checkout --theirs -- <filename>
git add <filename>
git commit -m "merged bla bla"

如果要对所有冲突文件执行以下操作:

git merge --strategy-option ours

要么

git merge --strategy-option theirs

查看所有更改并单独接受

  1. git mergetool
  2. 查看更改并接受每个版本中的任何一个。
  3. git add <filename>
  4. git commit -m "merged bla bla"

默认mergetool命令行中工作 。如何使用命令行合并工具应该是一个单独的问题。

您也可以为此安装可视化工具 ,例如, meld并运行

git mergetool -t meld

它将打开本地版本(我们的),“基本” 或 “合并的” 版本(合并的当前结果)和远程版本(它们的)。完成后保存合并的版本,再次运行git mergetool -t meld ,直到得到 “不需要合并文件”,然后转到步骤 3 和 4。