本文档仍在编写中,内容可能随时变更,可能无法准确地描述 Apache™ Subversion® 软件的任何已发布版本。将此页面添加书签或以其他方式将其推荐给其他人可能不是一个好主意。请访问 https://svnbook.subversion.org.cn/ 以获取此书的稳定版本。

处理结构冲突

到目前为止,我们只讨论了文件内容级别的冲突。当您和您的合作者对同一个文件进行重叠更改时,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
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 会自动安排在您的工作副本中重新添加,这在您想保留该文件的情况下简化了操作。

由于在 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 之前,您需要确定对它的更改是否需要应用到其他地方,例如应用到新的 baz.c 文件(所有 bar.c 代码现在所在的位置)。假设您的更改确实需要 跟随移动。Subversion 不够聪明,无法为您完成这项工作 [10],因此您需要手动迁移更改。

在我们的示例中,您可以手动重新进行对 bar.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 并解决冲突,指示解决逻辑接受工作副本中的当前内容作为所需的结果。

$ 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 发布