在使用 rebase -ir 处理之前的不相关提交后,如何重做合并冲突解决?

How to redo merge conflict resolution after working with unrelated commits that came before it with rebase -ir?

举个简单的例子,假设我们有一个 repo,其中向文件添加了不正确的行 a

$ git init 
Initialized empty Git repository in /someplace/.git/
$ echo "a1\nb2" > a
$ echo b1 > b    
$ git add .
$ git commit -m init  
[master (root-commit) 917f3b9] init
 2 files changed, 3 insertions(+)
 create mode 100644 a
 create mode 100644 b

分支 master 然后分叉出新分支 f

$ git branch f

master中,我们再添加一行到不正确的文件。

$ echo b3 >> a
$ git commit -am 'added b3 to a' 
[master e44bc9f] added b3 to a
 1 file changed, 1 insertion(+)

在分支f中,我们做了一些无关的事情,commit它,

$ git checkout f
$ echo c1 > c 
$ git add .
$ git commit -m "added c"
  1 label onto
[f 7cb63b2] added c
 1 file changed, 1 insertion(+)
 create mode 100644 c

然后我们将错误放置在文件a中的行移动到文件b中。

$ echo b2 >> b
$ sed -i '/b2/d' a
$ git commit -am "moved b2 to where it really belongs"
[f 32cee76] moved b2 to where it really belongs
 2 files changed, 1 insertion(+), 1 deletion(-)

然后我们尝试将 master 合并到 f 但遇到了冲突,即 master 添加了另一条不正确的行,而 f 删除了第一行。

$ git merge master 
Auto-merging a
CONFLICT (content): Merge conflict in a
Automatic merge failed; fix conflicts and then commit the result.
$ cat a
a1
<<<<<<< HEAD
=======
b2
b3
>>>>>>> master

为了解决冲突,我们将第二个不正确的行移动到正确的文件中,然后提交合并。

$ echo b3 >> b
$ sed -i '/HEAD/,$d' a
$ git commit -a --no-edit 
[f 28d8543] Merge branch 'master' into f

然后,我们意识到关于 c 的提交不应该在这个分支中,所以我们 "remove" 它与 rebase -ir 并且遇到了与之前相同的冲突。

$ git rebase -ir $(git merge-base master @) # remove the commit about c
Auto-merging a
CONFLICT (content): Merge conflict in a
Could not apply 28d8543... onto # Merge branch 'master' into f
$ cat a
a1
<<<<<<< HEAD
=======
b2
b3
>>>>>>> refs/rewritten/onto

那么,考虑到添加到 b 而未出现在冲突中的内容,这里重做之前的解决方案的最简单、最有效和通用的解决方案是什么?

目前想到的最好的解决方案(实际上不是那么好)是放弃 git 并制作合并冲突解决方案的补丁:

$ cp -a . ../$(basename $PWD)~
$ echo b3 >> b
$ sed -i '/HEAD/,$d' a
$ diff -ru ../$(basename $PWD)~ .
diff -ru ../t~/a ./a
--- ../t~/a 2019-03-08 12:54:34.701197573 -0800
+++ ./a 2019-03-08 12:54:56.761197321 -0800
@@ -1,6 +1 @@
 a1
-<<<<<<< HEAD
-=======
-b2
-b3
->>>>>>> refs/rewritten/onto
diff -ru ../t~/b ./b
--- ../t~/b 2019-03-08 12:54:34.701197573 -0800
+++ ./b 2019-03-08 12:54:54.044530686 -0800
@@ -1,2 +1,3 @@
 b1
 b2
+b3

我可以将该补丁保存在某个地方,并在需要再次解决此问题时应用它。这样做的问题是管理这些补丁并有纪律来预见我何时需要重做冲突解决或始终为每个冲突解决制作补丁。

不过,我更喜欢使用 git 来管理此问题的解决方案。

有一个 git 实用程序可以记录冲突是如何解决的,以便您以后可以重播。它被称为git rerere

它实际上是针对特定用例的 - 由于该用例基于我不同意的假设,我几乎从不使用 git rerere

我还要说的是,如果工作流程良好,您描述的场景即使不存在也应该很少见,所以我个人不会开始使用 rerere 来记录每个冲突解决方案稍后我会再次需要它。

但是...因为这是您提出的问题,git rerere 可能是“答案”。

git rerere 或制作 refs/rewritten/onto 的补丁是必要的,因为在 Git 2.23 中,refs/rewritten/onto 将在特定情况下被删除 git rebase -ir 中止。
git rebase --abort”在结束“git rebase -r”时曾经离开refs/rewritten/,已更正。

参见 commit d559f50, commit 37e9ee5, commit d3fce47, commit 7372eae (14 May 2019) by Phillip Wood (phillipwood)
(由 Junio C Hamano -- gitster -- in commit 88f95e4 合并,2019 年 7 月 9 日)

rebase --abort/--quit: cleanup refs/rewritten

When rebase -r finishes it removes any refs under refs/rewritten that it has created.
However if the user aborts or quits the rebase refs are not removed. This can cause problems for future rebases.

For example I recently wanted to merge a updated version of a topic branch into an integration branch so ran rebase -ir and removed the picks and label for the topic branch from the todo list so that

merge -C <old-merge> topic

would pick up the new version of topic.
Unfortunately refs/rewritten/topic already existed from a previous rebase that had been aborted so the rebase just used the old topic, not the new one.

The logic for the non-interactive quit case is changed to ensure buf is always freed.