Windows下使用PowerShell一键SVN迁移至Git脚本
目录
Git和SVN区别
- SVN是集中式的,只有中央仓库。Git是分布式的,有本地和远程两仓库
- SVN是按照文件存储,允许比较混乱的目录的目录结构,如果迁移前,不整理目录,会出现严重的文件混乱
- SVN的分支是一个目录
- Git有本地工作区、暂存区、远程仓库。因此代码提交到中央有区别
SVN先add提交到暂存,commit后直接到远程仓库
Git先add提交到暂存,commit后到本地仓库,最后push到远程仓库
Git的服务端与客户端介绍
- 其服务端有多个选择,类似于Git是一种规范,而服务端有多个不同实现。只要支持的都能用。例如Github、Gitlab、Gitblit、Onedev等
- 客户端和服务端同理。Git、TortoiseGit(图形)等
Git客户端与SVN客户端选择
本教程使用的是Git作为Git客户端,TortoiseSVN作为SVN客户端。
迁移前的准备工作
- 先在svn规整相关目录
svn路径 | git路径 |
---|---|
trunk | master |
tags | tags |
branches | branches |
例如在svn的branches有文件夹如下src、test,文件README.md,且历史提交记录里有publish文件夹,那么迁移到git后会生成src、test、publish等分支,但README.md文件会丢失
- 准备users.txt文件,该文件是svn和git账户的映射关系。有了这个文件以后,会将svn的提交记录同步到git上,否则失败。如果是Visual SVN用户,请务必添加
VisualSVN Server
的相应映射。以下users.txt文件格式示例:
user=user<user@mail.com>
test=test<test@mail.com>
VisualSVN Server=admin<admin@mail.com>
- 上述文件也可通过命令生成,需要进入svn项目目录生成,且生成的只有此项目的users.txt文件
# 这是powershell命令
svn.exe log --quiet | ? { $_ -notlike '-*' } | % { "{0} = {0} <{0}>" -f ($_ -split ' \| ')[1] } | Select-Object -Unique | Sort-Object | Out-File 'users.txt' -Encoding utf8
通过生成的users.txt的文件只有此项目的。本迁移脚本需要所有用户的映射关系。迁移过程中,如遭遇xxxx not defined in ./users.txt file
就是因为svn用户没在users.txt
文件中定义,添加上即可
迁移脚本
- 本脚本撰写时,svn服务器是被反代理出外网的,为了迁移快一点。会在本地局域网完成迁移以后,再改为远端地址
- 如果迁移时遭遇如
RPC failed; HTTP 413 curl 22 The requested URL returned error: 413
,请检查自己的代理软件设置,比如nginx,需要设置client_max_body_size
,设置最大请求长度即可。
本脚本基于powershell,且users.txt要求所有用户(包括已被删除的)映射关系,否则报错
# Git本地仓库目录
$gitPath = "D:\Git"
# git 本地Http路径
$gitLocalHttpUrl = "http://192.168.0.2:6610"
# git 远端Http路径,如果全是本地,可以改为和git本地Http地址一致即可
$gitRemoteHttpUrl = "https://git.youurl.com:6547"
# svn 本地Http路径
$svnLocalHttpUrl = "http://192.168.0.2:4782/svn"
# 项目名数组放这里
$data = @(
[pscustomobject]@{
# 项目名称
name = "Project1";
},
[pscustomobject]@{
name = "Project2";
}
)
# 如果Git路径不存在,那么创建,如果路径已存在,不影响命令继续执行
if (![System.IO.Directory]::Exists($gitPath)) {
md $gitPath
}
function migrate {
param (
[string]$projectName
)
# 项目路径
$projectPath = "$($gitPath)\$($projectName)"
# 推送分支的临时文件夹,迁移完成后可删除
$projectBarePath = "$($gitPath)\$($projectName).bare"
cd $gitPath
git svn clone $svnLocalHttpUrl/$projectName --prefix=svn/ --no-metadata --trunk=/trunk --branches=/branches --tags=/tags --authors-file "users.txt" $projectPath
# 添加.ignore相关文件
cd $projectPath
git svn show-ignore --id=svn/trunk > .gitignore
git add .gitignore
git commit -m 'Convert svn:ignore properties to .gitignore.'
# 准备迁移分支
git init --bare $projectBarePath
cd $projectBarePath
git symbolic-ref HEAD refs/heads/svn/trunk
# 推送分支到空目录
cd $projectPath
git remote add bare $projectBarePath
git config remote.bare.push refs/remotes/*:refs/heads/*
git push bare
# 更正分支名
cd $projectBarePath
git branch -m svn/trunk master
# 将svn标记迁移到git标记 这句代码要求严格的文件结构,如果不符合规范,会漏掉branches和tags
# git for-each-ref --format='%(refname)' refs/heads/svn/tags | % { $_.Replace('refs/heads/svn/tags/','') } | % { git tag $_ "refs/heads/svn/tags/$_"; git branch -D "svn/tags/$_" }
# 将所有svn分支创建为适当的git分支。这条命令会将所有branches、tags都创建为对应的git的branches,需要后期手动处理相关
git for-each-ref --format='%(refname)' refs/remotes | % { $_.Replace('refs/remotes/', '') } | % { git branch "$_" "refs/remotes/$_"; git branch -r -d "$_"; }
# 推送文件到远端
git remote add origin $gitLocalHttpUrl/$projectName
git push origin --all
# 如果路径名不等,那么设置最新地址
if (!"$($gitLocalHttpUrl)/$($projectName)".Equals("$($gitRemoteHttpUrl)/$($projectName)")) {
git remote set-url origin $gitRemoteHttpUrl/$projectName
}
# 将.ignore单独推送,之前执行的命令会漏掉这个文件
cd $projectPath
git remote add origin $gitRemoteHttpUrl/$projectName
git push origin --all
}
foreach ($curr in $data) {
try {
Write-Host 开始迁移 $curr.name 数据
migrate -projectName $curr.name
}
catch {
Write-Host "发生异常:$_"
break
}
}