返回介绍

6.4 关于合并

发布于 2024-01-21 17:11:03 字数 10290 浏览 0 评论 0 收藏 0

利用 Mercurial 等版本控制系统进行多项工作时,必然离不开成果的合并。Mercurial 的 hg merge 命令能自动通过三路合并为我们完成合并工作,但并不是说所有情况下都能正常合并。本节,我们将学习基本的合并流程以及发生冲突时的应对方法。熟练掌握合并的相关技巧,能让各位对版本控制系统更加得心应手。

6.4.1 未发生冲突的合并

多名成员并行开发时会发生多头现象。下面例子中就存在 rev1 和 rev2 两个头。要解决这一问题就必须进行合并。

$ hg log -G --style compact
o  2[tip]:0   c0d244a079ce   2011-11-18 11:40 +0900   tokibito
|  tokibito
|
| @  1   ead8ad7a4d9f   2011-11-16 11:07 +0900   monjudoh
|/   monjudoh
|
o  0   fe75405e6383   2011-11-16 11:05 +0900   monjudoh
   first commit

假设现在我们位于 rev1,进行合并要先运行 merge 命令再进行提交。在同一分支内合并多头时,merge 命令不需要加任何传值参数。

$ hg merge
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ hg ci -m "merge"

这样一来多头就被合并成了一个头。

$ hg log -G --style compact
@  3[tip]:1,2   b5f62f57038b   2011-11-18 14:07 +0900   monjudoh
|\   merge
| |
| o  2:0   c0d244a079ce   2011-11-18 11:40 +0900   tokibito
| |  tokibito
| |
o |  1   ead8ad7a4d9f   2011-11-16 11:07 +0900   monjudoh
|/   monjudoh
|
o  0   fe75405e6383   2011-11-16 11:05 +0900   monjudoh
   first commit

工作中我们也经常会遇到合并两个分支的情况。下面的例子是包含 default 和 tokibito2 两个分支的状态。

2 okibito 为本章撰写者冈野真也先生的 Twitter 用户名。——编者注

$ hg log -G o changeset: 2:a663e1bf8f71 | branch: tokibito | tag: tip | parent: 0:fe75405e6383 | user: tokibito | date: Fri Nov 18 11:40:13 2011 +0900 | summary: tokibito | | @ changeset: 1:ead8ad7a4d9f |/ user: monjudoh <monjudoh@gmail.com> | date: Wed Nov 16 11:07:59 2011 +0900 | summary: monjudoh | o changeset: 0:fe75405e6383 user: monjudoh <monjudoh@gmail.com> date: Wed Nov 16 11:05:34 2011 +0900 summary: first commit

此时就不能用无传值参数的 merge 命令了。因为分支内不存在多头,无法自动选择合并对象。

$ hg merge
abort: branch 'default' has one head - please merge with an explicit rev
(run 'hg heads' to see all heads)

假设我们位于 default 分支的 rev1,现在只要将合并对象的分支名交给 merge 命令就能完成合并。

$ hg merge tokibito
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ hg ci -m "merge"

执行后的合并情况如下。我们是在 default 分支下指定 tokibito 分支执行了 merge 命令,所以被合并的那个修订版现在属于 default 分支。这里千万注意别搞错合并方向。

$ hg log -G
@  changeset:   3:7dffb01df37b
|\   tag: tip
| |  parent:    1:ead8ad7a4d9f
| |  parent:    2:a663e1bf8f71
| |  user:    monjudoh <monjudoh@gmail.com>
| |  date:    Fri Nov 18 14:17:13 2011 +0900
| |  summary:   merge
| |
| o  changeset:   2:a663e1bf8f71
| |  branch:    tokibito
| |  parent:    0:fe75405e6383
| |  user:    tokibito
| |  date:    Fri Nov 18 11:40:13 2011 +0900
| |  summary:   tokibito
| |
o |  changeset:   1:ead8ad7a4d9f
|/   user:    monjudoh <monjudoh@gmail.com>
|  date:    Wed Nov 16 11:07:59 2011 +0900
|  ummary:    monjudoh
|
o  changeset:   0:fe75405e6383
   user:    monjudoh <monjudoh@gmail.com>
   date:    Wed Nov 16 11:05:34 2011 +0900
   summary:   first commit

