外部存储库副本

本文已获得原始位置 http://blogs.collab.net/subversion/foreign-repository-copies 的许可,并在此处镜像。已删除或更新了失效链接。

作者:C. Michael Pilato

发布 2013-06-28

有一段时间以前,我在 这个空间中写过关于我设法在 Subversion 中实现的一些新功能——从所谓的“外部存储库”(即与工作副本不同的存储库)将更改合并到工作副本中。  我将此功能吹捧为管理供应商分支的绝佳替代方法。  我承认我在那篇文章中可能略过了细节,但这个想法似乎很简单:  将第三方代码库的基线副本导入到您的存储库中,然后将第三方所做的更改直接从他们的存储库合并到您的本地基线副本中。  就这样!  供应商分支。

我有一些与 CollabNet 相关的个人项目,这些项目使用了第三方代码。例如,CollabNet 的 TeamForge 和 Subversion Edge 产品都使用 ViewVC。在许多情况下,需要对第三方代码库进行一些小的定制。供应商分支是管理这些更改的自然方式。

但是,当我开始将理论付诸实践时,我在第一个分支初始化步骤中遇到了一个意想不到的问题:属性。svn import 无法识别或复制可能记录在导入源上的任何 Subversion 文件或目录属性。因此,当我使用 svn import 来用第三方代码的基线版本实例化我的供应商分支时,我会得到一个混合的信息。当然,我在我的工作副本中完美地复制了供应商的文件内容和目录结构。但是,元数据(例如 svn:ignoresvn:eol-stylesvn:keywords 等节点属性)并非来自供应商的原始数据源。相反,它来自我自己的本地 自动属性配置

请注意,在以下(有点人为的)示例中,svn import 有效地丢失了第三方库中存在的 svn:ignore 属性。

$ svn co http://otherfolks.com/svn/trunk third-party-wc
A    third-party-wc/src
A    third-party-wc/src/source_file.c
A    third-party-wc/src/source_file.h
A    third-party-wc/notes
A    third-party-wc/notes/how_to_do_stuff.txt
A    third-party-wc/README
Checked out revision 61.
$ svn plist -vR third-party-wc/
Properties on 'third-party-wc/src':
  svn:ignore
    a.out
$ svn import -m "Initialize vendor branch." \
      third-party-wc https://127.0.0.1/svn/my-project/vendor
Skipped 'third-party-wc/.svn'
Adding         third-party-wc/src
Adding         third-party-wc/src/source_file.c
Adding         third-party-wc/src/source_file.h
Adding         third-party-wc/notes
Adding         third-party-wc/notes/how_to_do_stuff.txt
Adding         third-party-wc/README

Committed revision 13.
$ svn co https://127.0.0.1/svn/my-project/vendor vendor-wc
A    vendor-wc/src
A    vendor-wc/src/source_file.c
A    vendor-wc/src/source_file.h
A    vendor-wc/notes
A    vendor-wc/notes/how_to_do_stuff.txt
A    vendor-wc/Makefile
A    vendor-wc/README
Checked out revision 13.
$ svn plist -vR vendor-wc
$

糟糕!我们丢失了供应商的 svn:ignore 属性!

因此,供应商数据的初始导入会顺利进行,但在之后(在执行包含属性更改的外部仓库合并时),我最终会遇到属性冲突。合并会说:“将属性 NAME 的值从 OLD_VALUE 更改为 NEW_VALUE。”我的工作副本会皱眉并回答:“但是 NAME 的值根本不是 OLD_VALUE。它是 SOMETHING_ELSE。”砰!冲突。

$ svn merge -r61:62 http://otherfolks.com/svn/trunk vendor-wc
Conflict for property 'svn:ignore' discovered on '/home/cmpilato/vendor-wc/src'.
They want to change the property value to 'program', you want to delete the property.
Select: (p) postpone,
        (mf) mine-full, (tf) theirs-full,
        (s) show all options: p
--- Merging (from foreign repository) r62 into 'vendor-wc':
 C   vendor-wc/src
A    vendor-wc/Makefile
Summary of conflicts:
  Property conflicts: 1
$

Ugh。

