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 就会要求你在合并了这些修改 之后才能提交. [8]

如果其他人把你正在编辑的文件移动到其他地方或删除了, 那这时候又会发生 什么事? 发生这种事的原因可能是同事之间沟通不及时, 一个人认为文件应该 被删除, 而另一个人还想接着修改该文件, 也可能是你的同事想重新规划目录 布局. 如果你正在编辑的文件已经移动到了其他位置, 那么这些修改可能需要 应用到移动后的文件中. 这种冲突的级别是在目录树结构上, 而不是在文件的 内容上, 称为 目录冲突 (tree conflicts).

和文件内容的冲突一样, 只有在目录冲突解决之后才能向仓库提交修改.

目录冲突示例

假设有一个软件项目的代码目录结构如下所示:

$ svn list -Rv svn://svn.example.com/trunk/
     13 harry                 Sep 06 10:34 ./
     13 harry              27 Sep 06 10:34 COPYING
     13 harry              41 Sep 06 10:32 Makefile
     13 harry              53 Sep 06 10:34 README
     13 harry                 Sep 06 10:32 code/
     13 harry              54 Sep 06 10:32 code/bar.c
     13 harry             130 Sep 06 10:32 code/foo.c
$

后来, 在版本号 14, 你的同事 Harry 把 bar.c 重命名为 baz.c, 但是你并不知情. 此时你正忙于 编写另外一套修改, 其中就牵涉到 bar.c:

$ svn diff
Index: code/foo.c
===================================================================
--- code/foo.c	(revision 13)
+++ code/foo.c	(working copy)
@@ -3,5 +3,5 @@
 int main(int argc, char *argv[])
 {
     printf("I don't like being moved around!\n%s", bar());
-    return 0;
+    return 1;
 }
Index: code/bar.c
===================================================================
--- code/bar.c	(revision 13)
+++ code/bar.c	(working copy)
@@ -1,4 +1,4 @@
 const char *bar(void)
 {
-    return "Me neither!\n";
+    return "Well, I do like being moved around!\n";
 }
$

提交失败时你开始意识到有人已经修改了 bar.c:

$ svn commit -m "Small fixes"
Sending        code/bar.c
Transmitting file data .
svn: E155011: Commit failed (details follow):
svn: E155011: File '/home/svn/project/code/bar.c' is out of date
svn: E160013: File not found: transaction '14-e', path '/code/bar.c'
$

此时应该执行 svn update, 命令不仅把 Harry 的修改同步到本地工作副本, 还产生了一个目录冲突:

$ svn update
Updating '.':
   C code/bar.c
A    code/baz.c
U    Makefile
Updated to revision 14.
Summary of conflicts:
  Tree conflicts: 1
$

在上面的例子中, svn update 在第四列放置一个 大写字母 C 表示该条目有冲突. svn status 可以显示冲突的其他细节:

$ svn status
M       code/foo.c
A  +  C code/bar.c
      >   local edit, incoming delete upon update
Summary of conflicts:
  Tree conflicts: 1
$

注意 bar.c 如何又被自动地添加到工作副本中, 如果用户想保留 bar.c, 就不需要再额外执行一次 svn add.

由于 Subversion 是用一个复制操作和一个删除操作实现移动, 而且在 更新时很难将这两个操作联系在一起, 所以 Subversion 的警告信息只是说 在本地被修改的文件已经在仓库中被删除了, 这个删除可能是移动操作的一 部分, 也可能就是一次单纯的删除操作. 准确地判断仓库在语义上发生了什 么变化显得尤为重要—只有这样才能让自己的修改适应项目的整体 轨迹. 为了弄清楚冲突发生的原因, 你可以阅读日志, 和同事沟通, 在行的 级别上查看修改等.

在这个例子里, Harry 的提交日志提供了所需要的信息.

$ svn log -r14 ^/trunk
------------------------------------------------------------------------
r14 | harry | 2011-09-06 10:38:17 -0400 (Tue, 06 Sep 2011) | 1 line
Changed paths:
   M /Makefile
   D /code/bar.c
   A /code/baz.c (from /code/bar.c:13)

Rename bar.c to baz.c, and adjust Makefile accordingly.
------------------------------------------------------------------------
$

svn info 显示了冲突条目的 URL. 左边 (left) 的 URL 显示了 冲突的本地端来源, 右边 (right) 的 URL 显示了冲突的服务器端来源, 这些 URL 指出了我们应该从哪个版本号开始搜索导致冲突的修改.

$ svn info code/bar.c
Path: code/bar.c
Name: bar.c
URL: http://svn.example.com/svn/repo/trunk/code/bar.c
…
Tree conflict: local edit, incoming delete upon update
  Source  left: (file) ^/trunk/code/bar.c@4
  Source right: (none) ^/trunk/code/bar.c@5

$

bar.c 已经成为目录冲突的受害者, 在冲突解决 之前无法提交:

$ svn commit -m "Small fixes" 
svn: E155015: Commit failed (details follow):
svn: E155015: Aborting commit: '/home/svn/project/code/bar.c' remains in confl
ict
$

为了解决这个冲突, 用户要么同意, 要么不同意 Harry 提交的重命名 修改.

如果用户同意重命名, 那么 bar.c 就成了多余的 了, 你可能想要删除 bar.c 并把目录冲突标记为已 解决, 但是请等一下, 文件上还有你的修改! 在删除 bar.c 之前你必须决定它上面的修改是否需要应用到 其他地方, 比如重命名后的文件 baz.c. 不妨假设你 的修改需要 跟随重命名 (follow the move), 但是 Subversion 还没有聪明到能够替你完成这件工作[9], 所以你必须手动地迁移修改.

在我们的例子里, 你完全可以手动地再修改一次 baz.c—毕竟只修改了一行, 但是这种做法只适用 于修改很少的情况, 我们再介绍一种更具有通用性的方法. 先用 svn diff 创建一个补丁文件, 然后修改补丁文件的头 部信息, 使其指向重命名后的文件, 最后再应用修改后的补丁.

$ svn diff code/bar.c > PATCHFILE
$ cat PATCHFILE
Index: code/bar.c
===================================================================
--- code/bar.c	(revision 14)
+++ code/bar.c	(working copy)
@@ -1,4 +1,4 @@
 const char *bar(void)
 {
-    return "Me neither!\n";
+    return "Well, I do like being moved around!\n";
 }
$ ### Edit PATCHFILE to refer to code/baz.c instead of code/bar.c
$ cat PATCHFILE
Index: code/baz.c
===================================================================
--- code/baz.c	(revision 14)
+++ code/baz.c	(working copy)
@@ -1,4 +1,4 @@
 const char *bar(void)
 {
-    return "Me neither!\n";
+    return "Well, I do like being moved around!\n";
 }
$ svn patch PATCHFILE
U         code/baz.c
$

现在 bar.c 上的修改已经成功地转移到了 baz.c 上, 用户现在可以删除 bar.c 并告诉 Subversion 把工作副本的当前内容 作为冲突解决的结果.

$ svn delete --force code/bar.c
D         code/bar.c
$ svn resolve --accept=working code/bar.c
Resolved conflicted state of 'code/bar.c'
$ svn status
M       code/foo.c
M       code/baz.c
$ svn diff
Index: code/foo.c
===================================================================
--- code/foo.c  (revision 14)
+++ code/foo.c  (working copy)
@@ -3,5 +3,5 @@
 int main(int argc, char *argv[])
 {
     printf("I don't like being moved around!\n%s", bar());
-    return 0;
+    return 1;
 }
Index: code/baz.c
===================================================================
--- code/baz.c  (revision 14)
+++ code/baz.c  (working copy)
@@ -1,4 +1,4 @@
 const char *bar(void)
 {
-    return "Me neither!\n";
+    return "Well, I do like being moved around!\n";
 }
$

但是如果你不同意重命名, 那又该如何? 如果用户已经确定 baz.c 上的修改已经进行了保存或者可以丢弃, 那也可以 直接删除 baz.c (别忘了撤消 Harry 对 Makefile 的修改). 因为 bar.c 已经准备好添加到仓库中, 所以接下来只需 要把冲突标记为已解决即可:

$ svn delete --force code/baz.c
D         code/baz.c
$ svn resolve --accept=working code/bar.c
Resolved conflicted state of 'code/bar.c'
$ svn status
M       code/foo.c
A  +    code/bar.c
D       code/baz.c
M       Makefile
$ svn diff
Index: code/foo.c
===================================================================
--- code/foo.c	(revision 14)
+++ code/foo.c	(working copy)
@@ -3,5 +3,5 @@
 int main(int argc, char *argv[])
 {
     printf("I don't like being moved around!\n%s", bar());
-    return 0;
+    return 1;
 }
Index: code/bar.c
===================================================================
--- code/bar.c	(revision 14)
+++ code/bar.c	(working copy)
@@ -1,4 +1,4 @@
 const char *bar(void)
 {
-    return "Me neither!\n";
+    return "Well, I do like being moved around!\n";
 }
Index: code/baz.c
===================================================================
--- code/baz.c	(revision 14)
+++ code/baz.c	(working copy)
@@ -1,4 +0,0 @@
-const char *bar(void)
-{
-    return "Me neither!\n";
-}
Index: Makefile
===================================================================
--- Makefile	(revision 14)
+++ Makefile	(working copy)
@@ -1,2 +1,2 @@
 foo: 
-	$(CC) -o $@ code/foo.c code/baz.c
+	$(CC) -o $@ code/foo.c code/bar.c

恭喜, 你已经解决了你的第一个目录冲突! 现在你可以提交修改, 并告诉 Harry 由于他的修改, 你做了很多额外的工作.



[8] 当然, 你 可以 把仍然含有冲突标记的文件标记为已解决并提交它们, 但是现实中几乎不 会有人这么做.

[9] 在某些情况下, Subversion 1.5 和 1.6 替你完成这件事, 但 这种有点随意的功能已经在 Subversion 1.7 被移除了.