-
1. 起步
-
2. Git 基础
-
3. Git 分支
-
4. 服务器上的 Git
- 4.1 协议
- 4.2 在服务器上部署 Git
- 4.3 生成 SSH 公钥
- 4.4 架设服务器
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管服务
- 4.10 小结
-
5. 分布式 Git
-
A1. 附录 A: Git 在其他环境
- A1.1 图形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小结
-
A2. 附录 B: 在应用程序中嵌入 Git
-
A3. 附录 C: Git 命令
7.9 Git 工具 - Rerere
Rerere
git rerere 功能有点像一个隐藏的功能。它的名字是“reuse recorded resolution”(重用已记录的解决方案)的缩写,顾名思义,它允许你要求 Git 记住你如何解决了一个代码块(hunk)的冲突,以便下次 Git 遇到相同的冲突时,可以自动为你解决它。
在很多情况下,这个功能可能非常有用。文档中提到的一个例子是,当你想要确保一个长期存在的特性分支最终能够干净地合并,但又不希望有大量的中间合并提交弄乱你的提交历史时。启用 rerere 后,你可以尝试偶尔合并,解决冲突,然后撤销合并。如果你持续这样做,那么最终的合并应该会很容易,因为 rerere 可以自动为你完成所有操作。
如果你想保持一个分支的 rebase(变基),这样每次进行 rebase 时就不必处理相同的 rebase 冲突,也可以使用相同的策略。或者,如果你想将一个已经合并并解决了很多冲突的分支进行 rebase — 你可能不需要再次处理所有相同的冲突。
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 for FILE。除此之外,它应该看起来和普通的合并冲突完全一样。此时,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 还会告诉你它已经记录了哪个文件的预合并状态,使用 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',然后再次运行 git rerere diff 来查看 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.rb 文件中遇到一个代码块冲突,其中一边是“hello mundo”,另一边是“hola world”,它将将其解决为“hola mundo”。
现在我们可以将其标记为已解决并提交
$ git add hello.rb
$ git commit
Recorded resolution for 'hello.rb'.
[master 68e16e5] Merge branch 'i18n'
你可以看到它“Recorded resolution for FILE”(已记录 FILE 的解决方案)。
现在,让我们撤销那个合并,然后将其 rebase 到我们的 master 分支之上。我们可以使用 git reset 来回退分支,正如我们在 Reset 揭秘 中看到的。
$ git reset --hard HEAD^
HEAD is now at ad63f15 i18n the hello
我们的合并已经撤销。现在让我们 rebase 特性分支。
$ 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
现在,我们遇到了预期的相同的合并冲突,但请注意 Resolved FILE using previous resolution(使用之前的解决方案已解决 FILE)这一行。如果我们查看文件,会发现它已经被解决了,里面没有合并冲突标记。
#! /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 来重新创建冲突的文件状态
$ 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
我们已使用 rerere 缓存的解决方案自动重新解决了该文件。现在你可以将其添加到暂存区并继续 rebase 以完成它。
$ git add hello.rb
$ git rebase --continue
Applying: i18n one word
因此,如果你进行大量的重新合并,或者想在不产生大量合并提交的情况下让特性分支保持与 master 分支同步,或者你经常进行 rebase,你可以启用 rerere 来让你的工作更轻松一些。