This text is a work in progress—highly subject to change—and may not accurately describe any released version of the Apache™ Subversion® software. Bookmarking or otherwise referring others to this page is probably not such a smart idea. Please visit http://www.svnbook.com/ for stable versions of this book.

使用外部差异比较与合并工具

Subversion 与二路以及三路差异比较工具之间的接口可以一直追溯到它的 起源时期, 那时候 Subversion 的差异比较功能直接来自 GNU 工具的调用, 尤其是 diffdiff3. Subversion 为了得到期望的效果, 在调用这些工具时会加上一些参数或选项, 其中的大部分 选项都是工具所特有的. 随着 Subversion 的不断开发, 它渐渐地具有了自己的 差异比较库函数, 但同时也为客户端工具增加了选项 --diff-cmd--diff3-cmd, 于是用户 可以告诉客户端使用 GNU 差异比较工具 diffdiff3, 而非内建的差异比较库函数. 如果使用了选项 --diff-cmd--diff3-cmd, Subversion 就会忽略内建的差异比较库函数, 转而调用外部的差异比较程序, 在调用时传递 必要的选项.

读者应该很容易想到, 既然 Subversion 可以使用 GNU 的 diffdiff3, 那当然也可以使用 其他的差异比较工具, 毕竟 Subversion 并不知道它所调用的工具到底是不是 GNU 的差异比较工具. 不过, 在使用这些外部工具时, 唯一可配置的地方就是它 们在系统中的路径—而非选项或参数的顺序等. Subversion 仍然会像往常 一样把 GNU 工具的选项传递给你所指定的外部差异比较工具, 无论它们理不理解 这些选项, 大多数用户的困惑即来自于此.

[注意] 注意

什么时候决定调用差异比较工具由 Subversion 内部进行决定, 而且还 受到文件是否是文本文件的影响, 后者由属性 svn:mime-type 决定. 比如说, 即使说你有一个漂亮的, 可以对 Microsoft Word 文档进行差异比较和合并的工具, 但是如果 Word 文档的 MIME 类型是二进制文件 (例如 application/msword), 那么 Subversion 也不会调用该 工具. 幸运的是, 你可以为 svn diff 增加选项 --force, 强制 Subversion 对文件进行差异比较. 关于 MIME 类型设置的更多内容, 见 “文件内容类型”一节

Subversion 1.5 引入了交互式的冲突解决 (见 “解决冲突”一节), 这项特性提供的选项之一是允许用户 交互式地调用一个第三方合并工具. 如果 Subversion 需要调用第三方合并工具, 它就会检查用户是否已经指定了工具, 它首先检查环境变量 SVN_MERGE, 如果该变量没有被设置, 它就继续检查运行时 配置选项 merge-tool-cmd. 一旦找到外部合并工具, 它就 调用该工具.

[注意] 注意

由于三路差异比较工具和合并工具的目标是基本相同的 (把各自的, 但 是互相重叠的修改和谐地合并到一起), Subversion 会根据不同的情景使用 它们. 在与用户交互时调用 Subversion 内建的三路差异比较引擎和它的外部 替代品其实是有风险的, 因为使用这些工具而耽搁的时间可能会导致某些对 时间比较敏感的操作失败, 这时候 Subversion 更愿意交互式地调用外部合并 工具.

虽然 Subversion 与外部合并工具之间的接口, 比 Subversion 与 diff, diff3 之间的接口要简单得多, 但是能够找到完全符合 Subversion 要求的外部合并工具还是没那么简单的. 为 Subversion 指定外部差异比较和合并工具的关键是使用包装脚本, 包装脚本 的功能是把 Subversion 传过来的参数转换成外部工具能够理解的参数, 然后再 把外部工具的输出转换成 Subversion 支持的格式. 下面几节进行了具体的介绍.

外部差异比较工具

Subversion 按照 GNU diff 命令的要求向外部差异 比较工具传递参数, 同时期望外部差异比较工具按照 GNU diff 的要求返回正确的退出值. 对于大多数差异比较 工具, 它们通常只对第 6 和第 7 个参数—分别表示差异的左边内容与右边 内容的文件路径—感兴趣. 注意, Subversion 为第一个被修改的文件 运行一次差异比较工具, 所以说如果你的差异比较工具是异步运行的 (或者说 在后台运行), 那么多个运行实例可能会同时运行. 最后, 如果差异比较工具 检测到了有差异, 则 Subversion 希望工具返回退出值 1, 如果没有检测到差异 则返回退出值 0, 若返回其他退出值则认为工具遇到了错误.[71]

例 7.2 “diffwrap.py”例 7.3 “diffwrap.bat” 分别展示了如何使用 Python 和 Windows 批处理脚本编写外部差异比较工具 的包装脚本.

例 7.2. diffwrap.py

#!/usr/bin/env python
import sys
import os

# Configure your favorite diff program here.
DIFF = "/usr/local/bin/my-diff-tool"

# Subversion provides the paths we need as the last two parameters.
LEFT  = sys.argv[-2]
RIGHT = sys.argv[-1]

# Call the diff command (change the following line to make sense for
# your diff program).
cmd = [DIFF, '--left', LEFT, '--right', RIGHT]
os.execv(cmd[0], cmd)

# Return an errorcode of 0 if no differences were detected, 1 if some were.
# Any other errorcode will be treated as fatal.

例 7.3. diffwrap.bat

@ECHO OFF

REM Configure your favorite diff program here.
SET DIFF="C:\Program Files\Funky Stuff\My Diff Tool.exe"

