Git rerere:记录并复用冲突解决方案

git rerere 的名字来自 “reuse recorded resolution”,也就是重用已记录的解决方案。它让 Git 记住某段冲突是如何解决的;下一次出现相同文本冲突时,可以复用该结果。

它适合几种场景。长期维护的主题分支需要反复确认能否干净地合并,但又不希望留下大量中间合并提交。启用 rerere 后,可以偶尔试着合并、解决冲突,再退出这次合并;以后遇到已记录的相同冲突时,可以减少重复操作。反复变基时也有相同好处。若先合并并解决了冲突,随后决定改为变基,也不必重新处理每处已记录的文本冲突。

另一个场景是把多个开发中的主题分支临时合并到可测试的提交上,就像 Git 项目自身经常做的。如果测试失败,可以退回合并前、排除造成失败的主题分支,然后重新合并,而不用重复解决所有相同冲突。

启用 rerere

原例使用全局配置:

$ git config --global rerere.enabled true

也可以在特定仓库中创建 .git/rr-cache 目录来开启,但配置选项更清楚。全局配置会影响其他仓库。

记录第一次冲突

假设 hello.rb 最初如下:

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

在一条分支里把 “hello” 改为 “hola”,在另一条里把 “world” 改为 “mundo”。

两条分支分别修改同一行中的两个词

合并时,两处修改发生在同一行,于是出现冲突:

$ git merge i18n-world
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Recorded preimage for 'hello.rb'
Automatic merge failed; fix conflicts and then commit the result.

除了通常的合并失败信息,还多出 Recorded preimage:rerere 已记录解决前的冲突形态。此时仍可照常用 git status 查看未合并文件:

$ git status
# On branch master
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add <file>..." to mark resolution)
#
#	both modified:      hello.rb
#

git rerere status 会列出已记录冲突状态的文件:

$ git rerere status
hello.rb

git rerere diff 显示已记录的冲突原貌与当前文件之间的差异:

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,11 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
+<<<<<<< HEAD
   puts 'hola world'
->>>>>>>
+=======
+  puts 'hello mundo'
+>>>>>>> i18n-world
 end

与 rerere 本身无关,git ls-files -u 还可以列出未合并文件的共同祖先和两侧版本:

$ git ls-files -u
100644 39804c942a9c1f2c03dc7c5ebcd7f3e3a6b97519 1	hello.rb
100644 a440db6e8d1fd76ad438a49025a9ad9ce746f581 2	hello.rb
100644 54336ba847c3758ab604876419607e9443848474 3	hello.rb

现在把该行改为 puts 'hola mundo' 来解决冲突,再查看 rerere 将记住的变化:

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,7 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
-  puts 'hola world'
->>>>>>>
+  puts 'hola mundo'
 end

Git 记录的对应关系是:一侧为 “hello mundo”、另一侧为 “hola world” 的这段文本冲突,最终被解决为 “hola mundo”。

把文件标记为已解决并提交:

$ git add hello.rb
$ git commit
Recorded resolution for 'hello.rb'.
[master 68e16e5] Merge branch 'i18n'

提交输出中的 Recorded resolution 表示解决结果也已记录。

首次合并已解决并提交

在变基中复用结果

下面撤销这次合并,再改为把主题分支变基到 master 上。原例使用 重置:

$ git reset --hard HEAD^
HEAD is now at ad63f15 i18n the hello

--hard 会改动工作区与索引,这只是原书演示步骤。练习前应保存已有工作,并确认要重置的提交。

合并撤销后,切换到主题分支,再执行变基:

$ git checkout i18n-world
Switched to branch 'i18n-world'

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: i18n one word
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Failed to merge in the changes.
Patch failed at 0001 i18n one word

原例再次遇到同样的文本冲突,却出现 using previous resolution,表示已应用记录的结果。打开文件,可以看到没有冲突标记:

#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

git diff 显示自动复用如何把两侧内容统一为结果:

$ git diff
diff --cc hello.rb
index a440db6,54336ba..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
  #! /usr/bin/env ruby

  def hello
-   puts 'hola world'
 -  puts 'hello mundo'
++  puts 'hola mundo'
  end

变基复用了先前记录的文本解决结果

也可以恢复冲突标记,以观察处理过程。相关示例见高级合并:

$ git checkout --conflict=merge hello.rb
$ cat hello.rb
#! /usr/bin/env ruby

def hello
<<<<<<< ours
  puts 'hola world'
=======
  puts 'hello mundo'
>>>>>>> theirs
end

此时再次运行 git rerere,就会根据缓存重新应用解决结果:

$ git rerere
Resolved 'hello.rb' using previous resolution.
$ cat hello.rb
#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

文件冲突已按记录重新解决,随后暂存文件并继续变基:

$ git add hello.rb
$ git rebase --continue
Applying: i18n one word

如果经常重新合并,想让主题分支持续跟上 master 又不留下大量合并提交,或经常变基,rerere 可以减少重复解决相同文本冲突的工作。缓存匹配和应用成功不代表业务逻辑正确,仍需检查差异、运行项目测试并确认暂存内容。

作者:Scott Chacon、Ben Straub;中文原文由 Pro Git 中文社区翻译。来源:Pro Git 第二版 7.9 Rerere。本稿依据官方中文版本完整整理正文、保留代码和三幅示意图,并加入标明的使用提示。依照全书声明采用 CC BY-NC-SA 3.0:署名、非商业、相同方式共享。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容