在这个例子中,解决冲突很简单。但这并不是重点。很明显,我需要一种更好的方法来初始化我的供应商分支。我最终还是用了一种比较麻烦的方法。我没有使用 *svn import*,而是使用了 *svn add*,以及一系列 *svn propset* 命令来手动模拟源数据的属性集,然后使用 *svn commit*。这种方法很繁琐,但它确实有效。与此同时,我在 Apache Subversion 项目的 issue tracker 中提交了一个功能请求,希望能够找到更好的方法。

我很高兴地宣布,得益于 Subversion 1.7.0 版本中对工作副本管理库的大规模重写(这是 Subversion 1.7.0 的基石),以及我的 CollabNet 同事 Bert Huijben 的额外努力,Subversion 1.8.0 版本开始支持从外部仓库复制。现在,从外部仓库初始化供应商分支就像使用 *svn copy* 命令一样简单,只需将外部仓库 URL 作为源,将本地工作副本目录作为目标即可。

$ svn cp http://otherfolks.com/svn/trunk my-project-wc/vendor
--- Copying from foreign repository URL 'http://otherfolks.com/svn/trunk':
A         my-project-wc/vendor
A         my-project-wc/vendor/src
A         my-project-wc/vendor/src/source_file.c
A         my-project-wc/vendor/src/source_file.h
A         my-project-wc/vendor/notes
A         my-project-wc/vendor/notes/how_to_do_stuff.txt
A         my-project-wc/vendor/README
$ svn ci -m "Initialize third-party vendor branch." my-project-wc
Adding         my-project-wc/vendor
Adding         my-project-wc/vendor/README
Adding         my-project-wc/vendor/notes
Adding         my-project-wc/vendor/notes/how_to_do_stuff.txt
Adding         my-project-wc/vendor/src
Adding         my-project-wc/vendor/src/source_file.c
Adding         my-project-wc/vendor/src/source_file.h
Transmitting file data ....
Committed revision 13.
$ svn plist -vR my-project-wc/vendor
Properties on 'my-project-wc/vendor/src':
  svn:ignore
    a.out
$

这次,我们的供应商的 svn:ignore 属性被保留了!这意味着,当需要合并供应商的最新更改时,合并过程应该会很顺利(当然,前提是我们没有引入冲突的自定义修改)。

$ svn merge -r61:62 http://otherfolks.com/svn/trunk my-project-wc/vendor
--- Merging (from foreign repository) r62 into 'my-project-wc/vendor':
 U   my-project-wc/vendor/src
A    my-project-wc/vendor/Makefile
$

太棒了!

为了完整起见,我应该指出,在复制过程中,有一个例外情况,即外部仓库数据的完美保留:任何在外部仓库中找到的 svn:mergeinfo 属性都会被从您本地工作副本中创建的副本中过滤掉。这样做可以避免一系列问题,这些问题可能会在 Subversion 认为合并跟踪信息适用于您 *自身* 仓库的历史记录时出现。但最终结果还是不错的。由于外部仓库合并也会忽略 svn:mergeinfo 属性,因此这个例外情况不会阻止您使用这种合并来维护您的供应商分支!

此外,虽然 *svn copy* 命令语法允许复制的源和目标分别为仓库 URL 或工作副本路径,但只有 *svn copy REPOS-URL WC-PATH* 语法支持外部仓库复制。例如,您不能直接从一个仓库中的 URL 复制到另一个仓库中的 URL 目标。

有关使用这种方法管理供应商分支的更多详细信息,请查看我 重新编写的 *版本控制与 Subversion* 书籍中的章节。有关 Subversion 1.8 提供的功能的更多信息,请参阅 官方发布说明

(感谢 Bert!)

关于作者

C. Michael Pilato 是 Subversion 的核心开发者,也是《版本控制之道:使用 Subversion》(O'Reilly Media)的合著者,以及 ViewVC 的主要维护者。他作为 CollabNet 的软件工程师,从他的家乡北卡罗来纳州远程工作,并且从 2001 年初开始就是一名活跃的开源开发者。Mike 是一位自豪的丈夫和父亲,他热爱旅行、足球、与家人共度美好时光以及这些活动的任何组合。他还喜欢作曲和表演音乐,并怀有对摇滚明星的秘密幻想。Mike 拥有北卡罗来纳大学夏洛特分校的计算机科学和数学学位。