如何在Git仓库中解决合并冲突?

5350

我该如何解决Git仓库中的合并冲突?


40
以下博客文章似乎提供了一个很好的例子,可以帮助你处理Git中的合并冲突,并将你引导到正确的方向。 [处理和避免Git中的冲突] (http://weblog.masukomi.org/2008/07/12/handling-and-avoiding-conflicts-in-git) - mwilliams
4
你可以配置一个合并工具(kdiff3 http://jebaird.com/2013/07/08/setting-up-kdiff3-as-the-default-merge-tool-for-git-on-windows.html),然后使用git mergetool。当你在大型开发团队中工作时,你总会遇到合并冲突。 - Grady G Cooper
不要忘记,您可以通过定期合并下游来减轻大多数合并冲突! - Ant P
3
也请参考 http://www.git-tower.com/learn/git/ebook/command-line/tools-services/diff-merge-tools。 - Pacerier
Github.com有一种很好的可视化问题的方式。确保你看到这个答案。(它也有从站点上编辑和解决的方法,但那只是一个方便。) - mfaani
显示剩余3条评论
36个回答

3381

尝试:

git mergetool

它打开一个GUI,引导您通过每个冲突,并允许您选择如何合并。有时候需要一些手动编辑,但通常这已经足够了。这比完全手工完成要好得多。


根据 Josh Glover 的评论:

[此命令] 不一定会打开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
    
    您将会看到一个如下格式的 vimdiff 显示:
      ╔═══════╦══════╦════════╗
      ║       ║      ║        ║
      ║ LOCAL ║ BASE ║ REMOTE ║
      ║       ║      ║        ║
      ╠═══════╩══════╩════════╣
      ║                       ║
      ║        MERGED         ║
      ║                       ║
      ╚═══════════════════════╝
    
    这4个视图分别是:
    • LOCAL:当前分支中的文件
    • BASE:共同祖先,即这个文件在两次更改之前的样子
    • REMOTE:你正在合并到你的分支中的文件
    • MERGED:合并结果;这是保存在合并提交中并在将来使用的内容

    你可以使用 ctrl+w 在这些视图中导航。你可以直接使用 ctrl+w 后跟 j 来直接进入 MERGED 视图。

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

  3. 你可以像这样编辑MERGED视图:

    • 如果你想从REMOTE获取更改

      :diffg RE
      
    • 如果您想从BASE获取更改

    • :diffg BA
      
    • 如果您想从本地获取更改

    • :diffg LO
      
  4. 保存、退出、提交和清理

    :wqa 保存并退出 vi

    git commit -m "message"

    git clean 删除额外的文件(例如*.orig)。警告:如果不传递任何参数,它将删除所有未跟踪的文件。


58
如果你需要合并很多文件,可以使用"git mergetool -y"来省略一些按键操作。请注意,这不会改变原意,我会尽量使翻译更加通俗易懂。 - davr
395
除非您安装了GUI(图形用户界面),否则它不一定会打开GUI。对我来说,运行git mergetool使用的是vimdiff。您可以安装以下工具之一来代替使用:meld opendiff kdiff3 tkdiff xxdiff tortoisemerge gvimdiff diffuse ecmerge p4merge araxis vimdiff emerge - Josh Glover
31
好观点 Josh。在 Ubuntu 上,我使用 Meld 工具获得了最佳效果,其三方合并显示功能也不错。在 OSX 上,git 选择了一个漂亮的默认界面。 - Peter Burns
18
这打开了KDiff3。但我完全不知道如何使用它。 - David Murdoch
9
现在你也可以使用Beyond Compare 3 (git mergetool -t bc3)来进行操作。 - AzP
显示剩余13条评论

1791

以下是一个可能的使用案例,从最上面开始:

你将要拉取一些更改,但糟糕的是,你不是最新的:

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.

哒哒!


22
这很有帮助,因为我遇到了许多关于二进制文件(艺术资源)的合并错误,合并这些文件似乎总是失败,所以我需要始终使用新文件覆盖它而不是“合并”。 - petrocket
206
注意!"--ours"和"--theirs"的含义相反。"--ours"代表远程分支,"--theirs"代表本地分支。详见git merge --help - mmell
61
在我的情况下,我确认--theirs=远程仓库,--ours=我的本地仓库。这与@mmell的评论相反。 - Aryo
26
显然,只有在rebase时才会发生。请参考 这个问题 - Navin
213
朋友们,“ours”和“theirs”是相对于你是合并还是变基而言的。如果你正在进行“合并”,那么“ours”表示你要合并到的分支,“theirs”则表示你正在合并进来的分支。当你进行“变基”时,“ours”表示你要变基到的提交,而“theirs”则是指你想要变基的提交。 - user456814
显示剩余9条评论

782
我发现合并工具很少帮助我理解冲突或解决方案。通常,我更成功地查看文本编辑器中的冲突标记,并使用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.
>>>>>>>

中间的部分是共同祖先的样子。这很有用,因为你可以将它与顶部和底部版本进行比较,从而更好地了解每个分支上发生了什么变化,这使你更好地了解每个变化的目的。
如果冲突只有几行,那么这通常会使冲突非常明显。(知道如何解决冲突是非常不同的;你需要知道其他人正在做什么。如果你感到困惑,最好就直接把那个人叫到你的房间里,让他们看看你在看什么。)
如果冲突很长,那么我会将这三个部分分别剪切并粘贴到三个单独的文件中,例如“我的”,“共同的”和“他们的”。
然后我可以运行以下命令来查看导致冲突的两个diff块:
diff common mine
diff common theirs

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

提示二

有人已经提到过这一点,但理解每个差异块背后的意图通常对于理解冲突的来源以及如何处理冲突非常有帮助。

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

这将显示在共同祖先和你正在合并的两个分支之间影响该文件的所有提交记录。 (因此,它不包括在合并之前已经存在于两个分支中的提交记录。)这有助于忽略明显与当前冲突无关的差异块。
提示三:使用自动化工具验证更改。
如果您有自动化测试,请运行这些测试。 如果您有lint,请运行它。 如果是可构建项目,则在提交之前进行构建等。 在所有情况下,您需要进行一些测试以确保您的更改没有破坏任何内容。(甚至没有冲突的合并也可能会破坏正常工作的代码。)
提示四:提前计划;与同事沟通。
提前规划并了解其他人正在处理的工作可以帮助防止合并冲突和/或帮助尽早解决它们——当细节仍然新鲜在脑海中时。例如,如果您知道您和另一个人都在处理将影响相同文件集的不同重构,那么您应该提前与对方交流,并更好地了解每个人正在做出的更改类型。如果您依次而非并行地进行计划更改,您可能会节省大量时间和精力。 对于跨越大量代码的重大重构,您应严肃考虑依次进行:每个人在一个人执行完整的重构时都停止处理该代码区域。
如果你无法按照顺序工作(可能因为时间压力),那么至少要就预期的合并冲突进行沟通,这样可以在问题还新鲜时更快地解决它们。例如,如果同事在一周内连续提交了一系列破坏性的代码,你可以选择每天合并/变基一到两次该同事的分支。这样,如果你发现合并/变基冲突,就可以比等几周后将所有内容一次性合并在一起更快地解决它们。
第五个提示:
如果你不确定一个合并是否正确,请不要强制执行。
合并可能会让人感到不知所措,特别是当有很多冲突文件和冲突标记覆盖数百行时。通常情况下,在估算软件项目时,我们没有包括足够的时间来处理棘手的合并等额外项,所以花费几个小时来解决每个冲突感觉真的很烦人。
从长远来看,提前计划并了解其他人正在做什么是预测合并冲突并准备在更短时间内正确解决它们的最佳工具。

8
diff3 选项是合并时非常有用的功能。我遇到的唯一一个显示它的图形用户界面是Perforce的 p4merge,可以单独安装和使用,与Perforce的其他工具分开(我没用过,但听说有人抱怨)。 - alxndr
4
在尝试rebase时出现合并冲突: $ git log --merge -p build.xml 输出: 致命错误:--merge没有MERGE_HEAD? - Ed Randall
git config merge.conflictstyle diff3 - 谢谢您,先生。这真是太棒了,让我不必再费力寻找(并支付$$)一个好的三方合并GUI。在我看来,这更好,因为它显示了共同的祖先以及本地/远程,并显示了最后提交的日志行,这是(据我所知)没有GUI可以做到的。提交肯定有助于您确定哪些代码属于哪个分支。 - ffxsam
我发现有时diff3 conflictstyle会导致大量相似的巨大差异块,而默认设置会生成更小、更易管理的差异块。不幸的是,我没有一个可用于错误报告的重现器。但如果你遇到这个问题,你可以考虑暂时关闭该选项。 - Dave Abrahams
推荐使用diff3,这是一个大加分项。默认的冲突样式使得一些冲突无法解决。更多信息请参见https://dev59.com/pl4c5IYBdhLWcg3w59qQ#63739655。 - Tom Ellis

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

  2. 打开每个文件并查看差异;Git 会标记它们。希望很明显哪个版本的每个块应该保留。您可能需要与提交代码的其他开发人员讨论。

  3. 解决了文件中的冲突后,运行git add the_file

  4. 一旦您解决了所有冲突,请运行 git rebase --continue 或者 Git 在完成时提示执行的任何命令。


39
把 Git 理解为跟踪“内容”而不是跟踪文件。这样就很容易看出你更新的“内容”不在仓库中,需要添加。这种思考方式还可以解释为什么 Git 不跟踪空文件夹:虽然它们在技术上被视为文件,但没有任何内容可供跟踪。 - Gareth
7
由于内容有两个版本,所以发生了冲突。因此,“git add”的说法不正确。在解决冲突后,如果你只想提交那个文件,则使用“git add”和“git commit”是无效的(会出现“fatal: cannot do a partial commit during a merge.”错误)。 - Dainius
5
Thulfir:谁说要让一个分支与另一个分支完全相同了?有不同的情况需要合并,而不是“使一个分支与另一个分支相同”。其中一种情况是当你完成了一个开发分支,并想将其更改合并到主分支中时;之后可以删除开发分支。另一种情况是当你想要变基你的开发分支,以便更容易地最终合并到主分支中。 - Teemu Leisti
4
git add 将文件加入索引,但不会将其添加到仓库中。 git commit 才是将内容添加到仓库中的命令。这种用法对于合并操作很有意义——合并会自动将所有能够自动合并的更改加入暂存区;您需要在完成剩下的更改并将它们添加到索引后进行合并。 - Mark E. Haase
1
第二步让我感到有趣。听起来很简单。但有一天,你会遇到合并冲突,影响到数千个由已经不在公司工作的人编写的文件。唉... - Brian Knoblauch
显示剩余3条评论

127

当多个人同时修改同一个文件时,就会发生合并冲突。以下是解决方法。

git 命令行界面

以下是在出现冲突状态时应采取的简单步骤:

  1. 使用git status命令查看冲突文件列表(在Unmerged paths部分)。
  2. 按以下任一方式逐个解决每个文件的冲突:

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

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

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

      但是,您必须小心,因为冲突的远程更改有其原因。

      相关:Git中"ours"和"theirs"的确切含义是什么?

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

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

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

    如果您仍然有任何Unmerged paths下的文件,并且已经手动解决了冲突,则通过以下方式让Git知道您已经解决了它:git add path/file

  4. 如果所有冲突都已成功解决,请使用git commit -a提交更改,并像往常一样推送到远程。

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

查看实用教程,请访问:Katacoda的场景5 - 修复合并冲突

DiffMerge

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

它可以图形化地显示3个文件之间的更改,并允许自动合并(安全时)和完全控制编辑结果文件。

DiffMerge

图片来源: DiffMerge (Linux 截图)

只需下载并在仓库中运行即可:

git mergetool -t diffmerge .

macOS

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

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

如果没有提供,您可能需要以下额外的简单包装器放置在您的路径中(例如 /usr/bin):

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

然后,您可以使用以下键盘快捷方式:
  • -Alt-Up/Down 跳转到上一个/下一个更改。
  • -Alt-Left/Right 从左侧或右侧接受更改
或者,您可以使用opendiff(Xcode工具的一部分),它允许您将两个文件或目录合并在一起,创建第三个文件或目录。

111

请查看Stack Overflow问题中的答案Aborting a merge in 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 checkout -m" 的 "-m" 选项 - 它允许你将不同的文件提取回你的工作区。 - qneill
这救了我。分别查看每个文件使我能够记起我在每个分支中追求的目标,然后我可以做出选择。 - tim-phillips
假设我在 git 合并后解决了冲突,但是后来忘记了选择的选项是 ours 还是 theirs,那么之后,我该如何确定哪个选项被选择并在工作树和暂存区中准备提交? - tarekahf

82
如果你频繁地进行小的提交,那么可以使用git log --merge查看提交注释,然后使用git diff来查看冲突。
对于涉及多个行的冲突,更容易通过外部GUI工具了解情况。我喜欢opendiff--Git也支持vimdiff、gvimdiff、kdiff3、tkdiff、meld、xxdiff、emerge等工具,并且你还可以安装其他工具:git config merge.tool "your.tool"将设置你选择的工具,然后在合并失败后使用git mergetool即可显示差异和上下文。
每次编辑文件以解决冲突时,git add filename将更新索引,并且你的差异将不再显示。当处理完所有冲突并将它们的文件git add后,git commit将完成合并。

8
在这里真正的诀窍是使用"git add"。你可能甚至不想提交(也许你想要存储),但你必须使用"git add"来完成合并。我认为mergetool会为你执行添加操作(尽管在manpage中没有提到),但如果你手动合并,你需要使用"git add"来完成它(即使你不想提交)。 - Brent Bradburn

61

我希望能够全面接受我的版本或他们的版本,或者对每个变更进行审核并分别决定是否接受。

全面接受我的或他们的版本:

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

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命令行中工作。如何使用命令行mergetool应该是一个单独的问题。

您也可以安装用于此操作的可视化工具,例如meld并运行

git mergetool -t meld

它将会打开本地版本(我们的),"base"或者"merged"版本(合并后的当前结果)以及远程版本(他们的)。当你完成后保存合并版本,再次运行git mergetool -t meld直到得到"No files need merging",然后继续步骤3和4。


50

请参阅如何呈现冲突或者在Git中查看git merge文档,以了解合并冲突标记的含义。

此外,如何解决冲突部分详细说明了如何解决这些冲突:

看到了冲突后,你可以做两件事情:
1.决定不合并。你需要进行的清理只是将索引文件重置为HEAD提交以撤销2和清理工作树更改所做的3; 可以使用 git merge --abort
2.解决冲突。Git会在工作树中标记出冲突。将文件编辑成所需形状并将其添加到索引中git add。使用git commit来完成操作。
你可以使用多种工具解决冲突:
- 使用合并工具。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书的基本合并冲突章节中了解有关合并冲突标记及其解决方法的内容。


42

对于希望半自动解决合并冲突的Emacs用户:

git diff --name-status --diff-filter=U
显示需要解决冲突的所有文件。
逐个打开这些文件,或通过以下方式一次性打开全部文件:
emacs $(git diff --name-only --diff-filter=U)

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

ALT+x vc-resolve-conflicts

这将会打开三个缓冲区(我的缓冲区、他们的缓冲区和输出缓冲区)。按下'n'(下一个区域)、'p'(上一个区域)来切换。按下'a'和'b'分别将我的或他们的区域复制到输出缓冲区,或者直接编辑输出缓冲区。

完成后:按下'q'。Emacs会询问您是否要保存此缓冲区:是。 完成缓冲区标记为已解决,通过从终端运行:

git add FILENAME

完成所有缓存后,输入以下命令:

git commit

完成合并。


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