Created
November 26, 2019 09:40
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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