- 引言
- 本书涉及的内容
- 第 1 部分 Python 开发入门
- 第 1 章 Python 入门
- 第 2 章 开发 Web 应用
- 第 3 章 Python 项目的结构与包的创建
- 第 4 章 面向团队开发的工具
- 第 5 章 项目管理与审查
- 第 6 章 用 Mercurial 管理源码
- 第 7 章 完备文档的基础
- 第 8 章 模块分割设计与单元测试
- 第 9 章 Python 封装及其运用
- 第 10 章 用 Jenkins 持续集成
- 第 11 章 环境搭建与部署的自动化
- 第 12 章 应用的性能改善
- 第 13 章 让测试为我们服务
- 第 14 章 轻松使用 Django
- 第 15 章 方便好用的 Python 模块
- 附录 A VirtualBox 的设置
- 附录 B OS(Ubuntu)的设置
6.4 关于合并
利用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论