GIT - Rebase - 如何处理冲突

GIT - Rebase - How to deal with conflicts

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

feature 分支是不久前创建的,有很多次提交。

自从 feature 分支创建以来,已经有几次提交到 master

此时,当我从 master 转到 rebase 时,我遇到了冲突。我解决它们,然后 rebase --continue。然后我再次遇到冲突,再次解决并 rebase --continue。这种情况一遍又一遍地发生,而且很多时候它们似乎是正在出现的相同冲突。

在我看来,这是正在发生的事情:

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

feature 是从 master->a 分支出来的,然后创建了所有提交。

当我 rebase 时,它倒回到 feature 的开头,它从 master 开始应用 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 如何工作/处理 commits/conflicts/rebasing 的理解是否正确?

2) 我怎样才能避免一遍又一遍地解决相同的冲突?我正在考虑压缩功能分支上的所有提交,以便只有一个要处理。我不确定这是一个好主意还是在场景中压缩的最佳方法。

请注意,这是一个非常简化的示例。实际上,我有更多的提交,并且在每个文件中都有许多冲突。其中一些在整个 rebase --continue 过程中似乎是相同的,有些在每个 commit.

中都是新的

我的最终目标是尽可能简单地清理这个项目(让功能分支重新基于当前的主分支)。我不关心提交的历史。

你的心理形象很接近,但让我们更准确一点:

...--o--*--A--B          <-- master
         \
          C--D--...--Z   <-- feature

这就是您现在所拥有的:名称 master 指向 tip commit B(每个字母或 o* 表示提交)。每个提交都指向(左)到其先前的提交。 AB 两个提交仅在 master 上进行。一堆提交仅在 feature 上,即 CZ。提交 * 和所有 较早的 (更左)提交都在 两个 分支上。

git rebase 所做的是通过从提交 Zmaster 的尖端向后查找提交 *。然后它知道它需要 copy 提交 C-Z。副本将在 master 之后立即开始。如果我们用C'来命名C的副本,D'来命名D的副本,依此类推,最后的graph 将如下所示:

                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 并看到它应该根据那个改变 <div id="foo"><div id="bar">;但它应该根据 *-vs-C 更改 <div id="foo"> <div id="fubar">。这是第一个合并冲突。

所以,你解决这个问题并提交(嗯,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-ing 提交 D,这同样会将其与 C 进行比较,从而产生差异。

但是,您可能会被白色-space 变化或类似变化所困扰。值得仔细查看每个 git diff 输出,如果您正在使用这些(CRLF 转换),则检查诸如行尾属性之类的东西。行尾问题通常会影响每个文件中的每一行,但是您可能会遇到单行问题的情况,例如,这取决于谁使用什么编辑器。

我怀疑更可能的问题是:git diff 正在同步 错误的行 。它会找到一些匹配的琐碎事物,例如读取 } 的行,并使用这些来确定两个文件重新同步,然后挑选出 "wrong changes"。如果是这种情况,解决方法(如果有的话)就是指示 Git 使用更智能的差异。特别是,patience diff 尝试不在琐碎的行上同步,而是仅在重要的(即非重复出现的)行上同步。这实际上可能有帮助,也可能没有帮助——运行 git diff --diff-algorithm=patience(或具有相同参数的 git show)可能会告诉您。是否可以让 rebase 来使用不同的 diff 算法取决于你的 Git 版本。

除了 Git 似乎重复变化的原因之外,当它应该只有 other 变化时 C-vs-D,你 可以 做的一件事是使用 git rerere 将 Git 变为 -使用 recorded re 解决方案(因此得名)。基本上,当 Git 遇到合并冲突时,如果启用 rerere,Git 会将冲突部分写入其数据库,然后当您 git add 解析文件时,Git写入解析(与原来的冲突配对)。然后,下一次 Git 遇到合并冲突时,它会检查保存的 rerere 数据:如果冲突是针对同一个补丁,它会提取记录的解决方案,并在不与您交互的情况下使用它。

您确实可以通过在提交 * 上重新设置 feature(使用交互式重新设置)来将部分或所有功能提交压缩在一起。由于这些补丁不会相互冲突——至少只要它们被回放 "in order"——这可以让您减少需要解决的提交数量。你是否 应该 这样做取决于你:一般来说,如果你正在变基,你无论如何都会得到新的替换提交,所以你不妨为 "as pretty as possible" 创建新的未来的调试或其他代码探索。显然,有 5 个 "does something interesting, but pretty much stand-alone" 提交而不是 50 个 "do fragment of one thing, do another fragment, fix previous fragment, do another fragment, OK that thing is done move to next thing, oops fix tiny error, ..." 会更好,所以这些是非常好的挤压在一起的候选者。但是,通常有五个 "does one thing" 提交比一个 "does five things" 提交更好:这意味着如果五个中的一个有错误,您可以修复 that 不用担心其他四个。

I do not care about the history of the commits.

如果您真的不关心历史记录,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))来压缩修复类型的提交,而不完全消除所有离散的提交。

当您执行 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 将 return 您恢复到调用 git 变基之前的分支状态。
  • 您可以 运行 git rebase --skip 完全跳过提交。这意味着将包含 none 有问题的提交引入的更改。您很少会选择此选项。
  • 您可以解决冲突。

要解决冲突,您可以关注the standard procedures for resolving merge conflicts from the command line。完成后,您需要调用 git rebase --continue 以便 Git 继续处理 rebase 的其余部分。

我有一个类似的问题,结果我解决了与 --ours 的冲突,而我本应该使用 --theirs.

原因是--ours你的变化merge,但在rebase中是--theirs因为 git 有效地删除了您的提交并重播它们的副本。

因此,当您使用 rebase 解决与您的版本的冲突时:

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

但是当使用 merge 解决与您的版本的冲突时:

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

当你

git pull --rebase origin/upstream master

它检查最近是否有其他人合并的代码。如果是这样,您将必须使当前的工作树与上游的工作树保持一致。

您可以通过

看到您必须重新查看的已更改文件
git status

解决它们后,

git add 

git commit 然后 git push -f origin <branch-name>会让你开心。