处理目录冲突
到目前为止我们只在文件内容的级别上讨论冲突, 如果你和你的同事在 同一文件上的修改相互重叠, 那么 Subversion 就会要求你在合并了这些修改 之后才能提交. [8]
如果其他人把你正在编辑的文件移动到其他地方或删除了, 那这时候又会发生 什么事? 发生这种事的原因可能是同事之间沟通不及时, 一个人认为文件应该 被删除, 而另一个人还想接着修改该文件, 也可能是你的同事想重新规划目录 布局. 如果你正在编辑的文件已经移动到了其他位置, 那么这些修改可能需要 应用到移动后的文件中. 这种冲突的级别是在目录树结构上, 而不是在文件的 内容上, 称为 目录冲突 (tree conflicts).
Subversion 1.6 以前的目录冲突
在 Subversion 1.6 以前的版本中, 目录冲突会产生意想不到的结果. 例如, 如果一个文件在本地被修改了, 但是在仓库中被重命名了. 执行 svn update 时 Subversion 会执行以下步骤:
检查将要被重命名的文件.
删除重命名前的文件, 如果文件在本地被修改了, 就保留在 磁盘上, 但不再是仓库或工作副本的一部分.
添加重命名后的文件.
出现这种情况时, 用户可能还没有意识到有些修改被留在了未被跟踪的 文件上, 如果这时候向服务器提交, 这些修改就不会被提交到仓库中. 如果 文件很多, 出现这种情况的概率就会增大, 处理起来也会相当枯燥.
从 Subversion 1.6 开始, 这种以及其他类似的情况都会被当作冲突 看待.
和文件内容的冲突一样, 只有在目录冲突解决之后才能向仓库提交修改.
目录冲突示例
假设有一个软件项目的代码目录结构如下所示:
$ 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 被移除了.