REM Subversion provides the paths we need as the last two parameters.
REM These are parameters 6 and 7 (unless you use svn diff -x, in
REM which case, all bets are off).
SET LEFT=%6
SET RIGHT=%7

REM Call the diff command (change the following line to make sense for
REM your diff program).
%DIFF% --left %LEFT% --right %RIGHT%

REM Return an errorcode of 0 if no differences were detected, 1 if some were.
REM Any other errorcode will be treated as fatal.

外部三路差异比较工具

当 Subversion 要执行非交互式的合并时就会调用三路差异比较程序, 如果被调用的是外部工具, 那么 Subversion 就会按照 GNU diff3 的要求向外部工具传递参数, 并且期望外部工具 以表示成功的值退出, 而合并完成后的全部文件内容是打印到标准输出 (这样的话 Subversion 就能把它们重定向到任意一个文件中). 对于大多数可 供选择的合并工具来说, 它们只对第 9, 第 10 和第 11 个参数感兴趣, 这 3 个参数分别表示 自己的, 较老的你的 文件路径. 注意, 由于 Subversion 依赖合并工具所 产生的输出, 因此你的脚本必须等到工具的输出全部都传递给 Subversion 后才能退出. 当脚本最终退出时, 如果合并成功, 那它应该以 0 作为退出值, 如果还有未解决的冲突, 那就以 1 作为退出值, 除了 0 和 1 之外的其他值 都被视为发生了严重的错误.

例 7.4 “diff3wrap.py”例 7.5 “diff3wrap.bat” 是 外部三路差异比较工具的包装脚本模板, 分别用 Python 和 Windows 批处 理脚本编写.

例 7.4. diff3wrap.py

#!/usr/bin/env python
import sys
import os

# Configure your favorite three-way diff program here.
DIFF3 = "/usr/local/bin/my-diff3-tool"

# Subversion provides the paths we need as the last three parameters.
MINE  = sys.argv[-3]
OLDER = sys.argv[-2]
YOURS = sys.argv[-1]

# Call the three-way diff command (change the following line to make
# sense for your three-way diff program).
cmd = [DIFF3, '--older', OLDER, '--mine', MINE, '--yours', YOURS]
os.execv(cmd[0], cmd)

# After performing the merge, this script needs to print the contents
# of the merged file to stdout.  Do that in whatever way you see fit.
# Return an errorcode of 0 on successful merge, 1 if unresolved conflicts
# remain in the result.  Any other errorcode will be treated as fatal.

例 7.5. diff3wrap.bat

@ECHO OFF

REM Configure your favorite three-way diff program here.
SET DIFF3="C:\Program Files\Funky Stuff\My Diff3 Tool.exe"

REM Subversion provides the paths we need as the last three parameters.
REM These are parameters 9, 10, and 11.  But we have access to only
REM nine parameters at a time, so we shift our nine-parameter window
REM twice to let us get to what we need.
SHIFT
SHIFT
SET MINE=%7
SET OLDER=%8
SET YOURS=%9

REM Call the three-way diff command (change the following line to make
REM sense for your three-way diff program).
%DIFF3% --older %OLDER% --mine %MINE% --yours %YOURS%

REM After performing the merge, this script needs to print the contents
REM of the merged file to stdout.  Do that in whatever way you see fit.
REM Return an errorcode of 0 on successful merge, 1 if unresolved conflicts
REM remain in the result.  Any other errorcode will be treated as fatal.

外部合并工具

在交互式地解决冲突时, Subversion 可以选择调用一个外部合并工具. Subversion 提供给外部合并工具的参数有: 未修改的基础文件路径, 他们的 文件路径 (含有上游修改), 我们的 文件路径 (含有本地修改), 用于存放合并工具合并后的文件路径, 以及发生 冲突的文件的工作副本路径 (相对于合并操作的原始目标). 如果执行成功, 合并工具应该返回 0, 如果发生错误则返回 1.

例 7.6 “mergewrap.py”例 7.7 “mergewrap.bat” 是 外部合并工具的包装脚本模板, 分别用 Python 和 Windows 批处理脚本编写.

例 7.6. mergewrap.py

#!/usr/bin/env python
import sys
import os

# Configure your favorite merge program here.
MERGE = "/usr/local/bin/my-merge-tool"

# Get the paths provided by Subversion.
BASE   = sys.argv[1]
THEIRS = sys.argv[2]
MINE   = sys.argv[3]
MERGED = sys.argv[4]
WCPATH = sys.argv[5]

# Call the merge command (change the following line to make sense for
# your merge program).
cmd = [MERGE, '--base', BASE, '--mine', MINE, '--theirs', THEIRS,
              '--outfile', MERGED]
os.execv(cmd[0], cmd)

# Return an errorcode of 0 if the conflict was resolved; 1 otherwise.
# Any other errorcode will be treated as fatal.

例 7.7. mergewrap.bat

@ECHO OFF

REM Configure your favorite merge program here.
SET MERGE="C:\Program Files\Funky Stuff\My Merge Tool.exe"

REM Get the paths provided by Subversion.
SET BASE=%1
SET THEIRS=%2
SET MINE=%3
SET MERGED=%4
SET WCPATH=%5

REM Call the merge command (change the following line to make sense for
REM your merge program).
%MERGE% --base %BASE% --mine %MINE% --theirs %THEIRS% --outfile %MERGED%

REM Return an errorcode of 0 if the conflict was resolved; 1 otherwise.
REM Any other errorcode will be treated as fatal.



[71] GNU diff 的手册是这么说的: 退出值 0 表示没有检测到差异, 1 表示检测到差异, 2 表示遇到了错误.