关于Git分支高级合并的一些笔记整理

时间:2021-06-14 01:25:16


写在前面


  • 分享一些 Git 高级合并的笔记
  • 博文为《Pro Git》读书笔记整理
  • 感谢开源这本书的作者和把这本书翻译为中文的大佬们
  • 理解不足小伙伴帮忙指正,书很不错,感兴趣小伙伴可以去拜读

傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波


合并冲突

对于更复杂的冲突,Git 提供了几个工具来帮助你指出将会发生什么以及如何更好地处理冲突。

在做一次可能有冲突的合并前尽可能保证工作目录是干净的。如果你有正在做的工作,要么提交到一个临时分支要么储藏它。这使你可以撤消在这里尝试做的任何事情。如果在你尝试一次合并时工作目录中有未保存的改动,下面的操作可能会丢失你的操作。

合并发生冲突

$ git merge whitespace
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.

中断一次合并

可能你不想处理合并冲突的这种情况,完全可以通过 git merge --abort 来简单地退出合并。

$ git status -sb
## master
UU hello.rb
$ git merge --abort
$ git status -sb
## master

git merge --abort 选项会尝试恢复到你运行合并前的状态。 但当运行命令前,在工作目录中有未储藏、未提交的修改时它不能完美处理,除此之外它都工作地很好。

如果出于某些原因你想要重来一次,也可以运行 git reset --hard HEAD 回到上一次提交的状态。请牢记此时任何未提交的工作都会丢失,所以请确认你不需要保留任何改动。

忽略空白

如果你看到在一次合并中有大量关于空白的问题,你可以直接中止它并重做一次,这次使用-Xignore-all-space -Xignore-space-change选项。第一个选项在比较行时完全忽略空白修改,第二个选项将一个空白符与多个连续的空白字符视作等价的。

$ git merge -Xignore-space-change whitespace
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
 hello.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

手动文件再合并

通过 git show 命令与一个特别的语法,你可以将冲突文件的这些版本释放出一份拷贝。

Git 在索引中存储了所有这些版本,在 “stages” 下每一个都有一个数字与它们关联。

  • Stage 1 是它们共同的祖先版本
  • stage 2 是你的版本
  • stage 3 来自于 MERGE_HEAD,即你将要合并入的版本(“theirs”)。
$ git show :1:hello.rb > hello.common.rb
$ git show :2:hello.rb > hello.ours.rb
$ git show :3:hello.rb > hello.theirs.rb

工作目录中已经有这所有三个阶段的内容,我们可以手工修复它们来修复空白问题,然后使用鲜为人知的 git merge-file 命令来重新合并那个文件。

$ git merge-file -p \
  hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb

要在合并前比较结果与在你的分支上的内容,换一句话说,看看合并引入了什么,可以运行 git diff --ours

$ git diff --ours
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index 36c06c8..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -2,7 +2,7 @@
 # prints out a greeting
 def hello
- puts 'hello world'
+ puts 'hello mundo'
 end
 hello()

如果我们想要查看合并的结果与他们那边有什么不同,可以运行 git diff --theirs

$ git diff --theirs -b
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index e85207e..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
 #! /usr/bin/env ruby
+# prints out a greeting
 def hello
  puts 'hello mundo'
 end

可以通过 git diff --base 来查看文件在两边是如何改动的。

$ git diff --base -b
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index ac51efd..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,8 @@
 #! /usr/bin/env ruby
+# prints out a greeting
 def hello
- puts 'hello world'
+ puts 'hello mundo'
 end
 hello()

在这时我们可以使用 git clean 命令来清理我们为手动合并而创建但不再有用的额外文件。

$ git clean -f
Removing hello.common.rb
Removing hello.ours.rb
Removing hello.theirs.rb

撤消合并

假设现在在一个主题分支上工作,不小心将其合并到 master 中,现在提交历史看起来是这样:

关于Git分支高级合并的一些笔记整理

有两种方法来解决这个问题,这取决于你想要的结果是什么。

修复引用

如果这个不想要的合并提交只存在于你的本地仓库中,最简单且最好的解决方案是移动分支到你想要它指向的地方。 大多数情况下,如果你在错误的 git merge 后运行 git reset --hard HEAD~,这会重置分支指向所以它们看起来像这样:

关于Git分支高级合并的一些笔记整理

reset --hard 通常会经历三步:

  1. 移动 HEAD 指向的分支。 在本例中,我们想要移动 master 到合并提交(C6)之前所在的位置。
  2. 使索引看起来像 HEAD。
  3. 使工作目录看起来像索引。

如果其他人已经有你将要重写的提交,你应当避免使用 reset

还原提交

如果移动分支指针并不适合你,Git 给你一个生成一个新提交的选项,提交将会撤消一个已存在提交的所有修改。 Git 称这个操作为“还原”,在这个特定的场景下,你可以像这样调用它:

$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'

-m 1 标记指出 “mainline” 需要被保留下来的父结点。 当你引入一个合并到 HEAD(git merge topic),新提交有两个父结点:

  • 第一个是 HEAD(C6)
  • 第二个是将要合并入分支的最新提交(C4)。

在本例中,我们想要撤消所有由父结点 #2(C4)合并引入的修改,同时保留从父结点 #1(C6)开始的所有内容。

关于Git分支高级合并的一些笔记整理

新的提交 ^M 与 C6 有完全一样的内容,所以从这儿开始就像合并从未发生过,除了“现在还没合并”的提交依然在 HEAD 的历史中。 如果你尝试再次合并 topic 到 master Git 会感到困惑

$ git merge topic
Already up-to-date.

topic 中并没有东西不能从 master 中追踪到达。 更糟的是,如果你在 topic 中增加工作然后再次合并,Git只会引入被还原的合并 之后 的修改。

关于Git分支高级合并的一些笔记整理

$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topi

解决这个最好的方式是撤消还原原始的合并,因为现在你想要引入被还原出去的修改,然后 创建一个新的合并提交:

关于Git分支高级合并的一些笔记整理

选择以某一版本合并

当 Git 看到两个分支合并中的冲突时,它会将合并冲突标记添加到你的代码中并标记文件为冲突状态来让你解决。 如果你希望 Git 简单地选择特定的一边并忽略另外一边而不是让你手动解决冲突,你可以传递给 merge 命令一个-Xours -Xtheirs 参数。

$ git merge mundo
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
$ git merge -Xours mundo
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
 hello.rb | 2 +-
 test.sh | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)
 create mode 100644 test.sh