最近接手一个非常老的项目,因为使用SVN进行控制,而现在大部分都已经使用Git进行版本控制,所以就想迁移到Git,Git的优点自然是不用在这里说了,必经是当前公认最好用的。

因为项目比较老,代码比较多,提交多达3w多次,同时svn还有几十个分支和标签,这些历史记录丢失将对今后追溯debug造成很多麻烦,所以关键是如何才能保留所有的这些提交记录、分支、标签。

svn转换git

转换仓库

1. 提取开发者信息

为了实现正确的数据迁移,我们要对开发者信息,即提交作者的信息做一个映射,这就需要先生成一个用户对应关系的文件,格式如下:

JefferyWang = JefferyWang <jefferywang@example.com>

为了能够快速从svn内提取作者信息,可以使用下面的脚本,来快速提取在svn内有过提交的用户名,然后再做映射的处理,生成上面格式的文件即可:

svn log -q | awk -F '|' '/^r/ {sub("^ ", "", $2); sub(" $", "", $2); print $2}' | sort -u

当然如果你们所有作者的邮箱后缀都是一样的,可以使用下面这段脚本:

svn log -q | awk -F '|' '/^r/ {sub("^ ", "", $2); sub(" $", "", $2); print $2" = "$2" <"$2"@example.com>"}' | sort -u > authors.txt

2. 仓库转换

标准的svn文件布局

标准的svn文件布局

如果你的svn仓库是标准的目录结构,即只包含/trunk, /branches, tags 几个目录的结构,那么可以直接在运行命令的时候加上参数--stdlayout

命令格式为git svn clone --stdlayout --authors-file=authors.txt <svn-repo>/<project> <git-repo-name>,下面是一个示例:

# git v1.x
git svn clone --stdlayout --authors-file=authors.txt http://svn.example.com/test_rep/demo_proj demo

# git v2.x
git svn clone --prefix="" --stdlayout --authors-file=authors.txt http://svn.example.com/test_rep/demo_proj demo

非标准的svn文件布局

非标准的svn文件布局

针对非标准的目录结构,我们分别做显式指定参数--trunk, --branches, --tags

# git v1.x
git svn clone --trunk=/trunk --branches=/branches --branches=/bugfixes --tags=/tags --authors-file=authors.txt http://svn.example.com/test_rep/demo_proj demo

# git v2.x
git svn clone --prefix="" --trunk=/trunk --branches=/branches --branches=/bugfixes --tags=/tags --authors-file=authors.txt http://svn.example.com/test_rep/demo_proj demo

大仓库的转换策略

有些svn仓库可能非常大,而转换过程是个非常漫长的过程,这个也取决于机器性能。因此,如果你不在乎老的一些历史记录的话,那么你可以只保留部分提交历史,来加速转换的过程。

命令格式:git svn clone -r${REVNUMBER}:HEAD --stdlayout --authors-file=authors.txt <svn-repo>/<project> <git-repo-name>

git svn clone -r52733:HEAD --stdlayout --authors-file=authors.txt https://svn.waterstrong.com/demo demo

3. 仓库清理

这里查看我们的仓库,就发现转换工作基本已经完毕了,如果你只关注trunk和master分支,那么可以不用在乎这一部分,直接跳到下一节即可。这一步主要是将分支和标签进行本地化。

git svn clone操作并不会将svn的分支和标签导入到新的git仓库里面,并且在本地分支中也找不到svn的分支和标签。转换后,其实是把svn的分支和标签导入为git的远程分支和标签了,如下面示意图所示:

仓库转换结果

这个策略主要是为了svn和git双向同步服务的,但是我们切换为git后,应该大部分都会禁止在svn再进行提交,所以我们就需要对这些进行一下清理。

清理后的结果

第一种方法:

Atlassian提供了一个工具svn-migration-scripts.jar,可以进行清理。

下载地址:

svn-migration-scripts.jar 下载

使用方法:

java -Dfile.encoding=utf-8 -jar ~/svn-migration-scripts.jar clean-git
--force

第二种方法:

只需要执行两个命令:

# 标签本地化
for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git tag ${t/tags\//} $t && git branch -D -r $t; done

# 分支本地化
for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done

当然,如果你使用git v2.x版本,你可能通过网上查询的资料,在执行git svn clone的时候没有加参数--prefix="",那么执行之后会在远程的分支名称前面添加origin/,直接按照上面的脚本处理会有问题,你可以按照下面的命令进行处理

# 标签本地化
for t in $(git for-each-ref --format='%(refname:short)' refs/remotes | grep "origin/tags"); do git tag ${t/origin\/tags\//} $t && git branch -D -r $t; done

# 分支本地化
for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch ${b/origin\//} refs/remotes/$b && git branch -D -r $b; done

4. 收尾工作

添加远程仓库地址

git remote add origin JefferyWang@github.com:test/demo.git

将本地分支和标签推送到远程仓库

git push origin --all

git push origin --tags

参考资料