多人开发中的合并冲突是我们使用Git时常常会遇到的情况,小小合并门道大,讲述合并的那些事儿,晴耕 · 白话之“Git合并那些事”系列持续连载中……
注: 本文的部分写作灵感来自于“Pro Git book”。感谢原作者的精彩分享。 本文采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
撤销合并
当冲突发生时,我们有多种选择,这其中当然也包括撤销合并。比如,我们可以利用git merge --abort
,恢复或回退到执行合并以前的状态。或者,如果我们合并后的提交还停留在本地Git库,没有被推送到远程,我们还可以利用git reset --hard HEAD
命令,恢复到当前分支的最近一次提交。Git在接到这个命令以后,会按照下面的步骤一步步进行撤销:
- 把当前分支的HEAD指针移动到合并前的提交记录上;
- 把暂存区恢复成HEAD所指向的版本;
- 把工作目录恢复成和暂存区保持一致;
使用git revert
另外,还有一种撤销合并的办法,就是利用git revert
命令。和git merge --abort
,以及git reset
不同,它是在合并提交生成以后使用的。这种方法的好处是,它可以在恢复以前某个提交记录的同时,还能保留从那以后的所有提交历史。其实现思路是,在合并提交的后面再创建一个新的提交记录,在这个提交记录里撤销掉从某个前序提交记录开始往后的所有更改。
我们来看一个例子,接着“Git合并那些事儿——当冲突发生时”我们所使用的实验环境,假设我们手工解决了合并冲突,并建立了一个新的合并提交M:
$ cat README
When conflict happens...
* Prepare
* Cancel the merge
* Understand the conflict
* ...
$ git commit -am M
[master 01a84b6] M
这个时候的提交历史是这样的:
$ git log --oneline --all --graph
* 01a84b6 (HEAD -> master) M
|\
| * 225fb79 (dev) c3
| * f6cd947 c2
* | fc3fedf c1
|/
* 12de8e7 c0
现在,我们执行git revert
,把README的内容恢复到合并之前的ours版本,其实就是提交记录c1对应的版本:
$ git revert -m 1 HEAD
[master 931c6f9] Revert "M"
2 files changed, 2 deletions(-)
delete mode 100644 .gitignore
这里,-m 1
表示当前分支应该保留哪个版本(1代表ours)。
这个时候的提交记录历史变成了这个样子:
$ git log --oneline --all --graph
* 931c6f9 (HEAD -> master) Revert "M"
* 01a84b6 M
|\
| * 225fb79 (dev) c3
| * f6cd947 c2
* | fc3fedf c1
|/
* 12de8e7 c0
观察README的内容我们会发现,就好像合并从来没有发生过一样。但是合并的过程的确被记录在提交历史里了。因为Git在提交历史里保留了合并提交,所以当我们尝试再次合并时,Git会决绝:
$ git merge dev
Already up-to-date.
git revert的问题
有趣的是,如果这个时候我们又在dev分支上做了新的改动,当再次进行合并时,我们会发现Git只会合并提交记录Revert “M”之后引入的变更。
接下来,我们通过实验来进一步加深理解。首先我们切换到dev分支:
$ git checkout dev
Switched to branch 'dev'
在上面做些改动:
$ vi VERSION
$ cat VERSION
1.0
然后生成一个新的提交记录c4:
$ git add VERSION
$ git commit -m c4
[dev 060ab8a] c4
1 file changed, 1 insertion(+)
create mode 100644 VERSION
这个时候,再切换回master分支:
$ git checkout master
Switched to branch 'master'
合并dev分支上的修改:
$ git merge -m c5 dev
Merge made by the 'recursive' strategy.
VERSION | 1 +
1 file changed, 1 insertion(+)
create mode 100644 VERSION
这个时候,我们会发现在dev分支上新加的VERSION文件的确被成功合并了。但是,README文件依然保持着原来的样子,也就是提交记录c1对应的版本:
$ ls
README VERSION
$ cat README
When conflict happens...
* Cancel the merge
* Understand the conflict
这个时候的提交历史是这样的:
$ git log --oneline --all --graph
* 8243ea6 (HEAD -> master) c5
|\
| * 060ab8a (dev) c4
* | 931c6f9 Revert "M"
* | 01a84b6 M
|\ \
| |/
| * 225fb79 c3
| * f6cd947 c2
* | fc3fedf c1
|/
* 12de8e7 c0
撤销上一次撤销
如果我们希望再次从dev分支合并README的内容,可以再次执行git revert
命令,让它恢复到合并提交所对应的版本,也就是把Revert "M"
对应的提交记录撤销掉。
首先,让我们回退到合并以前的状态:
$ git reset --hard HEAD^
HEAD is now at 931c6f9 Revert "M"
然后,执行git revert
命令,撤销掉提交记录Revert “M”:
$ git revert 931c6f9
[master 929008c] Revert "Revert "M""
2 files changed, 2 insertions(+)
create mode 100644 .gitignore
再次查看提交历史:
$ git log --oneline --all --graph
* 929008c (HEAD -> master) Revert "Revert "M""
* 931c6f9 Revert "M"
* 01a84b6 M
|\
* | fc3fedf c1
| | * 060ab8a (dev) c4
| |/
| * 225fb79 c3
| * f6cd947 c2
|/
* 12de8e7 c0
如图所示,虽然我们在Revert “M”后面多了一个新的提交记录Revert “Revert “M”“:
但是查看README的文件内容会发现,它已经恢复到合并提交M的状态。也就是说,已经包含了上一次合并时来自dev分支的修改了:
$ cat README
When conflict happens...
* Prepare
* Cancel the merge
* Understand the conflict
* ...
在这个基础上,我们再进行新的合并,把dev分支上的新变更合并到master分支:
$ git merge -m c5 dev
Merge made by the 'recursive' strategy.
VERSION | 1 +
1 file changed, 1 insertion(+)
create mode 100644 VERSION
最后来看一下提交历史:
$ git log --oneline --all --graph
* 7084e12 (HEAD -> master) c5
|\
| * 060ab8a (dev) c4
* | 929008c Revert "Revert "M""
* | 931c6f9 Revert "M"
* | 01a84b6 M
|\ \
| |/
| * 225fb79 c3
| * f6cd947 c2
* | fc3fedf c1
|/
* 12de8e7 c0
留下评论
您的电子邮箱地址并不会被展示。请填写标记为必须的字段。 *