GIT - Rebase - 如何处理冲突

15

我正在处理一个有两个分支的项目:masterfeature

feature分支是一段时间前创建的,并且有许多提交记录。

自从创建feature分支以来,master分支已经进行了几次提交。

目前,当我尝试从master上进行rebase操作时,会出现冲突。 我解决这些冲突,然后用rebase --continue继续。 然后又再次出现冲突,我再次解决并使用rebase --continue继续。 这种情况反复发生,而且很多时候似乎出现了相同的冲突。

在我的想法中,以下是发生的事情:

master(commits)->a->b feature(commits)->c->d->e->f->g

featuremaster->a中分支出,并创建了所有提交记录。

当我进行rebase操作时,它会回退到featuremaster分支中分出的起点,应用了master->b,然后开始应用feature->c,此时出现冲突。 我解决冲突(接受master的更改)并继续。 现在它尝试应用feature->d,并发现同样的冲突。 再次需要我解决并使用continue命令继续。这种情况反复发生。

例如以下是更改记录:

master->a
    <div id="foo">

master->b
    <div id="bar">

feature->c
    <div id="fubar">

feature->d
    //Nothing has changed, inherited from feature->c
    <div id="fubar">

我假设当它到达feature->c时,会说将foo更改为fubar,然后它注意到foo已经更改为bar。 我解决bar,然后使用feature->d应用相同的逻辑。

我的两个问题:

1)我的理解有关Git如何处理提交/冲突/变基是否正确?

2)我该如何避免一次又一次地解决相同的冲突? 我想把特性分支上的所有提交压缩成一个提交,这样只需要处理一个提交。 我不确定这是否是一个好主意或在这种情况下压缩的最佳方法。

请注意,这只是一个非常简化的例子。 实际上,我有很多提交,每个提交中有许多文件和许多冲突。 其中一些看起来在rebase --continue过程中是相同的,而其他一些则是每个commit都是新的。

我的最终目标是尽可能简单地清理此项目(使特性分支重新基于当前主分支)。 我不关心提交历史记录。

4个回答

20
您的脑海中已经有了一个大概的想法,现在我们来让它更加准确:
...--o--*--A--B          <-- master
         \
          C--D--...--Z   <-- feature

这是您现在拥有的内容:名称master指向tip commitB(每个单字母或o*代表一个提交)。每个提交都会回溯(向左),指向其前一个提交。两个提交AB仅位于master上。一堆提交仅位于feature上,即这里的CZ。提交*和所有早期(更靠左)的提交都位于两个分支上。 git rebase的作用是通过从提交Zmaster的末尾开始向后工作来定位提交*。然后它知道需要复制提交C-Z。这些副本将从master的右侧开始。如果我们使用C'来命名C的副本,使用D'来命名D的副本,以此类推,则最终的图形如下所示:
                C'-D'-...-Z'   <-- feature
               /
...--o--*--A--B                <-- master
         \
          C--D--...--Z         [abandoned]

我想您已经意识到,每个副本都是一次提交,通过将每个提交(完整的快照)转换为一组更改来完成。为了获得C中发生的更改,Git执行以下操作:

git diff <hash-of-*> <hash-of-C>

Git尝试将这个变更应用到快照B中,但它并不容易应用,因此Git尝试进行合并:它将*与B进行比较,并看到应该根据此将
更改为
;但是,根据*-vs-C,它应该将
更改为
。这是第一个合并冲突。
因此,您需要解决此问题并提交更改(使用git rebase --continue提交)。
                C'       <-- HEAD (rebase in progress)
               /
...--o--*--A--B          <-- master
         \
          C--D--...--Z   <-- feature

然后Git继续复制D,通过将DC进行差异比较以获取补丁。

这次,应该没有从<div id="foo"><div id="fubar">的更改,因此您在此处不应该遇到合并冲突。要确定,请尝试git show提交D,它同样将其与C进行比较,生成差异。

但是,您可能会遇到空格更改或类似的问题。值得仔细查看每个git diff输出,并检查使用这些内容(CRLF转换)的行尾属性等内容。行尾内容通常会影响每个文件中的每一行,但是您可能会遇到单行问题,具体取决于谁使用了什么编辑器。

