如何在合并多个提交时避免出现许多git冲突?

82

故事背景:在项目中途,我的同事从master创建了一个新分支并开始进行重构工作。我从master创建了自己的分支并开始在页面上添加新功能。虽然我们都经常提交代码,但只有我能够将代码合并到master(因为同事的更改太多,尚不能从主分支部署)。不幸的是,我们的一些工作依赖于相同的文件。所以在几天的工作后,当她想要将自己的更改合并到master时,她遇到了很多Git冲突。

my_branch    #---#----#-#-------#----#--#-----#---#----#----#
            /     \              \   \   \              \    \
master     *-------*--------------*---*---*--------------*----*----*
            \                                                     /
her branch   #------#-------#-----------#-----------#------------#

问题1是: 如何避免在多人同时修改同一文件时出现大量的git冲突?(或者在这种情况下最佳实践是什么?)

但这并不是我们问题的结束,...为了绝对正确,她试图从主分支进行变基到自己的分支(以获得我提交的更改),因此提交记录应该看起来像这样

my_branch    #---#----#-#-------#----#--#-----#---#----#----#
            /     \              \   \   \              \    \
master     *-------*--------------*---*---*--------------*----*----*
            \                   \            \                    /
her branch   #------#-------#----*------#-----*-----#------------#
这就是困扰我们的问题所在。在这些变基过程中,她正在解决这些冲突。但是 Git 不会记住她对冲突修复的决定,因此当她从 master 变基到 her-branch 时,她不得不再次修复相同的 Git 冲突,这些冲突之前已经被她修复过了。
问题2是:如何告诉 Git 记住从主分支(master)进行 Git 变基后的 Git 冲突修复,这样在下一次变基之后,我们就不必再次修复相同的冲突了?

哦,我忘了,我的 Git 版本是 1.7.4.1,她的应该也一样。 - equivalent8
2
我想要问的问题! - kakyo
2
又更新了一下 - 过去几年我只使用 git merge 而不是 git rebase。是的,有些人说使用 merge 会导致不正确的历史记录,这是不正确的。观看 https://www.youtube.com/watch?v=1ffBJ4sVUb4 以了解 git 中的所有内容(+ rebase 实际上可能是破坏性的)。我的观点是 git merge 更加高效。我想这就是为什么 GitHub 在处理拉取请求时也使用 merge 的原因。 - equivalent8
1
@equivalent8 这样做会让你的分支历史变得混乱,我更喜欢使用 rebase,因为它可以为我所使用的功能分支创建一个干净的 Git 历史记录。Git 合并在拉取请求方面非常出色,因为它提供了一个视觉表示,显示了何时将拉取请求合并到主分支中。 - D. Foley
@D.Foley 我完全同意,这样更加清晰。对于超小型项目和我独自工作的项目,我也使用rebase。但是实际上,对于有很多开发人员频繁提交的项目来说,使用merge更加实用,否则10%的时间将会花费在解决rebase冲突上。 - equivalent8
6个回答

87
幸运的是,git有一种处理这个问题的机制,称为git rerere。如果启用了git rerere,则每次解决冲突时,解决该特定冲突的方式都会被记住。如果再次出现相同的冲突,则自动使用相同的解决方案。以下是一些有用的文章: ...但基本上您只需要执行以下操作:
git config --global rerere.enabled 1

...然后就可以忘掉它,同时享受更轻松的变基/合并 :)


9
只是想补充一下,您可能还想运行 git config --global rerere.autoupdate true。此外,如果您希望团队中的其他人也能使用这些记录的解决方案,您可以与他们分享您的 .git/rr-cache 文件夹。干杯! - Adam Dymitruk
2
启用全局配置设置有什么不利之处吗? - bryanbraun
6
是的,如果你以某种特定方式记录冲突的解决方案,下次遇到完全相同的冲突时它将自动得到解决-假如你之前曾以错误的方式解决过(无论那是什么),你可能甚至不会注意到在未来它再次被自动地以相同方式解决。 - Mark Longair
可以针对特定的分支完成吗?我不希望冲突解决方案在整个项目中使用,但如果我可以选择该分支,那么会感觉更安全。 - FabioR
@FabioR 你需要为每个分支使用本地配置。使用 git config --local rerere.enabled 1 即可完成任务。 - Deekshith Anand
@DeekshithAnand,我认为--local无法按分支工作(除非ProjectRoot/.git/config被Git跟踪,这是不典型的)。--local是按存储库/项目设置的,而@FabioR想要避免这种情况(https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration)。 - Joshua Goldberg

13

让我分享一种可能解决变基冲突的方法。我称其为合并变基。如果您想要将具有许多提交和许多冲突的分支变基,则可以使用此方法。

首先,让我们创建一个temp分支,并强制通过常规合并显示所有冲突。

git checkout -b temp
git merge origin/master

按照常规方式解决所有冲突,并完成合并。

因此,temp分支现在显示了项目正确解决所有冲突后的外观。

现在,让我们切换回你未修改的分支(称之为alpha)。

git checkout alpha

在当前分支优先进行机械冲突自动解决的情况下,执行一次变基操作。

git rebase origin/master -X theirs

此时,项目代码可能已经破损或无效。不用担心,最后一步是通过添加单个提交记录从temp分支中恢复项目状态。

git merge --ff $(git commit-tree temp^{tree} -m "Fix after rebase" -p HEAD)

基本上,这一步使用低级 Git 命令创建一个新的提交记录,其项目状态(树形结构)与 temp 分支完全相同。然后,该提交记录将立即被合并。

就是这样。我们刚刚通过隐藏合并进行了变基。现在可以删除 temp 分支了。

git branch -D temp

此外,还有一个可交互的脚本可以完成同样的事情。可以在这里找到。


1
在我的控制台上,{} 被解释了,所以我必须用单引号来保护它们,就像这样:git merge --ff $(git commit-tree 'temp^{tree}' -m "Fix after rebase" -p HEAD) - aki
这个方法会像 Git rebase 一样保持线性历史吗? - jacob galam
是的,历史记录保持线性,就像常规的变基一样。 - Adam

6

确保使用--onto开关进行重新定位。

为了防止冲突,请使用浮动开发分支。每个开发人员将不断地重新定位他们的开发分支。这很容易,因为开发人员知道他刚刚实现了什么,并且不应该有解决冲突的问题。而不是重新定位,只需合并最终版本(它已经被重新定位)。


1
如果两个开发人员对同一行代码进行了不同的更改,冲突将是不可避免的,因为 Git 不知道应该保留或丢弃哪行代码。
至于从主分支进行变基,这真的不是理想的做法,除非你想故意更改提交历史(并可能更改你同事的提交历史)。
根据官方文档 关于变基

不要变基存在于你的仓库之外并且可能有人基于其工作的提交。

你应该使用 git merge

enter image description here


1

您可以压缩她的分支以防止连续的冲突解决。当您将她的分支从主分支创建后,将所有提交都压缩为一个提交,然后冲突可以在一步中解决。

如果您想从头开始编写新的提交消息,则只需执行以下操作即可:

git reset --soft HEAD~3 &&
git commit

在这个例子中,我们将压缩最后三次提交。
为了避免这个问题,在每次提交后,我建议您使用源分支重新基于您的分支。

0

现在我只在简单的使用情况下使用rebase。cherry-pick几乎总是更容易使用,并且需要更简单的思维模型。 cherry-pick最复杂的事情有时是您需要创建一个新的干净分支并将cherry-pick提交应用于其中。


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