svn迁移git小记

迁移本身并不复杂,甚至直接拷贝文件后新建 git 目录也是可行的。若要保留提交历史则可以参考了解如何从 Subversion (SVN) 迁移到 Git(包括历史记录)和官方的迁移到 Git

现在发行的 windows 版 git 工具已经自带 svn 模块,但实际操作时因为各参考命令的环境不同,需要在 powershell 和 git bash 环境不断来回切换,这一点就比较费力。

对于迁移而言,首先需要一个 svn 用户和 git 用户的转换关系。在powershell 中执行以下语句导出 svn 用户列表,但 Out-File 默认格式是 utf8,git 不支持读取带 BOM 的文件,所以需要指定格式为 ASCII。

svn.exe log --quiet | ? { $_ -notlike '-*' } | % { "{0} = {0} <{0}>" -f ($_ -split ' \| ')[1] } | Select-Object -Unique | Sort-Object | Out-File 'authors-transform.txt' -Encoding ASCII

git svn 的命令可以查看官方参考文档,将 svn 项目转换为 git 项目的基本命令是

git svn clone ["SVN repo URL"] --prefix=svn/ --no-metadata --authors-file "authors-transform.txt" -r 10000:HEAD

其中 –no-metadata 的解释可以参考这个回答,应该是原始的 svn 提交信息。

-r 比较实用,能指定版本范围,也可以单条,还能提高速度(默认从0开始,到本项目第一次提交相差数万次提交),还能配合 git svn fetch 跳过某些提交。

–ignore-paths 可以通过正则过滤不需要拉取的路径,比如 –ignore-paths=”(Branch|Doc|Tool)”,但这个不一定能成功过滤。

对于有分支的项目,需要使用 –stdlayout,这个等价于 –trunk=/trunk –branches=/branches –tags=/tags。但对于不标准的目录结构来说就比较复杂了。

./project
├── Branch
│   ├── PROD
│   │   └── SolutionA
│   │       ├── SolutionA.Service
│   │       └── SolutionA.sln
│   └── feature_1
│       └── SolutionA
│           ├── SolutionA.Service
│           └── SolutionA.sln
├── Doc
├── SolutionA
│   ├── SolutionA.Service
│   └── SolutionA.sln
├── SolutionB
│   └── ...
└── Tool

对于这样一个项目,实际执行的命令是

# 签出solutionA
git svn clone http://xxx/project --prefix=svn/ --trunk=SolutionA --branches=Branch/PROD/SolutionA --authors-file "e:\authors-transform.txt" -r 30000:HEAD
# 签出solutionB
git svn clone http://xxx/project --prefix=svn/ --authors-file "e:\authors-transform.txt"  --ignore-paths="(Branch|Doc|Tool)" -r 30000:HEAD
# 签出Doc
git svn clone http://xxx/project/Doc --prefix=svn/ --authors-file "e:\authors-transform.txt" -r 30000:HEAD
# 签出Tool
git svn clone http://xxx/project/Tool --prefix=svn/ --authors-file "e:\authors-transform.txt" -r 30000:HEAD

其中对于 SolutionA,还需要把 svn 分支创建成 git 分支,以及进行改名。

git for-each-ref --format='%(refname)' refs/remotes | % { $_.Replace('refs/remotes/','') } | % { git branch "$_" "refs/remotes/$_"; git branch -r -d "$_"; }
git branch -m svn/xxx PROD
git branch -m svn/trunk TEST

对于 SolutionB,需要删除不需要的一些目录,这里需要执行 filter-branch。

git filter-branch -f --index-filter 'git rm -rf --cached --ignore-unmatch Tool' HEAD
git filter-branch -f --index-filter 'git rm -rf --cached --ignore-unmatch Doc' HEAD
git filter-branch -f --index-filter 'git rm -rf --cached --ignore-unmatch SolutionA' HEAD
git filter-branch -f --index-filter 'git rm -rf --cached --ignore-unmatch Tool' HEAD
# 删除空的提交历史
git filter-branch -f --prune-empty
# 删除缓存
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
# git 垃圾回收
git gc --prune=now

对于 Doc 和 Tool,则不需要处理了。

这里还有另一种情况,在 svn 中项目目录被误删除,但又被原地恢复,在 svn log 中可以看到删除前的内容,但 svn blame 则无法跟踪。这种情况可以通过迁移到 git 时可以进行接头手术接上,因为 git 每次提交是一个 diff,内容相同的情况下,应用diff 后结果也是相同的。

# 签出删除前的部分
git svn clone -r 1:35000 "http://xxx/project" --prefix=svn/ --no-metadata --authors-file "e:\authors-transform.txt"
# 签出删除后的部分
git svn clone -r 36000:HEAD "http://xxx/project" --prefix=svn/ --no-metadata --authors-file "e:\authors-transform.txt"

# 新建一个空项目,并提交一个更早的提交
git init
git commit --allow-empty -m"Initial dummy commit" --date="2017-01-04 00:00:00 0800"
# 添加刚才拉取转换的项目为远程源
git remote add -f old E:\project_old
git remote add -f new E:\project_new
# 进行接头手术
git merge old/master --allow-unrelated-histories
git branch head_old
git branch head_new
git checkout head_new
git rebase head_new new/master --committer-date-is-author-date --reapply-cherry-picks --strategy-option theirs 
git checkout master
git branch head_afterjoin e783830
git merge head_afterjoin --allow-unrelated-histories

这里通过 git rebase 强制连接2个不同的项目,在实际应用时,rebase 也能做一些强制处理,但要谨慎使用。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注