本文档旨在描述 Apache™ Subversion® 的 1.7.x 系列。如果您运行的是其他版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的文档。

处理结构冲突

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

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

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

树冲突示例

假设您正在进行的软件项目目前看起来像这样

$ 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
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 如何自动安排在您的工作副本中重新添加,这在您想要保留该文件的情况下简化了操作。

由于 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。 左侧 URL 显示冲突本地端的来源,而 右侧 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 文件。假设您的更改确实需要 跟随移动。Subversion 不够智能,无法为您完成这项工作[10],因此您需要手动迁移更改。

在我们的示例中,您可以手动轻松地重新进行对 bar.c 的更改——毕竟,它只是一个单行更改。但是,情况并非总是如此,因此我们将展示一种更可扩展的方法。我们首先使用 svn diff 创建一个补丁文件。然后,我们将编辑该补丁文件的标题以指向重命名文件的新的名称。最后,我们将修改后的补丁重新应用到我们的工作副本。

$ svn diff code/bar.c > PATCHFILE
$ cat PATCHFILE
Index: code/bar.c
===================================================================
--- code/bar.c	(working copy)
+++ 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	(working copy)
+++ 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 并解决冲突,指示解决逻辑接受当前工作副本中的内容作为所需结果。

$ 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,但要确保在重命名后对它所做的任何更改都已保留或不值得保留。(不要忘记也撤消 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 他给您带来了多少额外工作。



[9] 好吧,如果您真的想,您可以将包含冲突标记的文件标记为已解决并提交它们。但这在实践中很少见。

[10] 在某些情况下,Subversion 1.5 和 1.6 实际为您处理此问题,但此功能在 Subversion 1.7 中被删除,因为它有点不可靠。

TortoiseSVN 官方中文版 1.14.7 发布