Vendor Branches

As is especially the case when developing software, the data that you maintain under version control is often closely related to, or perhaps dependent upon, someone else's data. Generally, the needs of your project will dictate that you stay as up to date as possible with the data provided by that external entity without sacrificing the stability of your own project. This scenario plays itself out all the time—anywhere that the information generated by one group of people has a direct effect on that which is generated by another group.

For example, software developers might be working on an application that makes use of a third-party library. Subversion has just such a relationship with the Apache Portable Runtime library (see “Apache可移植运行库”一节). The Subversion source code depends on the APR library for all its portability needs. In earlier stages of Subversion's development, the project closely tracked APR's changing API, always sticking to the “bleeding edge” of the library's code churn. Now that both APR and Subversion have matured, Subversion attempts to synchronize with APR's library API only at well-tested, stable release points.

现在,如果你的项目依赖于其他人的信息,有许多方法可以用来尝试同步你的信息,最痛苦的,你可以为项目所有的贡献者发布口头或书写的指导,告诉他们确信他们拥有你们的项目需要的特定版本的第三方信息。如果第三方信息是用Subversion版本库维护,你可以使用Subversion的外部定义来有效的“强制”特定的版本的信息在你的工作拷贝的的位置(见“外部定义”一节)。

But sometimes you want to maintain custom modifications to third-party code in your own version control system. Returning to the software development example, programmers might need to make modifications to that third-party library for their own purposes. These modifications might include new functionality or bug fixes, maintained internally only until they become part of an official release of the third-party library. Or the changes might never be relayed back to the library maintainers, existing solely as custom tweaks to make the library further suit the needs of the software developers.

Now you face an interesting situation. Your project could house its custom modifications to the third-party data in some disjointed fashion, such as using patch files or full-fledged alternate versions of files and directories. But these quickly become maintenance headaches, requiring some mechanism by which to apply your custom changes to the third-party code and necessitating regeneration of those changes with each successive version of the third-party code that you track.

这个问题的解决方案是使用供方分支,一个供方分支是一个目录树保存了第三方实体或供应方的信息,每一个供应方数据的版本吸收到你的项目叫做供方drop

供方分支提供了两个关键的益处,第一,通过在我们的版本控制系统保存现在支持的供方drop,你项目的成员不需要指导他们是否有了正确版本的供方数据,他们只需要作为不同工作拷贝更新的一部份,简单的接受正确的版本就可以了。第二,因为数据存在于你自己的Subversion版本库,你可以在恰当的位置保存你的自定义修改—你不需要一个自动的(或者是更坏,手工的)方法来交换你的自定义行为。

常规的供方分支管理过程

Managing vendor branches generally works like this: first, you create a top-level directory (such as /vendor) to hold the vendor branches. Then you import the third-party code into a subdirectory of that top-level directory. You then copy that subdirectory into your main development branch (for example, /trunk) at the appropriate location. You always make your local changes in the main development branch. With each new release of the code you are tracking, you bring it into the vendor branch and merge the changes into /trunk, resolving whatever conflicts occur between your local changes and the upstream changes.

An example will help to clarify this algorithm. We'll use a scenario where your development team is creating a calculator program that links against a third-party complex number arithmetic library, libcomplex. We'll begin with the initial creation of the vendor branch and the import of the first vendor drop. We'll call our vendor branch directory libcomplex, and our code drops will go into a subdirectory of our vendor branch called current. And since svn import creates all the intermediate parent directories it needs, we can actually accomplish both of these steps with a single command:

$ svn import /path/to/libcomplex-1.0 \
             http://svn.example.com/repos/vendor/libcomplex/current \
             -m 'importing initial 1.0 vendor drop'
…

We now have the current version of the libcomplex source code in /vendor/libcomplex/current. Now, we tag that version (see “标签”一节) and then copy it into the main development branch. Our copy will create a new directory called libcomplex in our existing calc project directory. It is in this copied version of the vendor data that we will make our customizations:

$ svn copy http://svn.example.com/repos/vendor/libcomplex/current  \
           http://svn.example.com/repos/vendor/libcomplex/1.0      \
           -m 'tagging libcomplex-1.0'
…
$ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0  \
           http://svn.example.com/repos/calc/libcomplex        \
           -m 'bringing libcomplex-1.0 into the main branch'
…

我们取出我们项目的主分支—现在包括了第一个供方释放的拷贝—我们开始自定义libcomplex的代码,在我们知道之前,我们的libcomplex修改版本是已经与我们的计算器程序完全集成了。 [23]

几周之后,libcomplex得开发者发布了一个新的版本—版本1.1—包括了我们很需要的一些特性和功能。我们很希望升级到这个版本,但不希望失去在当前版本所作的修改。我们本质上会希望把我们当前基线版本是的libcomplex1.0的拷贝替换为libcomplex 1.1,然后把前面自定义的修改应用到新的版本。但是实际上我们通过一个相反的方向解决这个问题,应用libcomplex从版本1.0到1.1的修改到我们修改的拷贝。

To perform this upgrade, we check out a copy of our vendor branch and replace the code in the current directory with the new libcomplex 1.1 source code. We quite literally copy new files on top of existing files, perhaps exploding the libcomplex 1.1 release tarball atop our existing files and directories. The goal here is to make our current directory contain only the libcomplex 1.1 code and to ensure that all that code is under version control. Oh, and we want to do this with as little version control history disturbance as possible.

