Subversion 1.5 Mergeinfo - 深入理解

Filed Under (General) by rocksun on 19-11-2008

Tagged Under : ,

转载请注明本文地址:http://www.subversion.org.cn/submerged/?p=70

英文地址:http://www.collab.net/community/subversion/articles/merge-info.html,转载请注明本文地址。

本文假定你已经对《使用Subversion进行版本控制》中第四章的主题有所了解,并有了一些Subversion1.5的经验。本文所示的例子是通过候选4版本完成。在2008年5月6日,最新的RC是Subversion 1.5候选5。

Mergeinfo

Mergeinfo,或者更精确地说版本化属性svn:mergeinfo,是文件或目录上的合并历史。本文会详细介绍mergeinfo;包括它的含义和工作原理。

svn:mergeinfo 属性的值仅仅是新起一行的合并源路经(相对于版本库根),每个源路经后面跟一个冒号,冒号后是以前合并源的修订版本,如果你对精确的语法感兴趣,可以看一下Subversion代码中svn_mergeinfo.h的注释‘Overview of the @c SVN_PROP_MERGEINFO property’。

注意:这是一份非常长的文章,会覆盖许多非典型的用例,如果你采用Subversion手册中的‘同步和重新集成’范例,那本文有一点过于复杂了。如果是这个情况,你可能希望只阅读“明确Mergeinfo继承”的一部分,然后直接去阅读“离别思考”小节。

明确Mergeinfo

当一个路径设置了svn:mergeinfo属性,这个路径就有了明确的mergeinfo,明确的mergeinfo通常创建在工作拷贝中合并的目标(我们会再其上执行合并)上,我说“通常”,那是因为也有时候合并不会设置或修改mergeinfo,可以看小节“我的mergeinfo在哪里?”。

明确地mergeinfo也会在合并目标的子目录上创建或修改,通常这些子树在合并之前已经有了明确的mergeinfo,因为本身曾经作为合并的目标。然而也有时候mergeinfo会创建到以前没有明确mergeinfo的子树,可以看小节“mergeinfo继承和非继承范围”。

让我们看一些明确mergeinfo的例子,Subversion 1.5.x分支的工作拷贝,这个分支是我们在准备发布1.5的时候为trunk做的一份拷贝,几乎是创建之后我们就立刻开始选择性的将变更从trunk合并到分支:

>svn info \svn\src-1.5.x
Path: \SVN\src-1.5.x
URL:
http://svn.collab.net/repos/svn/branches/1.5.x
Repository Root: http://svn.collab.net/repos/svn
Repository UUID: 612f8ebc-c883-4be0-9ee0-a4e9ef946e3a
Revision: 30056
Node Kind: directory
Schedule: normal
Last Changed Author: hwright
Last Changed Rev: 30056
Last Changed Date: 2008-03-26 00:32:28 -0400 (Wed, 26 Mar 2008)
>svn pg svn:mergeinfo \svn\src-1.5.x –recursive
\SVN\src-1.5.x - /trunk:29085-29089,29091,29094-29107,29111,29114,29117,29126-29127, 29129-29133,29135-29150,29153-29164,29166,29174,29176-29186,29188-29189, 29193-29194,29198-29200,29202-29206,29208-29251,29254-29256,29261, 29267-29273,29277,29280-29281,29284,29287-29303,29305-29307,29309-29325, 29327-29343,29345-29348,29358-29379,29381-29392,29397,29399,29401,29409, 29412,29414-29415,29417-29423,29425-29426,29429,29433-29434,29436-29447, 29449-29466,29468-29478,29482,29484,29486-29487,29489,29491,29493,29496, 29498,29508,29527-29528,29531,29533,29539-29540,29542,29544,29546,29551, 29553,29556,29559,29565,29567-29569,29571-29578,29581,29583,29591,29594, 29600,29603,29607,29611,29613-29614,29619,29623,29625-29626,29630-29631, 29634,29642,29648,29650,29656,29659-29660,29663-29664,29671-29672, 29677-29680,29692,29738-29739,29742-29744,29746,29751,29763,29769-29770, 29784,29787,29797,29801,29821,29824,29828,29835,29855,29868-29869,29878, 29883-29884,29895,29898,29900,29914,29920,29925,29930,29940,29950,29958, 29962,29968,29980,29994-29997,30004,30020

注意:你会注意到例子中有许多反斜杠,不要试图调整监视器来调节你的视力,是的,我在Windows下进行Subversion开发。

在上面的例子中,只有一个合并源路经,/trunk,然后是合并到1.5.x分支的修订版本号码。请注意,格式并不是Subversion子命令中的-rX:Y,svn:mergeinfo使用的格式A-B,其中A也是被包含的。换句话说,如果你合并了-r3:7,则mergeinfo记录的范围是4-7。

非操作 Vs  操作(Mergeinfo修订版本)

有一点需要格外注意,并不是给定源上的svn:mergeinfo属性列出的所有修订版本就必须是源上经过的操作,可以看前面例子中显示的r29093:29107是从trunk合并过来的,但是如果我们看一下r29095的日志,我们就会看到那个变更并没有修改trunk:

>svn log http://svn.collab.net/repos/svn/trunk -v -r29095
------------------------------------------------------------------------

>

那为什么r29095会包含在1.5.x的mergeinfo中呢?因为某人从trunk合并了修订版本范围-rX:Y,其中X < 29095 <= Y。开发社区决定让未操作的修订版本记录到mergeinfo中,这样才能保证mergeinfo属性不会过于琐碎,更适合人们查看。可以看一下1.5.x的mergeinfo你会发出疑问:“重点在哪里?太乱了!”你对了,这是由于1.5.x是一个发布分支,但是对于其他情况,比如最终会合并会trunk的特性分支,这个特性可以让mergeinfo漂亮和整洁,有时候仅是一段范围。

我的Mergeinfo在哪里?

在某些情况下,合并不会创建或修改mergeinfo:

  • 当使用–ignore-ancestry选项时,更进一步,使用在决定应该合并哪些内容时不会考虑mergeinfo的合并选项。
  • 如果合并源来自与目标不同的版本库,也称为foreign repository merges
  • 如果反向合并某个路径自己的历史,这是合并用来恢复提交的变更的常见情况。这可以工作,但是mergeinfo不会更新,这是当前的mergeinfo设计所无法明确记录的反向合并。有点混乱?这是一个简单的例子,我们可以这样检出一个工作拷贝:

>svn co %url% wc
A    wc\A
A    wc\A\B
A    wc\A\B\lambda
A    wc\A\B\E
A    wc\A\B\E\alpha
A    wc\A\B\E\beta
A    wc\A\B\F
A    wc\A\mu
A    wc\A\C
A    wc\A\D
A    wc\A\D\gamma
A    wc\A\D\G
A    wc\A\D\G\pi
A    wc\A\D\G\rho
A    wc\A\D\G\tau
A    wc\A\D\H
A    wc\A\D\H\chi
A    wc\A\D\H\omega
A    wc\A\D\H\psi
A    wc\iota
Checked out revision 1.

请注意现在工作拷贝没有mergeinfo:

>svn pg svn:mergeinfo wc -R

>

然后我们作出简单的文本修改并提交为r2:T

>echo 'text change to a file' > wc\A\mu< /FONT >

>svn ci -m "" wc
Sending        wc\A\mu
Transmitting file data .
Committed revision 2.

噢,我们发现不需要那个变更,并将r2的修改回退掉。Subversion会一直允许我们做这种合并,无论mergeinfo的值是多少:

>svn merge %url%/A/mu wc/A/mu -c-2
--- Reverse-merging r2 into 'wc\A\mu':
U    wc\A\mu

因为当前的mergeinfo实现无法表示反向合并,前面的合并不会留下任何mergeinfo的证据:

>svn pg svn:mergeinfo wc -R

>

而且我们有了不会设置或修改任何mergeinfo的合并。

明确Mergeinfo继承