6.4.2 合并时发生冲突以及用文本编辑器解决冲突的方法

虽然大部分情况下合并都能自动完成,然而一旦发生冲突,就需要我们来手动解决问题了。

假设有如下多头情况。

$ hg log -G --style compact
o  2[tip]:0   0dffbb8f7780   2011-11-18 15:09 +0900   monjudoh
|  other
|
| @  1   a239fe812ab0   2011-11-18 15:08 +0900   monjudoh
|/   this
|
o  0   0648c3b5afbd   2011-11-18 15:07 +0900   monjudoh
   base

rev0、1、2(base、this、other)中 conflict.txt 的文件内容分别如下所示。

· rev0(base)

A A A A A

· rev1(this)第 3 行变更为 B

A A B A A

· rev2(other)第 3 行变更为 C

A A C A A

然后我们在 rev1(this)执行 merge 命令。

LIST 6.16 执行合并查看冲突

$ hg merge
merging conflict.txt
merge: warning: conflicts during merge
merging conflict.txt failed!
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon

不出意外应该会看到如 LIST 6.16 所示的消息。这就是发生了冲突的状态,我们来解决它。

首先执行一个查看冲突状态的命令。

我们用 -l 选项执行 hg resolve 命令,发现 conflict.txt 仍处于未解决状态(LIST 6.17)。

LIST 6.17 hg resolve -l(查看冲突)

$ hg resolve -l
U conflict.txt

U 表示“未解决”(Unresolved)。打开文件可以查看冲突的位置,具体请参考“查看冲突位置”部分的内容。这里与其他版本控制系统一样,可以手动进行修正。 **

◉ 查看冲突位置

我们打开合并时发生冲突的 conflict.txt 文件,会发现内容变成了下面的样子。其实简单说来,所谓发生冲突,就是两个头对同一个文件的同一行进行了变更,计算机无法从机器逻辑层面上决定采用哪一方。要解决这个问题,就必须由人类明确表示出采用哪个变更。

A
A
<<<<<<< local
B
=======
C
>>>>>>> other
A
A

于是我们用文本编辑器打开 conflict.txt,手动编辑出合并后的 conflict.txt 文件。这里我们采用 rev1(this)的变更。

A A B A A

Mercurial 当然不知道人类是否解决了冲突,所以现在执行 hg resolve -l 仍然会出现之前的结果。此时我们要用 hg resolve -m 将当前状态从“冲突未解决”设置为“冲突解决完毕”(LIST 6.18)。然后只要提交即可。

LIST 6.18 hg resolve -m(解决冲突)

$ hg resolve -m conflict.txt
$ hg resolve -l
R conflict.txt
$ hg ci -m "merge"
$ hg log -G --style compact
@  3[tip]:1,2   c72367726805   2011-11-18 16:08 +0900   monjudoh
|\   merge
| |
| o  2:0   0dffbb8f7780   2011-11-18 15:09 +0900   monjudoh
| |  other
| |
o |  1   a239fe812ab0   2011-11-18 15:08 +0900   monjudoh
|/   this
|
o  0   0648c3b5afbd   2011-11-18 15:07 +0900   monjudoh
   base

6.4.3 合并的类型与冲突

上面我们提到冲突是“计算机无法从机器逻辑层面上决定采用哪一方”的状态,这里将对此作进一步的说明。

这里依然和前面一样,假设出现了如下多头现象。

$ hg log -G --style compact
o  2[tip]:0   0dffbb8f7780   2011-11-18 15:09 +0900   monjudoh
|  other
|
| @  1   a239fe812ab0   2011-11-18 15:08 +0900   monjudoh
|/   this
|
o  0   0648c3b5afbd   2011-11-18 15:07 +0900   monjudoh
   base

rev0(base)的 conflict.txt 文件的内容如下。

A A A A A

我们将 rev1、2(this、other)对第 3 行的修改情况以及合并后的结果总结成了下表(合并类型)。

合并类型

类型

this

other

结果

A

A

A(无变更)

B

A

B(采用 this 的变更)

A

C

C(采用 other 的变更)

B

B

B(碰巧合并成功)

B

C

冲突