After replacing the 1.0 code with 1.1 code, svn status will show files with local modifications as well as, perhaps, some unversioned files. If we did what we were supposed to do, the unversioned files are only those new files introduced in the 1.1 release of libcomplex—we run svn add on those to get them under version control. If the 1.1 code no longer has certain files that were in the 1.0 tree, it may be hard to notice them; you'd have to compare the two trees with some external tool and then svn delete any files present in 1.0 but not in 1.1. (Although it might also be just fine to let these same files live on in unused obscurity!) Finally, once our current working copy contains only the libcomplex 1.1 code, we commit the changes we made to get it looking that way.

Our current branch now contains the new vendor drop. We tag the new version as 1.1 (in the same way we previously tagged the version 1.0 vendor drop), and then merge the differences between the tag of the previous version and the new current version into our main development branch:

$ cd working-copies/calc
$ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0      \
            http://svn.example.com/repos/vendor/libcomplex/current  \
            libcomplex
… # resolve all the conflicts between their changes and our changes
$ svn commit -m 'merging libcomplex-1.1 into the main branch'
…

In the trivial use case, the new version of our third-party tool would look, from a files-and-directories point of view, just like the previous version. None of the libcomplex source files would have been deleted, renamed, or moved to different locations—the new version would contain only textual modifications against the previous one. In a perfect world, our modifications would apply cleanly to the new version of the library, with absolutely no complications or conflicts.

But things aren't always that simple, and in fact it is quite common for source files to get moved around between releases of software. This complicates the process of ensuring that our modifications are still valid for the new version of code, and things can quickly degrade into a situation where we have to manually recreate our customizations in the new version. Once Subversion knows about the history of a given source file—including all its previous locations—the process of merging in the new version of the library is pretty simple. But we are responsible for telling Subversion how the source file layout changed from vendor drop to vendor drop.

svn_load_dirs.pl

Vendor drops that contain more than a few deletes, additions, and moves complicate the process of upgrading to each successive version of the third-party data. So Subversion supplies the svn_load_dirs.pl script to assist with this process. This script automates the importing steps we mentioned in the general vendor branch management procedure to make sure that mistakes are minimized. You will still be responsible for using the merge commands to merge the new versions of the third-party data into your main development branch, but svn_load_dirs.pl can help you more quickly and easily arrive at that stage.

一句话,svn_load_dirs.pl是一个增强的svn import,具备了许多重要的特性:

  • 它可以在任何有一个存在的版本库目录与一个外部的目录匹配时执行,会执行所有必要的添加和删除并且可以选则执行移动。

  • 它可以用来操作一系列复杂的操作,如那些需要一个中间媒介的提交—如在操作之前重命名一个文件或者目录两次。

  • 它可以随意的为新导入目录打上标签。

  • 它可以随意为符合正则表达式的文件和目录添加任意的属性。

svn_load_dirs.pl利用三个强制的参数,第一个参数是Subversion工作的基本目录URL,第二个参数在URL之后—相对于第一个参数—指向当前的供方分支将会导入的目录,最后,第三个参数是一个需要导入的本地目录,使用前面的例子,一个典型的svn_load_dirs.pl调用看起来如下:

$ svn_load_dirs.pl http://svn.example.com/repos/vendor/libcomplex \
                   current                                        \
                   /path/to/libcomplex-1.1
…

你可以说明你会希望svn_load_dirs.pl同时打上标签,这使用-t命令行选项,需要指定一个标签名,这个标签是第一个参数的一个相对URL。

$ svn_load_dirs.pl -t libcomplex-1.1                              \
                   http://svn.example.com/repos/vendor/libcomplex \
                   current                                        \
                   /path/to/libcomplex-1.1
…

When you run svn_load_dirs.pl, it examines the contents of your existing “current” vendor drop and compares them with the proposed new vendor drop. In the trivial case, there will be no files that are in one version and not the other, and the script will perform the new import without incident. If, however, there are discrepancies in the file layouts between versions, svn_load_dirs.pl will ask you how to resolve those differences. For example, you will have the opportunity to tell the script that you know that the file math.c in version 1.0 of libcomplex was renamed to arithmetic.c in libcomplex 1.1. Any discrepancies not explained by moves are treated as regular additions and deletions.

这个脚本也接受单独配置文件用来为添加到版本库的文件和目录设置匹配正则表达式的属性。配置文件通过svn_load_dirs.pl-p命令行选项指定,这个配置文件的每一行都是一个空白分割的两列或者四列值:一个Perl样式的正则表达式来匹配添加的路径、一个控制关键字(break或者是cont)和可选的属性名和值。

\.png$              break   svn:mime-type   image/png
\.jpe?g$            break   svn:mime-type   image/jpeg
\.m3u$              cont    svn:mime-type   audio/x-mpegurl
\.m3u$              break   svn:eol-style   LF
.*                  break   svn:eol-style   native

对每一个添加的路径,会按照顺序为匹配正则表达式的文件配置属性,除非控制标志是break(意味着不需要更多的路径匹配应用到这个路径)。如果控制说明是contcontinue的缩写—然后匹配工作会继续到配置文件的下一行。

任何正则表达式,属性名或者属性值的空格必须使用单引号或者双引号环绕,你可以使用反斜杠(\)换码符来回避引号,反斜杠只会在解析配置文件时回避引号,所以不能保护必要正则表达式字符之外的的其它字符。



[23] 而且完全没有bug,当然!