可以再看一下更靠前的例子,检查subversion 1.5.x的分支工作拷贝上的mergeinfo信息,请注意--recursive选项?这意味着我们会得到设置在\SVN\src-1.5.x工作拷贝目录根下的所有svn:mergeinfo属性列表,但是我们只看到设置在\SVN\src-1.5.x上的明确mergeinfo。想必许多从trunk到\SVN\src-1.5.x一定影响了\SVN\src-1.5.x的子目录树?看一下trunk最近的合并修订版本我们可以看到这是正确的:

>svn log -v -q -c30020 http://svn.collab.net/repos/svn/trunk
------------------------------------------------------------------------
r30020 | pburba | 2008-03-24 11:15:48 -0400 (Mon, 24 Mar 2008)
Changed paths:
M /trunk/subversion/libsvn_client/copy.c
------------------------------------------------------------------------

那\SVN\src-1.5.x\subversion\libsvn_client\copy.c怎么知道trunk的r30020合并了进来?它是通过mergeinfo继承获知的。如果路径没有明确的svn:mergeinfo,它还是可以从父(祖父或祖宗上)上的明确mergeinfo获取mergeinfo。

“最近祖先”的概念不仅限于工作拷贝。当在某个没有明确mergeinfo的路径上的检测继承mergeinfo时,subversion首先会搜寻工作拷贝,查看包含明确mergeinfo的祖先。如果它达到工作拷贝的根,而无法找到这样的父路径,它会再去查找版本库的父路径,如果必要会一直达到版本库的根。只有没有继承mergeinfo在版本库中发现,我们可以说这个路径没有mergeinfo(有时,我们甚至不可以那样说,看下个小节)。

举一个继承的真实例子,我们再看一下\SVN\src-1.5.x\subversion\libsvn_client\copy.c,我们知道\SVN\src-1.5.x中的mergeinfo中来自trunk的合并r30020一定修改了这个文件,但是我们知道copy.c本身没有任何明确的mergeinfo信息。但是因为我们知道copy.c的拥有明确mergeinfo的最近祖先是\SVN\src-1.5.x,那么因为mergeinfo继承,copy.c的继承mergeinfo等同于\SVN\src-1.5.x的信息。

不幸的是我们无法从命令行中直接看到这些信息,1.5提供了新的mergeinfo子命令,但是1.5只能为给定源提供了一列已经合并的(或对于合并合适的)修订版本:

>svn mergeinfo --show-revs merged http://svn.collab.net/repos/svn/trunk/subversion/libsvn_client/copy.c\svn\src-1.5.x\subversion\libsvn_client\copy.c
r229
r282
r316
r326
r380
.
.
<snipping a *long* list of revisions>
.
.
r28472
r28512
r28825
r29374
r30020

就像你看到的,输出有意作为脚本的输入,每个修订版本都单列一行。也要注意它与svn:mergeinfo不同,只列出了操作修订版本;每个列出的修订版本都确实对文件http://svn.collab.net/repos/svn/trunk/subversion/libsvn_client/copy.c作出了修改。

可以很容易的查看隐含继承的mergeinfo,让我们试着将两个修订版本直接合并到1.5.x中工作拷贝的copy.c中,其中一个修订是r30020,应当被忽略,因为copy.c的继承mergeinfo显示它已经被合并过了。为了发现另一个适合(eligible )合并的操作修订版本,我们再次使用svn mergeinfo,但此时我们使用–show-revs eligible选项:

>svn mergeinfo --show-revs eligible http://svn.collab.net/repos/svn/trunk/subversion/libsvn_client/copy.c\svn\src-1.5.x\subversion\libsvn_client\copy.c
r29167
r29961

好的,让我们将r29167和r30020合并到copy.c(请注意在1.5中,我们可以在merge的-c选项中指明多个修订版本):

>svn merge -c29167,30020 http://svn.collab.net/repos/svn/trunk/subversion/libsvn_client/copy.c\svn\src-1.5.x\subversion\libsvn_client\copy.c
--- Merging r29167 into ‘\SVN\src-1.5.x\subversion\libsvn_client\copy.c’:
U    \SVN\src-1.5.x\subversion\libsvn_client\copy.c

回想一下Subversion手册中说,1.5合并跟踪的一个关键特性是防止重复合并。从前面的输出中我们可以看到Subversion只会合并r29167,因为它发现根据copy.c的继承mergeinfo,r30020已经合并了!

已经覆盖了明确mergeinfo和mergeinfo继承的大多数基础,在进入更深入的主题之前,还需要知道关于mergeinfo和继承的两个关键点:

首先,如果一个路径有明确mergeinfo,则它会完全描述进入路径的合并,这个路径不会再继承任何信息。换句话说,继承只发生在路径没有明确mergeinfo的时候。

第二,看起来非常明显,但值得进行强调:当路径继承了mergeinfo,它只会来自包含明确mergeinfo的最近祖先,所以如果我们有了一个如下mergeinfo的工作拷贝:

>svn pg svn:mergeinfo -R src-branch
src-branch\subversion - /trunk/subversion:30045-30191,30210
src-branch - /trunk:30045-30197
src-branch\www - /trunk/www:30045-30212

文件src-branch\subversion\libsvn_repos\reporter.c 应当继承哪个mergeinfo?src-branch\www有mergeinfo,但它不是reporter.c的祖先。在src-branch也有mergeinfo,但是最接近的祖先是src-branch\subversion,也就是reporter.c得到mergeinfo的地方。

自然历史和隐含Mergeinfo

无论是路径的明确还是继承mergeinfo,每个路径都有一个自然的历史,而Subversion把其当作隐含的mergeinfo。诚然,这不是普通用户需要理解的东西,所以如果你只想浏览一下,可以安全的跳过这个主题。

还在?然后让我们检出一个某位Subversion开发者工作的特性分支的工作拷贝,然后立刻看一下隐含的mergeinfo:

>svn co http://svn.collab.net/repos/svn/branches/in-memory-cache mem-cache-wc -q

通过运行一些日志子命令,我们可以看到分支是从Subversion的trunk的r29755拷贝得到的:

>svn log --stop-on-copy -q mem-cache-wc
------------------------------------------------------------------------
r30314 | glasser | 2008-04-04 19:08:12 -0400 (Fri, 04 Apr 2008)
------------------------------------------------------------------------
r30313 | glasser | 2008-04-04 19:07:41 -0400 (Fri, 04 Apr 2008)
------------------------------------------------------------------------
r30312 | glasser | 2008-04-04 19:06:14 -0400 (Fri, 04 Apr 2008)
------------------------------------------------------------------------
.
.
<snipping some of the output for brevity>
.
.
------------------------------------------------------------------------
r29775 | glasser | 2008-03-07 13:57:45 -0500 (Fri, 07 Mar 2008)
------------------------------------------------------------------------
r29773 | glasser | 2008-03-07 13:23:54 -0500 (Fri, 07 Mar 2008)
------------------------------------------------------------------------
r29755 | glasser | 2008-03-06 19:46:43 -0500 (Thu, 06 Mar 2008)
------------------------------------------------------------------------
>svn log -v -r29755 mem-cache-wc
------------------------------------------------------------------------
r29755 | glasser | 2008-03-06 19:46:43 -0500 (Thu, 06 Mar 2008) | 14 lines
Changed paths:
A /branches/in-memory-cache (from /trunk:29754)
<snipping the actual log message>
------------------------------------------------------------------------

现在看一下分支的mergeinfo:

>svn pg svn:mergeinfo -R mem-cache-wc
mem-cache-wc - /branches/svn-mergeinfo-enhancements:30045-30214
/trunk:29755-30312

请注意来自/trunk的mergeinfo从r29755开始?如果我们希望合并一些早于r29754的修订时会怎样?Subversion会尝试从mem-cache-wc自己的历史中合并这些变更吗?让我们试一下,我们不按照普通的特性分支进行与trunk的合并(不需要指明范围),我们明确指定范围:

>svn merge http://svn.collab.net/repos/svn/trunk mem-cache-wc -r32000:HEAD
--- Merging r30313 through r30422 into 'mem-cache-wc':
U    mem-cache-wc\COMMITTERS
U    mem-cache-wc\subversion\libsvn_fs_base\tree.c
U    mem-cache-wc\subversion\libsvn_fs_base\bdb\node-origins-table.c
.
.
<snip>
.
.
U    mem-cache-wc\contrib\client-side\svnmucc\svnmucc-test.py
U    mem-cache-wc\configure.ac
G    mem-cache-wc

