使用svn-all-fast-export将SVN转为Git

最近将某项目代码库从SVN迁移到了Git。网上有介绍使用git svn来进行迁移的,我试过最终因git svn clone无法正常执行完成失败了。我也注意到有一款采用Ruby开发的svn2git工具,原理应该是基于git svn进行二次开发。因为之前用git svn方法没成功并且每次尝试消耗的时间过长,就没有再试这个工具。

我最终采用了svn-all-fast-export工具。这个工具基于libsvn-dev开发,是直接对SVN库目录进行操作。svn-all-fast-export需要Linux环境运行,我采用VirtualBox+Debian;Synaptic新立得包管理器已包含这个软件,直接安装即可。

先建个目录svn2git,保存SVN库目录(Svn_CQClient和Svn_CQServer)及对应的转换规则配置文件(cqclient.rules和cqserver.rules)。目录结构如下:

~/svn2git

|-Svn_CQClient/

|-Svn_CQServer/

|-cqclient.rules

`-cqserver.rules

在制作rules文件之前,需要先梳理一下SVN库的内容并规划将来的Git库。以CQServer库为例说明,先建立一个表格,有几列:Git、branches、based on branch、tags和labels(因为CQServer最早期用VSS做版本管理,所以VSS转SVN时保留了labels;如果你的SVN库没有/labels就不需要labels列)。

1. 规划Git库名和分支名

打开TortoiseSVN Repository Browser,逐个查看/trunk和/branch目录下每个项目的“Show log”输出(设置“Stop on copy/rename”标记,点“Show All”显示完整日志),看看该项目最早的代码是从其他项目派生的,还是直接添加入库的,将项目路径写入branches列,将它的源项目路径(如有)写入base on branch。还可以使用TortoiseSVN的Revision Graph来查看,已删除的项目路径也要进行处理并特别标注。

不同源的项目之间无法跨项目应用merge操作,如果放在同一个Git库会产生根节点不同的分支树。所以不同源项目一般应归属不同的Git库。

同源项目可以在同一个Git库中作为不同的分支进行管理。如果新项目在建立时就是为了替换某个同源旧项目,或是由旧项目改名而来,有明显的承接关系,旧项目后续也没有commit记录,那么分支名可以相同。

有时项目是从它的父目录或子目录复制过来的,比如某个项目之前在/trunk/保存了开发版本,后来把项目整个移到/trunk/Develop/目录下,然后删除了原父目录下的文件。这样的话可以将父目录/trunk/和子目录/trunk/Develop/规划为Git中的不同分支,并利用max revision规则(见下文)保留/trunk/删除版本文件之前的样子。

为每一组同源项目分配一个Git库名(第一列“Git”)。为每个branches项分配一个Git分支名。

2. 规划Git库标签

和branches类似,但是不需要确定分支名,可以使用通配符来标记。例如:

Git tags labels
CQServer CQServer/([^/]+)/
CQServerRelease/([^/]+)/
([^/]+)/(Develop|Release)/
([^/]+)/
CQModule CQModule/([^/]+)/ ([^/]+)/CQModule/
CQServer-x64 CO3Server/([^/]+)/
CO3ServerCn/([^/]+)/
CQServerIpad/([^/]+)/
CQServerRelease-x64/([^/]+)/
CQServer-x64/([^/]+)/
 

这里只是粗略地分一下,实际转换时可能会发现某些tag跨不同Git库导致错误的情况,就要编写更细致的规则把这些tag分配到不同Git库或者直接忽略掉对应的commit记录。

3. 编写建库规则

转换配置文件的例子可参考https://github.com/svn-all-fast-export/svn2git/tree/master/samples

先根据Git库规划写创建Git库的规则,例:

create repository CQServer.git

end repository

把”CQServer.git”依次替换为其他Git库名(第一列“Git”)。

4. 编写分支规则

根据第二列“branches”和取好的分支名转换为Git库中的分支,例如:

match /trunk/Develop/

    repository CQServer.git

    branch master

end match

将红字部分依次替换为之前规划好的配置。注意:目录名末尾必须加”/”字符。

分支规则的书写顺序很重要。转换程序会逐条处理SVN库的commit记录,将commit记录中每个文件路径按match规则(”match”之后的正则表达式,采用Qt4 QRegExp)的顺序逐条匹配。如果匹配成功就按照match/end match之间的配置进行处理(repository指定接收的Git库,branch指定接收或新建的分支名)。如果匹配失败则继续尝试下一条match规则。

这相当于将SVN的commit记录的文件修改按照match规则去修改对应的Git库和分支,并进行commit和push。

如果match匹配全部失败,转换程序会报告错误并退出,因此全部匹配失败是不允许的。

如果确实打算忽略某些commit记录,可以在match/end match之间不写任何语句。例如,在所有其他/trunk/的match规则的后面加上:

match /trunk/

end match

就可以忽略之前未被匹配到的/trunk/下的commit记录。

5. 编写labels和tags规则

例如:

match /labels/([^/]+)/(Develop|Release)/

    repository CQServer.git

    branch refs/tags/\1

    annotated true

end match

根据规划表格中的“tags”和“labels”列编写,替换上面红字的部分。“\1”和“\2”表示match规则中被圆括号对包围的那部分内容,第一对圆括号内的内容对应\1,第二对对应\2。这个写法也适用于之前处理/trunk/和/branches/的branch语句。

注意:如果tag不是从SVN库中某个版本直接导出的,而是从外部复制版本目录到/tags/下(类似于新建一个项目),这样的tag无法在Git库中溯源,只能忽略掉。例如:

match /tags/CQServerRelease/

    min revision 1930

    max revision 3220

end match

这样的异常tag记录有一个特征:TortoiseSVN中显示commit message的字体是黑色,与正常tag的蓝色字体不同。

“min revision”和“max revision”除了一般的match匹配规则还针对SVN的revision编号进行匹配,如果匹配成功才处理,匹配不成功则继续尝试下一条match规则。“min revision”和“max revision”可以同时出现也可以单独出现。min revision的编号不能大于max revision,否则可能会出现非预期的结果却不报错。这个办法也可以用于处理SVN记录中已删除的项目路径。

6. 逐条检查SVN commit记录中所有包含”Deleted”的记录

主要注意删除或重命名项目分支目录或版本标签的情况,这类情况一般需要在转换配置文件中进行特殊处理。

在svn-all-fast-export处理后期会对tags进行Finalising处理,即使tag创建后被删除。如果tag删除之后没有被重新创建,Finalising就会异常报错,因此对于这样的tag要编写对应的match规则进行处理(比如忽略删除tag的记录,或同时忽略tag创建和删除记录)。Tag改名也有类似问题,改名以后相当于旧名被删除了,但是转换工具仍会试图对旧名进行处理。

7. 用转换命令处理SVN库

export SVN2GITDIR=~/svn2git; cd $SVN2GITDIR; svn-all-fast-export --identity-domain nd --rules cqserver.rules Svn_CQServer 1>out.log 2>err.log

要看看err.log和log-*.git的日志信息是否正常。如果程序正常结束,log-*.git日志末尾都应该有一些程序运行统计信息,非正常结束的情况部分日志文件没有这部分信息。

有些特别大的SVN库可能会出错,可以加“--commit-interval=1”参数看看能否解决。

8. 后期检查

对比TortoiseGit和TortoiseSVN版本分支图(Revision Graph),检查分支版本文件及日志,看看是否有异常或非预期的结果。如果转换结果有错,可用以下命令删除Git库和转换日志,修改转换配置文件后再重新转换:

export SVN2GITDIR=~/svn2git; cd $SVN2GITDIR; rm -rf *.git *.log

猜你喜欢

转载自blog.csdn.net/feiyunw/article/details/78404698