①~④都是合并成功。①的双方都没有作变更,所以合并结果中也没有出现变更。

②和③都只有一方作了变更,所以只采用变更的一方。

④比较特殊,虽然双方都对同一行作了变更,但变更内容是相同的,所以可以直接采用。具体④的情况下能不能成功合并还要看版本控制系统的种类,虽然 Mercurial 认为是成功,但有一部分版本控制系统会判定为冲突。

⑤是双方对同一行作了不同变更,于是出现了冲突。

6.4.4 用 GUI 的合并工具进行合并

除命令行之外,Mercurial 还支持用 GUI 的合并工具进行合并。

◉ KDiff3

这里以 KDiff33 为例介绍 GUI 的合并工具。KDiff3 支持 OS X、Windows、Linux,可以进行三路合并,是 GPLv2 的 OSS。

3 http://kdiff3.sourceforge.net/

◉ 安装 KDiff3

任何环境下都能轻松安装 KDiff3。

在 OS X 上安装

下载 dmg 并解压,将解压出来的 kdiff3.app 放到应用程序文件夹中。OS X10.9 无法直接使用放在文件夹中的 kdiff3.app。初次双击启动 kdiff3.app 时,系统会弹出对话框通知无法启动(图 6.2)。

图 6.2 通知 kdiff3.app 无法启动的对话框

于是我们需要以下流程。

初次启动时不要双击,要点右键通过上下文菜单打开(图 6.3)。

图 6.3 通过上下文菜单打开

此时会弹出确认启动的对话框,点击“打开”便能启动 kdiff3.app(图 6.4)。只要第一次成功启动,以后我们就可以通过双击打开它了。

图 6.4 确认启动的对话框

在 Windows 上安装

下载安装包并运行。

在 Linux(基于 Debian)上安装

$ sudo apt-get install kdiff3

在 hgrc 中设置 merge-tools

要想关联 KDiff3,在合并发生冲突时通过它来解决问题,需要在 hgrc 或 Mercurial.ini(Windows 的情况下)中作以下设置。

[merge-patterns]
**.* = kdiff3
[merge-tools]
# Override stock tool location
# MacOSX
kdiff3.executable = /Applications/kdiff3.app/Contents/MacOS/kdiff3
# Windows
# kdiff3.regkey=Software\KDiff3
# kdiff3.regappend=\kdiff3.exe
# kdiff3.fixeol=True
# kdiff3.gui=True
# Linux
# kdiff3.executable = ~/bin/kdiff3
# Specify command line
kdiff3.args = $base $local $other -o $output
# Windows
# kdiff3.args=--auto --L1 base --L2 local --L3 other $base $local $other -o $output
# Give higher priority
kdiff3.priority = 1

在 merge-tools 节的 kdiff3 中设置 KDiff3 的命令以及命令行对象,然后在 merge-patterns 节中设置所有文件冲突都使用 kdiff3 解决。

用 KDiff3 解决冲突

我们还以 6.4.2 节描述的状态为例,给版本库执行 hg merge 命令。随后 conflict.txt 发生冲突,KDiff3 自动启动(图 6.5)。

图 6.5 因发生冲突而启动 KDiff3 之后的状态

左、中、右的 A、B、C 中分别是多头共同的祖先版本、执行 merge 命令时的 parent 修订版、执行 merge 命令时的另一个最新修订版中的 conflict.txt 内容,下方的视图显示了合并完成后的结果。我们可以看到发生冲突的地方显示为红色。前面也说了,解决冲突就是要在发生冲突的位置明确选择采用(或者不采用)某一方的变更。

在 KDiff3 中,右键点击发生冲突的位置,就会显示菜单供我们选择变更(图 6.6)。

图 6.6 KDiff3 解决冲突时选择变更的菜单

这里我们选 B(图 6.7)。

图 6.7 KDiff3 选择采用 B 时的状态

现在的状态是冲突已解决,界面中也明确显示出了合并对象的行采用了哪一个结果。随后保存文件并退出即可。

$ hg merge
merging conflict.txt
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

关闭 KDiff3 的同时 merge 命令也就执行完了。与未设置合并工具的合并不同,这里没有出现 Unresolved 的文件。最后只要提交一下,这次合并工作就完工了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文