看一下提醒,Subversion只会合并r30313之后的,我们也不会预期合并r29754:30312之间的内容,因为这个范围已经进入明确mergeinfo。但是对于(原文-r32000:29754)-r32000:29754呢?为什么Subversion不会尝试合并这些修订版本?答案取决于/branches/in-memory-cache的隐含mergeinfo。因为/branches/in-memory-cache分支是从/trunk的r29755复制而来,之前,作为一次复制,与/trunk共享r29754以及之前的历史。在确定从turnk合并哪些版本时,Subversion会把“自然”历史当作隐含的mergeinfo。实际上,这意味着除了明确mergeinfo,/branches/in-memory-cache拥有隐含的mergeinfo为“/trunk:1-29754”。请注意,在合并之后in-memory-cache分支上的mergeinfo只添加了“/trunk:30313-30422”(因为mergeinfo描述路径自己的历史有点多余):

>svn pg svn:mergeinfo -R mem-cache-wc
mem-cache-wc - /branches/1.5.x-r30215:30238
/trunk:29755-30422

Mergeinfo继承和非继承范围

Subversion允许工作拷贝不完全代表版本库,这可以通过肤浅检出、转移子目录或对于部分树的授权限制实现。如果你愿意,可以一个不完整的树,Subversion会努力保持mergeinfo的精确。这主要通过非继承的mergeinfo范围实现,最好还是用例子来说明:

首先我们通过浅显检出来创建一个Subversion 1.5.x的不完整工作拷贝:

注意:–depth选项也是1.5的新特性,可以限制Subversion的子命令在目标工作拷贝或URL的某深度进行,在这个例子下,我们使用‘immediates’值,可以给我们1.5.x分支的根,以及根的直接文件和子目录。

>svn co http://svn.collab.net/repos/svn/branches/1.5.x@30435 1.5.x --depth immediates
A    1.5.x\Makefile.in
A    1.5.x\STATUS
A    1.5.x\build.conf
A    1.5.x\www
A    1.5.x\win-tests.py
A    1.5.x\COMMITTERS
A    1.5.x\TRANSLATING
A    1.5.x\notes
A    1.5.x\README
A    1.5.x\subversion
A    1.5.x\build
A    1.5.x\tools
A    1.5.x\BUGS
A    1.5.x\contrib
A    1.5.x\configure.ac
A    1.5.x\HACKING
A    1.5.x\doc
A    1.5.x\INSTALL
A    1.5.x\COPYING
A    1.5.x\CHANGES
A    1.5.x\autogen.sh
A    1.5.x\gen-make.py
A    1.5.x\aclocal.m4
A    1.5.x\packages
U   1.5.x
Checked out revision 30435.

现在修改’subversion’目录的深度,所以可以展示整个子树:

注意:这里我们使用了–set-depth选项,也是1.5的新选项,–depth只是子命令的一个限制,而–set-depth则是update的一个活跃的操作,这里我们使用值‘infinity’,可以将1.5.x\subversion子树扩展为全部范围。

>svn up 1.5.x\subversion --set-depth infinity --quiet

几分钟之后,我们看一下1.5.x分支的mergeinfo:

>svn pg svn:mergeinfo -R 1.5.x
1.5.x\CHANGES - /branches/1.5.x-r30215/CHANGES:30236,30238,30245,30288
/branches/svn-mergeinfo-enhancements/CHANGES:30122
/trunk/CHANGES:29085-29089,29091,29094-29107,29111,29114,29117,29126-29127,29129-29133,29135-29150,29153-29164,29166-29170,29174,29176-29186,29188-29189,29193-29194,29198-29206,29208-29251,29254-29256,29261,29267-29273,29277,29280-29281,29284,29287-29303,29305-29307,29309-29343,29345-29348,29358-29379,29381-29392,29397,29399,29401,29409,29412,29414-29415,29417-29423,29425-29426,29429,29433-29434,29436-29447,29449-29466,29468-29478,29482,29484,29486-29487,29489,29491,29493,29496,29498,29508,29527-29528,29531,29533,29539-29540,29542,29544,29546,29551,29553,29556,29559,29565,29567-29569,29571-29578,29581,29583,29591,29594,29600,29603,29607,29611,29613-29614,29619,29623,29625-29626,29630-29631,29633-29634,29642,29645,29648,29650,29656,29659-29660,29663-29666,29671-29672,29677-29680,29692,29738-29739,29741-29744,29746,29751,29763,29767,29769-29770,29784,29786-29787,29797,29801,29815,29821,29824,29828,29835,29852,29854-29855,29857-29859,29868-29869,29876,29878,29883-29884,29895,29898,29900,29914,29920,29922,29925,29930,29939-29940,29942,29950,29958,29962,29965,29967-29968,29980,29986,29994-29997,30004,30009,30020,30030,30050,30053-30054,30059,30061-30062,30067,30070,30074,30086,30098,30101,30112,30117,30124,30129-30130,30137,30145,30151,30159,30161-30162,30180-30181,30185,30210,30233,30237,30239,30246,30249,30256,30278-30279,30281,30285,30297,30299,30304,30319-30321,30328,30335-30336,30340,30342,30347,30362,30368,30373,30375,30378,30380,30392,30402,30407-30409,30412
1.5.x - /trunk:29085-29089,29091,29094-29107,29111,29114,29117,29126-29127,29129-29133,29135-29150,29153-29164,29166-29170,29174,29176-29186,29188-29189,29193-29194,29198-29206,29208-29251,29254-29256,29261,29267-29273,29277,29280-29281,29284,29287-29303,29305-29307,29309-29343,29345-29348,29358-29379,29381-29392,29397,29399,29401,29409,29412,29414-29415,29417-29423,29425-29426,29429,29433-29434,29436-29447,29449-29466,29468-29478,29482,29484,29486-29487,29489,29491,29493,29496,29498,29508,29527-29528,29531,29533,29539-29540,29542,29544,29546,29551,29553,29556,29559,29565,29567-29569,29571-29578,29581,29583,29591,29594,29600,29603,29607,29611,29613-29614,29619,29623,29625-29626,29630-29631,29633-29634,29642,29645,29648,29650,29656,29659-29660,29663-29666,29671-29672,29677-29680,29692,29738-29739,29741-29744,29746,29751,29763,29767,29769-29770,29784,29786-29787,29797,29801,29815,29821,29824,29828,29835,29852,29854-29855,29857-29859,29868-29869,29876,29878,29883-29884,29895,29898,29900,29914,29920,29922,29925,29930,29939-29940,29942,29950,29958,29962,29965,29967-29968,29980,29986,29994-29997,30004,30009,30020,30030,30050,30053-30054,30059,30061-30062,30067,30070,30074,30086,30098,30101,30117,30124,30129-30130,30137,30145,30151,30159,30161-30162,30180-30181,30185,30210,30233,30237,30239,30246,30249,30256,30278-30279,30281,30285,30297,30299,30304,30319-30321,30328,30335-30336,30340,30342,30347,30362,30368,30373,30375,30378,30380,30392,30402,30407-30409,30412

1.5.x\CHANGES已经直接合并进去(例如,这里它是一个合并目标),所以它包含自己的明确mergeinfo,当合并到1.5.x时,我们把1.5.x\CHANGES作为区别于mergeinfo的子树,我们会在后面找回这个子树。

现在我们从trunk合并一些变更到工作拷贝,我们将从trunk合并两个修订版本。一个修订是工作拷贝所能展示的一部分,而另一个则是因稀疏检出未能展示的路径。

我们使用的特定修订版本是r30431和r30435:

>svn log --verbose --quiet -r30430:30435 http://svn.collab.net/repos/svn/trunk
------------------------------------------------------------------------
r30431 | epg | 2008-04-07 19:40:11 -0400 (Mon, 07 Apr 2008)
Changed paths:
M /trunk/subversion/tests/cmdline/changelist_tests.py
------------------------------------------------------------------------
r30435 | julianfoad | 2008-04-08 09:56:02 -0400 (Tue, 08 Apr 2008)
Changed paths:
A /trunk/notes/tree-conflicts/policy.txt
------------------------------------------------------------------------

