Skip to content

Instantly share code, notes, and snippets.

@simonbondo
Created November 26, 2019 09:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simonbondo/3696e954e5f0660a19dbbb73e5e73682 to your computer and use it in GitHub Desktop.
Save simonbondo/3696e954e5f0660a19dbbb73e5e73682 to your computer and use it in GitHub Desktop.
Converts a collection of projects from a SVN mono-repo to separate git repos.
# Where svn.exe can be found
$svnPath = 'C:\Data\svn'
# Where the resulting git repositories should be created
$gitRepoPath = 'C:\Data\svn\git'
# The branch that should become the default branch in the git repository
$defaultBranch = 'trunk'
$removeTempRepository = $true
# The URL to the SVN server (can be file:///)
$svnRepoUrl = 'file:///c:\Data\svn\SVNRepo'
# The relative paths of each project inside the SVN repository, where 'trunk', 'branches' and 'tags' folders can be found
# Note: If the project doesn't have a trunk folder, the script will not be able to clone the project.
$projectSvnPaths = 'Projects/ProjectA',
'Projects/ProjectB',
'Projects/ProjectC'
# git-svn uses a more strict unix url format, while svn.exe requires a Windows compatible format.
# If svn url is already a valid http:// or svn:// style url, this should not change it.
$svnRepoUrlForGit = $svnRepoUrl -replace ':\\','\\' -replace '\\','//' -replace ' ','%20'
If (-not (Test-Path "$gitRepoPath")) {
md "$gitRepoPath" | Out-Null
}
$authorTransformFile = Join-Path $gitRepoPath "authors-transform.txt"
If (-not (Test-Path "$authorTransformFile")) {
Write-Host "Extracting authors from the repository" -ForegroundColor Green
& (Join-Path "$svnPath" 'svn.exe') log "$svnRepoUrl" -q | ?{ $_ -cmatch '^r.*?\|\s(.*?)\s\|' } | %{ "{0} = {0} <{0}>" -f $Matches[1] } | select -Unique | sc "$authorTransformFile"
Write-Host "You can now edit the authors transform file '$authorTransformFile'"
Read-Host -Prompt 'Press Enter when done' | Out-Null
}
Push-Location "$svnPath"
$projectSvnPaths | %{
Write-Host
Write-Host " Processing project '$_' " -ForegroundColor Black -BackgroundColor Green
$projectPath = $_
$projectCloneRootPath = Join-Path $gitRepoPath $projectPath
$projectCloneTempPath = "$projectCloneRootPath.git-temp"
$projectCloneBareGitPath = "$projectCloneRootPath.git"
# Create folder if it doesn't exist
# Skip project if it does and is not empty
If (Test-Path "$projectCloneBareGitPath") {
Write-Host "Project '$projectPath' already converted. Skipping project." -ForegroundColor Yellow
Start-Sleep -Milliseconds 300
return
}
If (-not (Test-Path "$projectCloneTempPath")) {
md "$projectCloneTempPath" | Out-Null
}
ElseIf ((gci "$projectCloneTempPath" | measure).Count -gt 0) {
Write-Host "Clone path not empty '$projectPath'. Skipping project" -ForegroundColor Yellow
return
}
Write-Host "Creating temporary clone of SVN project '$projectPath' through Git" -ForegroundColor Green
git svn clone "$svnRepoUrlForGit" --no-metadata -A "$authorTransformFile" --trunk="$projectPath/trunk" --tags="$projectPath/tags" --branches="$projectPath/branches" --prefix="origin/" "$projectCloneTempPath"
Write-Host "Converting svn:ignore properties to .gitignore file" -ForegroundColor Green
Push-Location "$projectCloneTempPath"
git svn show-ignore --id=origin/trunk | sc .gitignore
If (Test-Path '.gitignore') {
git add .gitignore
git commit -m 'Convert svn:ignore properties to .gitignore'
}
Pop-Location
Write-Host "Creating new bare git repository" -ForegroundColor Green
git init --bare "$projectCloneBareGitPath"
Push-Location "$projectCloneBareGitPath"
git symbolic-ref HEAD refs/heads/trunk
Pop-Location
Write-Host "Pushing to bare git repository" -ForegroundColor Green
Push-Location "$projectCloneTempPath"
git remote add bare "$projectCloneBareGitPath"
git config remote.bare.push 'refs/remotes/*:refs/heads/*'
git push bare
Pop-Location
Write-Host "Fixing tags and branch names in bare repository" -ForegroundColor Green
Push-Location "$projectCloneBareGitPath"
git for-each-ref --format='%(refname)' refs/heads/origin | %{
$branch = $_ -replace '^refs/heads/origin/(.*)$','$1'
if ($branch -like 'tags/*') {
# If ref is a tag, create a real tag and remove the branch
$tag = $branch -replace '^tags/(.*)$','$1'
git tag "$tag" "refs/heads/origin/tags/$tag"
Write-Host " New tag '$tag'" -ForegroundColor DarkYellow
git branch -D "origin/$branch"
}
Else {
# Remove 'origin/' from all refs
git branch -m "origin/$branch" "$branch"
}
}
Write-Host "Setting default branch name in bare repository" -ForegroundColor Green
$repoDefaultBranch = $defaultBranch
Do {
If (-not $repoDefaultBranch) {
Write-Host "Branch '$repoDefaultBranch' not found. Please select one from above list: " -ForegroundColor Yellow -NoNewline
$repoDefaultBranch = Read-Host
}
$repoDefaultBranch = git branch -l | %{
If ($_ -cmatch "^.*\s($repoDefaultBranch)$") {
Write-Host $_ -ForegroundColor Cyan
return $Matches[1]
}
Write-Host $_ -ForegroundColor DarkYellow
# returning anything at all (including empty string and even $null) results in weird output
return
}
} Until ($repoDefaultBranch)
git branch -m "$repoDefaultBranch" master
git symbolic-ref HEAD refs/heads/master
Pop-Location
If ($removeTempRepository) {
Write-Host "Removing temporary clone" -ForegroundColor Green
del "$projectCloneTempPath" -Recurse -Force | Out-Null
}
}
Pop-Location
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment