本手册旨在描述 Subversion 1.6.x 版本系列。如果您运行的是其他版本的 Subversion,强烈建议您访问 https://svnbook.subversion.org.cn/ 并参阅与您 Subversion 版本相符的文档版本。

处理结构冲突

到目前为止,我们只讨论了文件内容级别的冲突。当您和您的合作者在同一文件内进行重叠更改时,Subversion 会强制您在提交之前合并这些更改。[9]

但是,如果您的合作者移动或删除了您仍在处理的文件会发生什么?也许存在沟通错误,一个人认为文件应该被删除,而另一个人仍然想要提交对该文件的更改。或者也许您的合作者进行了一些重构,在此过程中重命名了文件和移动目录。如果您仍在处理这些文件,则可能需要将这些修改应用于其新位置的文件。此类冲突表现为目录树结构级别,而不是文件内容级别,称为 树冲突

与文本冲突一样,树冲突会阻止从冲突状态进行提交,从而让用户有机会检查工作副本的状态,以了解树冲突引起的潜在问题,并在提交之前解决任何此类问题。

树冲突示例

假设您正在处理的软件项目当前看起来像这样

$ svn list -Rv svn://svn.example.com/trunk/
      4 harry                 Feb 06 14:34 ./
      4 harry              23 Feb 06 14:34 COPYING
      4 harry              41 Feb 06 14:34 Makefile
      4 harry              33 Feb 06 14:34 README
      4 harry                 Feb 06 14:34 code/
      4 harry              51 Feb 06 14:34 code/bar.c
      4 harry             124 Feb 06 14:34 code/foo.c

您的合作者 Harry 已将文件 bar.c 重命名为 baz.c。您仍在工作副本中处理 bar.c,但您还不知道该文件已在仓库中重命名。

Harry 提交的日志消息看起来像这样

$ svn log -r5 svn://svn.example.com/trunk
------------------------------------------------------------------------
r5 | harry | 2009-02-06 14:42:59 +0000 (Fri, 06 Feb 2009) | 2 lines
Changed paths:
   M /trunk/Makefile
   D /trunk/code/bar.c
   A /trunk/code/baz.c (from /trunk/code/bar.c:4)

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

您所做的本地更改看起来像这样

$ svn diff
Index: code/foo.c
===================================================================
--- code/foo.c  (revision 4)
+++ 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 4)
+++ 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";
 }

您的更改都是基于版本 4 的。它们无法提交,因为 Harry 已经检入版本 5

$ svn commit -m "Small fixes"
Sending        code/bar.c
Sending        code/foo.c
Transmitting file data ..
svn: Commit failed (details follow):
svn: File not found: transaction '5-5', path '/trunk/code/bar.c'

此时,您需要运行 svn update。除了将我们的工作副本更新到最新状态,以便您查看 Harry 的更改之外,这还会标记树冲突,以便您有机会评估并正确解决它。

$ svn update
   C code/bar.c
A    code/baz.c
U    Makefile
Updated to revision 5.
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
M       code/baz.c

请注意,bar.c 会自动在工作副本中计划重新添加,这在您想保留文件的情况下简化了操作。

因为 Subversion 中的移动是通过复制操作后跟删除操作实现的,并且这两个操作在更新期间不能轻易相互关联,因此 Subversion 只能警告您关于本地修改文件上的传入删除操作。此删除操作 可能 是移动的一部分,也可能是一个真正的删除操作。与您的合作者交谈,或者作为最后的手段,svn log 是找出实际发生情况的好方法。

svn status 的输出中,foo.cbaz.c 都被报告为本地修改。您自己对 foo.c 做了更改,因此这并不奇怪。但是,为什么 baz.c 被报告为本地修改呢?

答案是,尽管移动实现有限,但 Subversion 足够智能,可以将您在 bar.c 中的本地编辑转移到 baz.c

$ svn diff code/baz.c
Index: code/baz.c
===================================================================
--- code/baz.c  (revision 5)
+++ 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";
 }
[Warning] 警告

对文件 bar.c 的本地编辑(在更新期间重命名为 baz.c)将仅应用于 bar.c,如果您的 bar.c 工作副本是基于它上次修改的版本(在仓库中移动之前)。否则,Subversion 将恢复从仓库中检索 baz.c,并且不会尝试将您的本地修改转移到它。您必须手动执行此操作。

svn info 显示了参与冲突的项目的 URL。 左侧 URL 显示冲突本地端的来源,而 右侧 URL 显示冲突传入端的来源。这些 URL 指示了您应该从哪里开始搜索仓库的历史记录以找到与您的本地更改冲突的更改。

$ svn info code/bar.c | tail -n 4 
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: Commit failed (details follow):
svn: Aborting commit: 'code/bar.c' remains in conflict

那么如何解决此冲突呢?您可以同意或不同意 Harry 做的移动。如果您同意,您可以删除 bar.c 并将树冲突标记为已解决

$ 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 5)
+++ 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 5)
+++ 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,确保在重命名后对它所做的任何更改都被保留或不值得保留。不要忘记撤消 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 5)
+++ 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 5)
+++ 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 5)
+++ code/baz.c	(working copy)
@@ -1,4 +0,0 @@
-const char *bar(void)
-{
-	return "Me neither!\n";
-}
Index: Makefile
===================================================================
--- Makefile	(revision 5)
+++ 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 他给您带来了多少额外工作。



[9] 好吧,如果您真的想这样做,您可以将包含冲突标记的文件标记为已解决并提交。但实际上很少这样做。