好的,让我们开始合并:

注意:我们这里使用–depth infinity是为了强制合并到所有1.5.x的已展示子文件,因为前面1.5.x的浅显检出是‘immediates’,而合并操作的缺省深度是合并目标的深度,所以如果我们不指明深度为infinite,Subversion就不会合并r30435。

>svn merge --depth infinity http://svn.collab.net/repos/svn/trunk 1.5.x -r30430:30435
Skipped missing target: '1.5.x\notes\tree-conflicts\policy.txt'
Skipped missing target: '1.5.x\notes\tree-conflicts'
--- Merging r30431 through r30435 into '1.5.x':
U    1.5.x\subversion\tests\cmdline\changelist_tests.py

因为1.5.x\notes\tree-conflicts和1.5.x\notes\tree-conflicts\policy.txt都是由于稀疏工作拷贝而不会展示的文件,所以当我们尝试合并r30435时就会被忽略,而1.5.x\subversion\tests\cmdline\changelist_tests.py已经存在,所以r30431的变更就会合并进去。

让我们看一下1.5.x现在的mergeinfo:

>svn pg svn:mergeinfo 1.5.x
/trunk:29085-29089,29091,29094-29107,29111,29114,29117,29126-29127,29129-29133,29135-29150,29153-29164,29166-29170,29174,29176-29186,29188-29189,29193-29194,29198-29206,29208-29251,29254-29256,29261,29267-29273,29277,29280-29281,29284,29287-29303,29305-29307,29309-29343,29345-29348,29358-29379,29381-29392,29397,29399,29401,29409,29412,29414-29415,29417-29423,29425-29426,29429,29433-29434,29436-29447,29449-29466,29468-29478,29482,29484,29486-29487,29489,29491,29493,29496,29498,29508,29527-29528,29531,29533,29539-29540,29542,29544,29546,29551,29553,29556,29559,29565,29567-29569,29571-29578,29581,29583,29591,29594,29600,29603,29607,29611,29613-29614,29619,29623,29625-29626,29630-29631,29633-29634,29642,29645,29648,29650,29656,29659-29660,29663-29666,29671-29672,29677-29680,29692,29738-29739,29741-29744,29746,29751,29763,29767,29769-29770,29784,29786-29787,29797,29801,29815,29821,29824,29828,29835,29852,29854-29855,29857-29859,29868-29869,29876,29878,29883-29884,29895,29898,29900,29914,29920,29922,29925,29930,29939-29940,29942,29950,29958,29962,29965,29967-29968,29980,29986,29994-29997,30004,30009,30020,30030,30050,30053-30054,30059,30061-30062,30067,30070,30074,30086,30098,30101,30117,30124,30129-30130,30137,30145,30151,30159,30161-30162,30180-30181,30185,30210,30233,30237,30239,30246,30249,30256,30278-30279,30281,30285,30297,30299,30304,30319-30321,30328,30335-30336,30340,30342,30347,30362,30368,30373,30375,30378,30380,30392,30402,30407-30409,30412,30431-30435

我们可以看到/trunk的-r30430:30435已经添加到1.5.x的mergeinfo,它能够理解,它就是我们在合并时请求的范围!也要注意r30432、r30433和r30434,所有trunk中的非操作修订版本也记录到了mergeinfo,就像前面“”所讨论的“非操作 Vs  操作(Mergeinfo修订版本)”。

现在你可能会想,“很好,但是如果我们提交变更,然后其他人检出完整的1.5.x工作拷贝时会怎么样?他们notes\tree-conflicts\policy.txt的拷贝会错误的继承根的r30430:30435?而那些文件在合并中并没有发生修改!”这绝对正确,但是前面只是查看工作拷贝的mergeinfo会有一点误导,让我们在深入看一下,并检查工作拷贝的状态:

>svn st 1.5.x
M     1.5.x
M     1.5.x\www
M     1.5.x\notes
M     1.5.x\build
M     1.5.x\subversion\tests\cmdline\changelist_tests.py
M     1.5.x\contrib
M     1.5.x\tools
M     1.5.x\doc
M     1.5.x\CHANGES
M     1.5.x\packages

很显然,Subversion在合并时所做的不仅仅是它显示的,每个depth为空的项目都有了属性修改,这些修改是什么呢?让我们用svn diff看一下:

>svn diff 1.5.x\www
Property changes on: 1.5.x\www
___________________________________________________________________
Added: svn:mergeinfo
Merged /trunk/www:r29085-29089,29091,29094-29107,29111,29114,29117,29126-29127,29129-29133,29135-29150,29153-29164,29166-29170,29174,29176-29186,29188-29189,29193-29194,29198-29206,29208-29251,29254-29256,29261,29267-29273,29277,29280-29281,29284,29287-29303,29305-29307,29309-29343,29345-29348,29358-29379,29381-29392,29397,29399,29401,29409,29412,29414-29415,29417-29423,29425-29426,29429,29433-29434,29436-29447,29449-29466,29468-29478,29482,29484,29486-29487,29489,29491,29493,29496,29498,29508,29527-29528,29531,29533,29539-29540,29542,29544,29546,29551,29553,29556,29559,29565,29567-29569,29571-29578,29581,29583,29591,29594,29600,29603,29607,29611,29613-29614,29619,29623,29625-29626,29630-29631,29633-29634,29642,29645,29648,29650,29656,29659-29660,29663-29666,29671-29672,29677-29680,29692,29738-29739,29741-29744,29746,29751,29763,29767,29769-29770,29784,29786-29787,29797,29801,29815,29821,29824,29828,29835,29852,29854-29855,29857-29859,29868-29869,29876,29878,29883-29884,29895,29898,29900,29914,29920,29922,29925,29930,29939-29940,29942,29950,29958,29962,29965,29967-29968,29980,29986,29994-29997,30004,30009,30020,30030,30050,30053-30054,30059,30061-30062,30067,30070,30074,30086,30098,30101,30117,30124,30129-30130,30137,30145,30151,30159,30161-30162,30180-30181,30185,30210,30233,30237,30239,30246,30249,30256,30278-30279,30281,30285,30297,30299,30304,30319-30321,30328,30335-30336,30340,30342,30347,30362,30368,30373,30375,30378,30380,30392,30402,30407-30409,30412,30431-30435*

1.5.x\www现在有了明确mergeinfo,描述了从/trunk/www的合并。仔细查看,你会看到修订版本本身与1.5.x完全相同,只有一个例外:在本次合并添加的r30430:30435有一个‘*’后缀,这个‘*’是非继承mergeinfo范围,这个‘*’的意思是只有mergeinfo明确设置的路径才是真正已经被合并了。

我不会展示所有细节,但是现在1.5.x每个空的子项目都有自己的明确mergeinfo,这个mergeinfo是子继承1.5.x之前的mergeinfo,外加30431-30435*表明这些合并都发生在空的子项目中。

如果我们提交这个变更,另一个用户会执行使用--depth infinity检出1.5.x的分支,然后他们notes\tree-conflicts\policy.txt的拷贝就会从notes继承所有的mergeinfo,除了30431-30435,因为那些修订版本没有实际的合并到policy.txt,这是件好事情。

当Subversion不能访问一个合并目录的子树时,类似的过程也会发生,如果子树转移到其他URL或没有出现在磁盘,它是不可访问的,后者可能是浅显工作拷贝或授权限制造成。无论子树缺失的原因,Subversion都会尝试用我们看到的方法解决:

  • 尽管继续合并吧。
  • 如果任何缺失路径的父目录没有明确的mergeinfo,那么可以设置等价的祖先上的继承的mergeinfo。
  • 添加一个非继承范围来描述每个缺失路径的祖先的合并。
  • 设置或更新合并目标上描述合并的mergeinfo。

注意:因为转移造成的子目录树缺失更加复杂一点,因为转移的路径本身也有明确的mergeinfo,我将在以后的文章中完成。

现在,通常大多数用户不会合并包含缺失子树的目标,但是如果你需要,可以放心Subversion会尝试保持明确/继承某个路径上的mergeinfo,以反映合并到路径上的正确内容。

空白Mergeinfo

空白mergeinfo是使用空白字符串作为值的svn:mergeinfo,它的意思是“路径的状态好似没有任何数据已经合并进去”。空白mergeinfo通常发生在进行工作拷贝之间的移动或复制时(这个主题将会出现Submerged后面的发布中),或者当在子树上反向合并所有以前合并的时候。让我们看一下后者的一个例子:

我们当前的工作目录是一个简单的分支:

>svn ls -R .
B/
B/E/
B/E/alpha
B/E/beta
B/F/
B/lambda
C/
D/
D/G/
D/G/pi
D/G/rho
D/G/tau
D/H/
D/H/chi
D/H/omega
D/H/psi
D/gamma
mu

通过mergeinfo我们看到一些修订版本已经合并到了分支上:

>svn pl -vR .
Properties on '.':
svn:mergeinfo : /A:2-6

如果我们在分支子树上反向合并这些修订会怎样?

>svn merge %url92%/A/D/H .\D\H -r6:1
--- Reverse-merging r6 through r2 into 'D\H':
U    D\H\omega
U    D\H\psi
>svn pl -vR .
Properties on '.':
svn:mergeinfo : /A:2-6
Properties on 'D\H':
svn:mergeinfo :

空白mergeinfo就是这样产生的,如果.\D\H没有明确的空白mergeinfo,则如果继承’.'的mergeinfo信息/A/D/H:2-6就不是正确的了。

Mergeinfo省略

在每次合并的最后,Subversion会尝试“整理”所有多余的子树mergeinfo,这个整理过程称为省略(elision)。合并一旦完成,Subversion会遍历合并目标所在的工作拷贝目录树的根,如果发现一个路径包含明确的mergeinfo,而它的子树有等价的明确mergeinfo,则子树的mergeinfo可以略去。这里的“等价性”按照以下方式定义:

  • 路径PARENT有明确的mergeinfo为PXM。
  • 而PARENT的子树CHILD有明确的mergeinfo为CXM。
  • 考虑CHILD如果没有明确的mergeinfo,则CIM是CHILD继承自PARENT的mergeinfo。
  • 如果CIM和CXM恰巧相同,则CXM等价于PXM,则CXM是多余的了。

换句话说,在这里删除子树的mergeinfo是安全的,如果子树的mergeinfo等价于最近的有明确mergeinfo的祖先,则继承自祖先的mergeinfo足以描述子树的合并。它可能清楚地表明现在mergeinfo的继承和省略只是同样的事情:继承是mergeinfo从祖先下降到孩子,而省略mergeinfo则是将mergeinfo从孩子上升到祖先。

理论已经足够了,我们看一个省略(elision)的行动,在这个例子中,我们会为当前的Subversion特性分支检出工作拷贝:

>svn co http://svn.collab.net/repos/svn/branches/dont-save-plaintext-passwords-by-default no_pass_wc -q
>svn pg svn:mergeinfo -R no_pass_wc
no_pass_wc - /branches/1.5.x-r30215:30238
/branches/diff-callbacks3:29985-30687
/branches/svn-mergeinfo-enhancements:30045-30214
/trunk:30654-30731

这个分支的开发者会定时与Subversion的trunk同步,但是trunk的开发会一直进行,而使用svn mergeinfo子命令,我们会看到一些trunk上的修订版本已经准备合并:

>svn mergeinfo --show-revs eligible http://svn.collab.net/repos/svn/trunk no_pass_wc
r30735
r30736
r30738
r30741
r30743
r30745
r30746
r30747
r30748
r30749
r30750
r30751
r30753
r30754
r30756

假设我们在分支上工作,而我们是Windows开发者,我们知道需要trunk上的变更(r30754)以构建Windows上的分支,但是我们不希望将所有trunk的变更合并过来(或许我们知道会有许多冲突去解决,我们还没有准备好)。我们决定只合并我们立马就需要的一些小的修订,用来处理分支上的build.conf错误:

>svn merge http://svn.collab.net/repos/svn/trunk/build.confno_pass_wc\build.conf -c30754
--- Merging r30754 into 'no_pass_wc\build.conf':
U    no_pass_wc\build.conf

和预期的一样,diff命令显示了在build.conf上有明确的mergeinfo,而它与工作拷贝的根相比,除了r30754,没有其他区别。

>svn diff no_pass_wc
Index: no_pass_wc/build.conf
===================================================================
--- no_pass_wc/build.conf       (revision 30756)
+++ no_pass_wc/build.conf       (working copy)
<snip text diff>
Property changes on: no_pass_wc\build.conf
___________________________________________________________________
Added: svn:mergeinfo
Merged /branches/diff-callbacks3/build.conf:r29985-30687
Merged /trunk/build.conf:r30654-30731,30754
Merged /branches/1.5.x-r30215/build.conf:r30238
Merged /branches/svn-mergeinfo-enhancements/build.conf:r30045-30214

之后我们决定我们已经准备好合并trunk了,为此,暂时不管我们已经为build.conf所作的合并:

>svn merge http://svn.collab.net/repos/svn/trunk no_pass_wc
--- Merging r30732 through r30753 into 'no_pass_wc':
U    no_pass_wc\subversion\include\svn_client.h
A    no_pass_wc\subversion\include\private\svn_opt_private.h
U    no_pass_wc\subversion\include\svn_opt.h
U    no_pass_wc\subversion\libsvn_wc\status.c
U    no_pass_wc\subversion\libsvn_subr\opt.c
U    no_pass_wc\subversion\libsvn_subr\mergeinfo.c
A    no_pass_wc\subversion\libsvn_client\cmdline.c
U    no_pass_wc\subversion\libsvn_client\merge.c
U    no_pass_wc\subversion\libsvn_client\prop_commands.c
U    no_pass_wc\subversion\libsvn_client\mergeinfo.h
U    no_pass_wc\subversion\tests\libsvn_client\client-test.c
U    no_pass_wc\subversion\tests\cmdline\special_tests.py
U    no_pass_wc\subversion\tests\cmdline\basic_tests.py
U    no_pass_wc\subversion\tests\cmdline\merge_tests.py
U    no_pass_wc\subversion\tests\cmdline\depth_tests.py
U    no_pass_wc\subversion\svn\merge-cmd.c
U    no_pass_wc\subversion\svn\cl.h
U    no_pass_wc\subversion\svn\propdel-cmd.c
U    no_pass_wc\subversion\svn\checkout-cmd.c
U    no_pass_wc\subversion\svn\move-cmd.c
U    no_pass_wc\subversion\svn\mkdir-cmd.c
U    no_pass_wc\subversion\svn\cat-cmd.c
U    no_pass_wc\subversion\svn\revert-cmd.c
U    no_pass_wc\subversion\svn\diff-cmd.c
U    no_pass_wc\subversion\svn\copy-cmd.c
U    no_pass_wc\subversion\svn\mergeinfo-cmd.c
U    no_pass_wc\subversion\svn\list-cmd.c
U    no_pass_wc\subversion\svn\util.c
U    no_pass_wc\subversion\svn\blame-cmd.c
U    no_pass_wc\subversion\svn\propget-cmd.c
U    no_pass_wc\subversion\svn\changelist-cmd.c
U    no_pass_wc\subversion\svn\log-cmd.c
U    no_pass_wc\subversion\svn\update-cmd.c
U    no_pass_wc\subversion\svn\resolved-cmd.c
U    no_pass_wc\subversion\svn\cleanup-cmd.c
U    no_pass_wc\subversion\svn\commit-cmd.c
U    no_pass_wc\subversion\svn\add-cmd.c
U    no_pass_wc\subversion\svn\propset-cmd.c
U    no_pass_wc\subversion\svn\switch-cmd.c
U    no_pass_wc\subversion\svn\delete-cmd.c
U    no_pass_wc\subversion\svn\import-cmd.c
U    no_pass_wc\subversion\svn\proplist-cmd.c
U    no_pass_wc\subversion\svn\resolve-cmd.c
U    no_pass_wc\subversion\svn\export-cmd.c
U    no_pass_wc\subversion\svn\status-cmd.c
U    no_pass_wc\subversion\svn\propedit-cmd.c
U    no_pass_wc\subversion\svn\lock-cmd.c
U    no_pass_wc\subversion\svn\info-cmd.c
U    no_pass_wc\subversion\svn\unlock-cmd.c
U    no_pass_wc\subversion\libsvn_fs_fs\structure
--- Merging r30754 through r30757 into 'no_pass_wc':
U    no_pass_wc\subversion\include\svn_config.h
U    no_pass_wc\subversion\libsvn_wc\merge.c