我怀疑一个更可能的问题是:git diff在同步错误的行。它会找到一些琐碎的匹配项,例如读取}的行,并使用它们来确定两个文件已经重新同步,然后挑选出“错误的更改”。如果是这种情况,解决方法(如果有的话)就是指示Git使用更智能的diff。特别是,patience diff尝试不要在琐碎的行上同步,而是只在重要的(即非重复的)行上同步。这可能有助于或者可能没有帮助——在您的提交上运行git diff --diff-algorithm=patience(或具有相同参数的git show)可以告诉您。是否可以让rebase使用不同的diff算法取决于您的Git版本。
除了Git似乎在重复更改时应该只有CD中的其他更改,这个谜团之外,你可以做的一件事是使用git rerere来帮助Git重用记录的解决方案(因此得名)。基本上,当Git遇到合并冲突时,如果启用了rerere,Git会将冲突部分写入其数据库,然后当您git add已解决的文件时,Git会写入解决方案(与原始冲突配对)。然后,下次Git遇到合并冲突时,它会检查保存的rerere数据:如果冲突是相同的补丁,则提取记录的解决方案,并在不与您交互的情况下使用它。
您可以通过将feature变基到提交*上(使用交互式变基)来压缩一些或所有功能提交。由于这些补丁本身不会冲突——至少只要它们按顺序播放,那么就可以减少需要解决的提交数量。是否应该这样做取决于您:通常情况下,如果您正在变基,您会得到新的替换提交,因此您可能会尽可能使新提交“漂亮”,以便未来调试或其他代码探索。显然,最好拥有五个“执行某些有趣但基本独立”的提交,而不是50个“执行一部分事情,执行另一部分,修复先前的片段,执行另一部分,好了,那件事做完了,转移到下一个事情,哎呀,修复小错误,...”所以它们非常适合压缩在一起。但是,通常比起一个“执行五个事情”的提交,拥有五个“执行一个事情”的提交更好:这意味着如果其中一个有错误,您可以修复该错误而不必担心其他四个。

10

我不关心提交的历史记录。

如果你真的不关心历史记录,使用 git merge --squash 命令可以让你一次性解决所有冲突,并生成一个包含所有更改的单个提交。

如果想要在原地执行类似于 --squash 的操作,可以像这样做:

git branch -m feature feature-old
git checkout master -b feature
git merge --squash feature-old

当你解决所有冲突后(仅一次),你将在 feature 分支上创建一个单独的提交,其父级为 master

话虽如此,我很喜欢保留历史记录。首先可以尝试使用 rerere。您还可以尝试使用原地的 rebase --interactive (例如 git rebase -i $(git merge-base HEAD master))来压缩 fixup 类型的提交,而不完全消除所有离散提交。


3

我曾经遇到过类似的问题,结果发现我在解决冲突时使用了 --ours,但实际上应该使用 --theirs

原因是,在 合并(merge) 中,--ours 是指你自己的更改,但在 变基(rebase) 中,它表示 --theirs,因为 Git 实际上会移除你的提交并重新播放其副本。

因此,在使用 变基(rebase) 解决版本冲突时,请执行以下操作:

git checkout --theirs some.file git add some.file

但是,在使用 合并(merge) 解决版本冲突时,请执行以下操作:

git checkout --ours some.file git add some.file


-1

当您执行git rebase操作时,通常会移动提交。因此,可能会出现合并冲突的情况。这意味着您的两个提交修改了同一文件中的同一行,Git不知道应该应用哪个更改。

在使用git rebase重新排序和操作提交后,如果发生合并冲突,Git将通过以下消息在终端上打印告诉您:

error: could not apply fa39187... something to add to patch A

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply fa39187f3c3dfd2ab5faa38ac01cf3de7ce2e841... Change fake file

在这里,Git 告诉你哪个提交引起了冲突(fa39187)。你有三个选择:

  • 你可以运行 git rebase --abort 来完全撤销 rebase。Git 会将你的分支状态返回到调用 git rebase 之前的状态。
  • 你可以运行 git rebase --skip 来完全跳过该提交。这意味着问题提交引入的任何更改都不会被包含。很少会选择此选项。
  • 你可以解决冲突。

要解决冲突,你可以按照命令行解决合并冲突的标准程序进行操作。完成后,你需要调用 git rebase --continue 以便 Git 继续处理剩余的 rebase。


这只是来自 https://docs.github.com/en/get-started/using-git/resolving-merge-conflicts-after-a-git-rebase 的文本吗? - Heath Raftery

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