请注意Subversion识别出了r30754已经合并到了build.conf,而且不会尝试重复合并的部分。不仅如此,而且一旦合并已经完成,Subversion就会注意到工作拷贝的根和build.conf的mergeinfo是等价的,可以省去,我们可以看到工作拷贝的状态确实如此,或者可以直接察看svn:mergeinfo属性:

>svn st no_pass_wc
M     no_pass_wc
M     no_pass_wc\build.conf
<snip>
M     no_pass_wc\subversion\svn\unlock-cmd.c
M     no_pass_wc\subversion\libsvn_fs_fs\structure
>svn pg svn:mergeinfo -R no_pass_wc
no_pass_wc - /branches/1.5.x-r30215:30238
/branches/diff-callbacks3:29985-30687
/branches/svn-mergeinfo-enhancements:30045-30214
/trunk:30654-30757

如果我们可以在完成合并后,进行省略(elision)之前停止subversion,我们会看到mergeinfo:

no_pass_wc - /branches/1.5.x-r30215:30238
/branches/diff-callbacks3:29985-30687
/branches/svn-mergeinfo-enhancements:30045-30214
/trunk:30654-30757
no_pass_wc\build.conf - /branches/1.5.x-r30215/build.conf:30238
/branches/diff-callbacks3/build.conf:29985-30687
/branches/svn-mergeinfo-enhancements/build.conf:30045-30214
/trunk/build.conf:30654-30757

因为no_pass_wc/build.conf的mergeinfo等价于no_pass_wc的,所以前者的就会被省略掉。

单纯记录的合并

1.5的一个新的merge子命令选项–record-only。使用--record-only选项的合并不会尝试合并任何东西,而只是记录,并像真正的合并发生那样省略mergeinfo。当我们需要让一个变更看起来已经合并,而实际没有发生(封杀它)时非常有用,可以让子树的mergeinfo整洁。

1.5的封杀非常简单,只需要使用–record-only来合并一个修订版本,而Subversion会让mergeinfo看起来已经进行了合并,封杀以后对其可能的合并。例如,如果重新看一下前一个例子的干净检出,我们首先合并了一个合适的修订版本,而没有使用--record-only:

>svn merge http://svn.collab.net/repos/svn/trunk no_pass_wc -c30757
--- Merging r30757 into 'no_pass_wc':
U    no_pass_wc\subversion\include\svn_config.h

和预期的一样,这个合并更新合并目标的mergeinfo:

>svn st no_pass_wc
M   no_pass_wc
M   no_pass_wc\subversion\include\svn_config.h
>svn diff no_pass_wc --depth empty
Property changes on: no_pass_wc
___________________________________________________________________
Modified: svn:mergeinfo
Merged /trunk:r30757

现在我们回退这个合并,在重新执行它,但这一次使用--record-only:

>svn revert -R no_pass_wc
Reverted 'no_pass_wc'
Reverted 'no_pass_wc\subversion\include\svn_config.h'
>svn merge
http://svn.collab.net/repos/svn/trunk no_pass_wc -c30757 –record-only

>

合并没有输出,因为没有任何合并,这是有道理的。但是mergeinfo发生了变化,就像diff显示的:

>svn diff no_pass_wc
Property changes on: no_pass_wc
___________________________________________________________________
Modified: svn:mergeinfo
Merged /trunk:r30757

如果我们尝试再次合并,使用没有--record-only选项的合并,我们可以看到封杀:

>svn merge http://svn.collab.net/repos/svn/trunk no_pass_wc -c30757

>

注意:1.5的封杀不是真正的,很明显这是实际合并的 VS 只是被封杀了。在1.5中可以检测由某些路径给定的mergeinfo的所有的源和范围是真的合并了,还是被封杀了,但这不是琐碎的,开发者社区会在以后的巴嫩考虑提供真正的封杀。

为了使用--record-only清理子树的mergeinfo,让我们看一个简单的例子,检出一个非常简单的版本库:

>svn co %SIMPLEURL% simple_wc
A    simple_wc\A
A    simple_wc\A\B
A    simple_wc\A\B\lambda
A    simple_wc\A\B\E
A    simple_wc\A\B\E\alpha
A    simple_wc\A\B\E\beta
A    simple_wc\A\B\F
A    simple_wc\A\mu
A    simple_wc\A\C
A    simple_wc\A\D
A    simple_wc\A\D\gamma
A    simple_wc\A\D\G
A    simple_wc\A\D\G\pi
A    simple_wc\A\D\G\rho
A    simple_wc\A\D\G\tau
A    simple_wc\A\D\H
A    simple_wc\A\D\H\chi
A    simple_wc\A\D\H\omega
A    simple_wc\A\D\H\psi
A    simple_wc\iota
A    simple_wc\A_branch
A    simple_wc\A_branch\B
A    simple_wc\A_branch\B\lambda
A    simple_wc\A_branch\B\E
A    simple_wc\A_branch\B\E\alpha
A    simple_wc\A_branch\B\E\beta
A    simple_wc\A_branch\B\F
A    simple_wc\A_branch\mu
A    simple_wc\A_branch\C
A    simple_wc\A_branch\D
A    simple_wc\A_branch\D\gamma
A    simple_wc\A_branch\D\G
A    simple_wc\A_branch\D\G\pi
A    simple_wc\A_branch\D\G\rho
A    simple_wc\A_branch\D\G\tau
A    simple_wc\A_branch\D\H
A    simple_wc\A_branch\D\H\chi
A    simple_wc\A_branch\D\H\omega
A    simple_wc\A_branch\D\H\psi
Checked out revision 4.

从日志中我们看到这个版本库的树在r1添加了A,在r2复制为分支,然后在r3和r4发生修改:

>svn log --verbose -r1:HEAD simple_wc
------------------------------------------------------------------------
r1 | jrandom | 2008-04-23 13:00:56 -0400 (Wed, 23 Apr 2008) | 1 line
Changed paths:
A /A
A /A/B
A /A/B/E
A /A/B/E/alpha
A /A/B/E/beta
A /A/B/F
A /A/B/lambda
A /A/C
A /A/D
A /A/D/G
A /A/D/G/pi
A /A/D/G/rho
A /A/D/G/tau
A /A/D/H
A /A/D/H/chi
A /A/D/H/omega
A /A/D/H/psi
A /A/D/gamma
A /A/mu
A /iota
Log message for revision 1.
------------------------------------------------------------------------
r2 | pburba | 2008-04-23 13:02:04 -0400 (Wed, 23 Apr 2008) | 1 line
Changed paths:

A /A_branch (from /A:1)
Make a branch from A
------------------------------------------------------------------------
r3 | pburba | 2008-04-23 13:02:44 -0400 (Wed, 23 Apr 2008) | 1 line
Changed paths:
M /A/D/H/psi
------------------------------------------------------------------------
r4 | pburba | 2008-04-23 13:03:09 -0400 (Wed, 23 Apr 2008) | 1 line
Changed paths:
M /A/B/E/beta
------------------------------------------------------------------------

首先我们直接把r3合并到A_branch\D\H\psi:

>svn merge %SIMPLEURL%/A/D/H/psi simple_wc\A_branch\D\H\psi -c3
--- Merging r3 into 'simple_wc\A_branch\D\H\psi':
U    simple_wc\A_branch\D\H\psi
>svn ci -m "merged r3 to A_branch/D/H/psi" simple_wc
Sending        simple_wc\A_branch\D\H\psi
Transmitting file data .
Committed revision 5.
>svn pl -vR merge_tests-92
>svn pl -vR simple_wc
Properties on 'simple_wc\A_branch\D\H\psi':
svn:mergeinfo : /A/D/H/psi:3

然后我们将r4合并到分支的根:

>svn merge %SIMPLEURL%/A simple_wc\A_branch -c4
--- Merging r4 into 'simple_wc\A_branch':
U    simple_wc\A_branch\B\E\beta

>svn pl -vR simple_wc
Properties on 'simple_wc\A_branch':
svn:mergeinfo : /A:4
Properties on 'simple_wc\A_branch\D\H\psi':
svn:mergeinfo : /A/D/H/psi:3-4

因为在版本库中simple_wc是修订版本5,我们必须更新工作拷贝来防止在提交时得到一个过期的错误:

>svn up simple_wc
At revision 5.
>svn ci -m "" simple_wc
Sending        simple_wc\A_branch
Sending        simple_wc\A_branch\B\E\beta
Sending        simple_wc\A_branch\D\H\psi
Transmitting file data .
Committed revision 6.

请注意合并目标的子树,A_branch\D\H\psi的mergeinfo也发生了更新:

>svn pl --verbose -R simple_wc
Properties on 'simple_wc\A_branch':
svn:mergeinfo : /A:4
Properties on 'simple_wc\A_branch\D\H\psi':
svn:mergeinfo : /A/D/H/psi:3-4

Oops,我们刚刚记住我们公司的制度是只能合并分支的根,这样才能保持明确mergeinfo的整理一直在那里发生。我们在第一次合并时忘记了这一点,现在我们刺头老板刺激我们。如何修正这个问题?好的,我们知道r3只影响了A/D/H/psi,所以如果我们为A_branch的mergeinfo添加“/A:r3”,则我们的mergeinfo在语义上与目前完全一样。当然,如果我们这样做了,A_branch\D\H\psi上的mergeinfo会等价于A_branch上的,而且可以省略。我们可以使用–record-only的合并快速实现这一步。尽管如此,首先我们还是需要更新工作拷贝(这一点是必须的原因可以看“混合修订版本工作拷贝”):

>svn up simple_wc
At revision 6.
>svn merge %SIMPLEURL%/A simple_wc\A_branch -c3 –record-only
>svn st simple_wc
M     simple_wc\A_branch
M     simple_wc\A_branch\D\H\psi
>svn pl -vR simple_wc
Properties on 'simple_wc\A_branch':
svn:mergeinfo : /A:3-4

可以看到--record-only的合并在A_branch的mergeinfo上添加了r3,然后省略了A_branch\D\H\psi的等价mergeinfo,现在是找刺头老板要升迁的时候了!

混合修订版本工作拷贝和Mergeinfo

Subversion的一个基本设计原理是尽可能的灵活,通常灵活意味着非常的复杂,过高的复杂性经常会导致混淆。这样的情况也出现在混合修订版本工作拷贝和Mergeinfo:

灵活性是:Subversion允许你合并入混合修订版本的工作拷贝。

复杂性是:Mergeinfo继承和省略依赖于跨工作拷贝的一致工作修订版本。

混淆是:当在混合修订版本的工作拷贝中,Mergeinfo继承和省略可能不会按照你预期的方式进行。

当进行“子树合并”时这个问题尤其明显(例如不是从分支的根上合并,而是根的一个子树),在下面的例子中,我们使用openCOLLABNET合并跟踪项目的一个版本库作为例子。

首先,我们检出一个实例版本库的新工作拷贝:

>svn co %URL% wc --quiet

这个版本库按照相当平常的方式组织,根目录下包含trunk、branches和tags目录…

>svn ls wc
branches/
tags/
trunk/

…以及branches下几个trunk的拷贝:

>svn ls wc\branches
a/
b/
c/

我们将要在分支c下工作,所以先看一下这里的mergeinfo:

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11
/branches/b:10-13
/trunk:5-14

现在我们在trunk作一些修改:

Doing some work…
>svn ci -m “some changes under trunk” wc
Sending        wc\trunk\jobs\index.html
Transmitting file data .
Committed revision 18.

Doing some work…
>svn ci -m “some changes under trunk” wc
Sending        wc\trunk\jobs\index.html
Transmitting file data .
Committed revision 19.

Doing some work…
>svn ci -m “some changes under trunk” wc
Sending        wc\trunk\about\index.html
Sending        wc\trunk\jobs\index.html
Transmitting file data ..
Committed revision 20.

现在开始合并,假定我们需要将r18、r19和r20的变更合并到分支c,而且只希望变更影响jobs子目录,而不想合并about子目录,所以我们直接在branches\c\jobs下合并变更:

>svn merge %URL%/trunk/jobs wc\branches\c\jobs -c18,19,20
--- Merging r18 into 'wc\branches\c\jobs':
U    wc\branches\c\jobs\index.html
--- Merging r19 into 'wc\branches\c\jobs':
G    wc\branches\c\jobs\index.html
--- Merging r20 into 'wc\branches\c\jobs':
G    wc\branches\c\jobs\index.html

这意味着branches\c\jobs现在有了自己的明确mergeinfo,区别于branches\c的内容:

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11
/branches/b:10-13
/trunk:5-14
wc\branches\c\jobs - /branches/a/jobs:3-11
/branches/b/jobs:10-13
/trunk/jobs:5-14,18-20

一旦我提交了合并,请注意我们现在有了混合修订版本的工作拷贝:

>svn ci -m "Merged some changes from trunk to a subtree of c branch" wc
Sending        wc\branches\c\jobs
Sending        wc\branches\c\jobs\index.html
Transmitting file data .
Committed revision 25.
>svnversion wc\branches\c
17:25

修订版本25?可以看到在初始化检出和提交这段时间,其他开发者所做的变更产生了新的r21-r24。

之后,我们需要从r20合并一些会改变branches\c的about子目录的变更,还记得我们老板对于在分支的根上合并的咆哮吗,我们决定在分支的根上合并r20,我们也决定重新合并r18和r19,因为知道合并跟踪逻辑一定能防止重复合并,而且能够省略branches\c\jobs上的mergeinfo:

>svn merge %URL%/trunk wc\branches\c -c18,19,20
--- Merging r20 into 'wc\branches\c':
U    wc\branches\c\about\index.html

在提交之前我们检查branches\c的mergeinfo。什么?branches\c\jobs\index.html还有mergeinfo,它应该和branches\c有完全等价的mergeinfo?为什么branches\c\jobs的mergeinfo不因为branches\c而省略?

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11
/branches/b:10-13
/trunk:5-14,18-20
wc\branches\c\jobs - /branches/a/jobs:3-11
/branches/b/jobs:10-13
/trunk/jobs:5-14,18-20

问题取决于我们的混合修订版本工作拷贝:

>svn st -v wc\branches\c
M              17       16 cuser        wc\branches\c
17       15 merger       wc\branches\c\products
17        2 user         wc\branches\c\products\little.html
17       15 merger       wc\branches\c\products\medium.html
17        2 user         wc\branches\c\products\big.html
17       15 merger       wc\branches\c\products\roadmap.html
17       15 merger       wc\branches\c\products\index.html
17       15 merger       wc\branches\c\about
M              17       15 merger       wc\branches\c\about\index.html
17       15 merger       wc\branches\c\index.html
17       15 merger       wc\branches\c\news
17       15 merger       wc\branches\c\news\index.html
17        2 user         wc\branches\c\support
17        2 user         wc\branches\c\support\index.html
25       25 pburba       wc\branches\c\jobs
25       25 pburba       wc\branches\c\jobs\index.html

Notice that branches\c\jobs is at working revision 25, while the rest of branches\c is at r17.  Subversions elision logic won’t try to elide the mergeinfo on branches\c\jobs up the working copy to branches\c because the two are at different working revisions.  Why not?  Because there is no way of knowing if the mergeinfo found on branches\c@17 is the same as .  It’s possible the changes our colleague made in r21-r24 changed to mergeinfo of branches\c.  If this were the case and we try to commit this merge we’d get an out of date error and have to update the working copy before committing.

请注意branches\c\jobs的工作修订版本是25,而branches\c的余下部分是r17。Subversion的省略逻辑不会因为branches\c而尝试省略branches\c\jobs的mergeinfo,这是因为不同的工作修订版本?为什么不呢?因为没有方法知道branches\c@17发现的mergeinfo是否和branches\c@25相同。很有可能我们同事在r21-r25改变了branches\c的mergeinfo。如果存在这个情况,当我们提交的时候我们会得到过期的错误,必须在提交前更新工作拷贝。

我们可以使用–show-updates选项检查r21-r24是否影响了分支branches\c 。

>svn st --show-updates --verbose wc\branches\c
17        2 user         wc\branches\c\products\little.html
17       15 merger       wc\branches\c\products\medium.html
17        2 user         wc\branches\c\products\big.html
17       15 merger       wc\branches\c\products\roadmap.html
17       15 merger       wc\branches\c\products\index.html
17       15 merger       wc\branches\c\products
M              17       15 merger       wc\branches\c\about\index.html
17       15 merger       wc\branches\c\about
17       15 merger       wc\branches\c\index.html
17       15 merger       wc\branches\c\news\index.html
17       15 merger       wc\branches\c\news
17        2 user         wc\branches\c\support\index.html
17        2 user         wc\branches\c\support
25       25 pburba       wc\branches\c\jobs\index.html
25       25 pburba       wc\branches\c\jobs
M              17       16 cuser        wc\branches\c
Status against revision:     25

不是,没有人修改了branches\c,因为没有看到在第8列有代表过期的‘*’号,所以我们决定更新branches\c:

>svn up wc\branches\c
At revision 25.

现在分支是一致的工作修订版本(对于未提交的合并都还有本地修改):

>svnversion wc\branches\c
25M

我们还是希望消除branches/c/jobs上多余的明确mergeinfo,所以我们重新合并,依赖于合并跟踪逻辑能够避免重复合并(我们可以这里也使用--record-only,在这个情况下没有有效的区别):

>svn merge %URL%/trunk wc\branches\c -c18,19,20

>

好的,没有发生合并,但是足够确定,mergeinfo被省略,现在工作拷贝是一致的修订版本,已经发生:

>svn st wc\branches\c -v
M              25       25 pburba       wc\branches\c
25       15 merger       wc\branches\c\products
25        2 user         wc\branches\c\products\little.html
25       15 merger       wc\branches\c\products\medium.html
25        2 user         wc\branches\c\products\big.html
25       15 merger       wc\branches\c\products\roadmap.html
25       15 merger       wc\branches\c\products\index.html
25       15 merger       wc\branches\c\about
M              25       15 merger       wc\branches\c\about\index.html
25       15 merger       wc\branches\c\index.html
25       15 merger       wc\branches\c\news
25       15 merger       wc\branches\c\news\index.html
25        2 user         wc\branches\c\support
25        2 user         wc\branches\c\support\index.html
M              25       25 pburba       wc\branches\c\jobs
25       25 pburba       wc\branches\c\jobs\index.html

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11
/branches/b:10-13
/trunk:5-14,18-20

这个例子描述了使用mergeinfo省略的问题,就像前面提及的省略和继承是完全相同的事情,所以你可以在混合修订版本工作拷贝看到和包含继承的工作拷贝完全相同的问题。在r26完成前面的合并提交之后,假设我们有如下的混合修订版本工作拷贝:

>svn st -v wc\branches\c
26       26 pburba       wc\branches\c
26       15 merger       wc\branches\c\products
26        2 user         wc\branches\c\products\little.html
26       15 merger       wc\branches\c\products\medium.html
26        2 user         wc\branches\c\products\big.html
26       15 merger       wc\branches\c\products\roadmap.html
26       15 merger       wc\branches\c\products\index.html
25       15 merger       wc\branches\c\about
26       26 pburba       wc\branches\c\about\index.html
26       15 merger       wc\branches\c\index.html
26       15 merger       wc\branches\c\news
26       15 merger       wc\branches\c\news\index.html
26        2 user         wc\branches\c\support
26        2 user         wc\branches\c\support\index.html
26       26 pburba       wc\branches\c\jobs
26       25 pburba       wc\branches\c\jobs\index.html

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11
/branches/b:10-13
/trunk:2,5-14,18-20

如果我们尝试直接将17:20合并到branches\c\about,Subversion会指出branches\c\about所包含的明确mergeinfo,从而可以避免任何重复合并。不幸的是,很难从branches\c继承明确mergeinfo,因为有不同的工作修订版本。所以Subversion会询问服务器,获取branches\c\about@25的明确或继承mergeinfo。在那个修订版本没有明确mergeinfo,但是有从branches\c@25得到的继承mergeinfo ,但是其中没有包含r18-20,毕竟在r26之前还没有提交!所以Subversion认为r18-20还没有应用到branches/c/about,并重复了合并:

>svn merge %URL%/trunk/about wc\branches\c\about -c18,19,20
--- Merging r20 into 'wc\branches\c\about':
U    wc\branches\c\about\index.html

在这个情况下,发生的是branches\c\about获得了明确mergeinfo,尽管从重复合并可以轻易得到一个冲突:

>svn st wc\branches\c
M     wc\branches\c\about
>svn diff wc\branches\c
Property changes on: wc\branches\c\about
___________________________________________________________________
Added: svn:mergeinfo
Merged /branches/b/about:r10-13
Merged /trunk/about:r2,5-14,18-20
Merged /branches/a/about:r3-11

如果我们回退这个合并,并更新合并目标为统一的工作拷贝修订版本,然后再重新合并,我们会看到什么事都没有发生:

>svn revert -R wc
Reverted 'wc\branches\c\about'
>svn up wc
At revision 26.
>svn merge %URL%/trunk/about wc\branches\c\about -c18,19,20
>svn st wc
>

这是因为branches\c\about现在能继承来在branches\c的明确mergeinfo,其中包含了r18-20。看到那些修订版本说明它们已经合并到了branches\c\about,Subversion不会再尝试合并。

诚然,这些例子都有一点不够自然,而你可能永远都不会遇到这些问题,但是如果你看到关于mergeinfo的神奇行为,检查一下混合修订版本工作拷贝是没有错的。

作者也很不好意思的承认曾经为混合修订版本工作拷贝和mergeinfo继承/省略的交织感到困惑,在理解事情的原理之前,甚至要在Subversion的问题跟踪提交一个新的问题…

临别思考

嗨,说了这么多!真希望你可以很好的理解mergeinfo的工作原理,特别是一些非典型用例。但是如果你希望避免整个复杂性?根据你的需要,这不是很难。如果这些与你的开发过程兼容,下面是保证你的mergeinfo(和你的合并)尽可能直白的一些规则:

  • 对于特性分支(那些从trunk拷贝到branch,然后最终要合并回到trunk)使用Subversion手册中的:保持你的分支与trunk的同步,当将分支合并会trunk时使用--reintegrate。
  • 对于发布分支,合并只是会发生在分支的根上,不要做子树合并。这可以保持你的明确mergeinfo的统一。
  • 尽可能不要合并混合修订版本工作拷贝,在合并前进行更新可以防止许多混淆。
  • 不要使用svn propset或svn propedit“手工编辑”svn:mergeinfo,svn命令行客户端会检查你使用子命令修改mergeinfo的格式,但是不会检查语义的正确性。很容易让你的mergeinfo以‘,’结尾,或者让你的mergeinfo引用根本不存在的路径。还是使用svn merge的--record-only选项,它足够解决大多数问题,不太可能给你带来麻烦。
  • 不要合并有转移子树的目标。
  • 不要从没有完全授权的源合并到没有完全授权的目标。
  • 不要合并到非无限(non-infinite)深度(empty‘、’files‘或’immediates‘)。

是的,还有许多不要做的,这里有一些:不要在需要做的时候犹豫,就像我开始所说的,Subversion一直会尝试为你的mergeinfo“做正确的事”,也希望本文也能帮助你同样的做事情。

转载请注明本文地址:http://www.subversion.org.cn/submerged/?p=70

Make a comment