When a Legacy Project Gets “Stuck” on SVN
I was handed a project that had been running since 2016, with its codebase sitting on the old company’s SVN server — that’s the situation I ran into not too long ago. Seven years of commit history, dozens of branches, everything in SVN format. The new team wanted to switch to Git to enable CI/CD integration, but nobody wanted to throw away that valuable commit history.
After trying a few options, I settled on git-svn — a bridge tool built into Git that requires no extra installation. Rather than exporting code and importing it fresh, it performs a true conversion, revision by revision, preserving every commit, author, and branch structure.
How git-svn Works
git-svn has been available since Git 1.5.x. It reads each SVN revision and creates a corresponding Git commit. Specifically, it handles three things:
- Maps SVN revision numbers (r1, r2, r3…) to Git commit SHAs
- Converts SVN usernames to Git author format (name + email)
- Converts the SVN directory structure (
trunk/branches/tags) into Git branches and tags
Structural Differences Between SVN and Git
SVN uses linear revision numbers and stores branches as actual directories on the server:
svn-repo/
├── trunk/ ← equivalent to the main branch
├── branches/
│ ├── feature-login/
│ └── hotfix-payment/
└── tags/
├── v1.0/
└── v2.1/
Git works differently: branches and tags are just pointers, not real directories. git-svn automatically maps trunk to main, branches/* to Git branches, and tags/* to Git tags. After cloning, you’ll need an extra conversion step to turn them into actual local branches and tags instead of remote tracking refs.
Step-by-Step: Migrating SVN to Git
The walkthrough below uses an SVN repo at https://svn.example.com/myproject with the standard trunk/branches/tags structure.
Step 1: Install git-svn
On many distros, git-svn doesn’t ship with Git by default:
# Ubuntu/Debian
sudo apt-get install git-svn
# CentOS/RHEL
sudo yum install git-svn
# macOS (Homebrew)
brew install git-svn
# Verify installation
git svn --version
Step 2: Create an Author Mapping File
SVN only stores usernames (e.g., john_dev), while Git requires a full name and email. First, extract the list of all SVN authors:
svn log https://svn.example.com/myproject --xml --quiet \
| grep "<author>" \
| sort -u \
| sed 's/.*<author>\(.*\)<\/author>.*/\1/' > /tmp/svn-authors-raw.txt
cat /tmp/svn-authors-raw.txt
From that list, create an authors.txt file:
john_dev = John Nguyen <[email protected]>
mary_tran = Mary Tran <[email protected]>
admin = Admin Bot <[email protected]>
Each line follows the format: svn_username = Full Name <email>. Don’t miss any authors — git-svn will stop at the first revision where it encounters an unmapped username and throw an error, refusing to continue.
Step 3: Clone the SVN Repo with git-svn
This is the most time-consuming step. A repo with 1,000 revisions takes roughly 10–20 minutes; 10,000+ revisions can run for hours depending on network speed and the server. It’s worth starting a screen or tmux session first:
screen -S svn-migration
git svn clone https://svn.example.com/myproject \
--stdlayout \
--authors-file=authors.txt \
--no-metadata \
myproject-git
# Press Ctrl+A, D to detach the screen session if needed
Key flags:
--stdlayout: Automatically detects the standard trunk/branches/tags structure (shorthand:-s)--authors-file: The mapping file created in Step 2--no-metadata: Prevents appending agit-svn-id:string to the end of each commit message — keeps the commit history cleaner
If your SVN repo doesn’t use the standard structure, drop --stdlayout and specify the paths manually:
git svn clone https://svn.example.com/myproject \
--trunk=code \
--branches=feature-branches \
--tags=releases \
--authors-file=authors.txt \
myproject-git
Step 4: Convert Branches and Tags
After cloning, SVN branches and tags appear as remote tracking refs (refs/remotes/*), not actual Git local branches or tags. You need to convert them:
cd myproject-git
# Convert SVN tags into real Git tags
git for-each-ref refs/remotes/tags | cut -d / -f 4- | while read tagname; do
git tag "$tagname" "refs/remotes/tags/$tagname"
git branch -r -d "tags/$tagname"
done
# Convert SVN branches into local Git branches
git for-each-ref refs/remotes | grep -v '@' | grep -v 'tags' | cut -d / -f 3- | while read branchname; do
git branch "$branchname" "refs/remotes/$branchname"
git branch -r -d "$branchname"
done
# Rename trunk to main
git branch -m trunk main
Verify the results:
git branch -a # List all local branches
git tag -l # List all tags
git log --oneline -15 # Check commit history
Step 5: Push to the Remote Git Repository
Create an empty repo on GitHub, GitLab, or Gitea, then push everything up:
git remote add origin https://github.com/yourorg/myproject.git
# Push all branches
git push origin --all
# Push all tags
git push origin --tags
One thing worth emphasizing at this step: don’t use --force. I once lost important code by accidentally force-pushing to a branch someone else was actively working on — ever since, I’ve been very cautious with git push --force. The remote repo starts empty, so there’s no reason to use force at all. If you hit an error, track down the real cause instead of using --force to bypass it.
Common Issues You’ll Encounter in Practice
Missing Authors in the Mapping File
The clone stops with this message:
Author: old_contractor not defined in authors.txt file
Add the missing entry to authors.txt and resume — no need to start the clone over:
echo "old_contractor = Old Contractor <[email protected]>" >> authors.txt
cd myproject-git
git svn fetch
Repository with Too Many Revisions
Have 10,000+ revisions but don’t need the full history? Limit the clone to a specific starting point:
# Clone only from revision 8000 to HEAD
git svn clone https://svn.example.com/myproject \
-r 8000:HEAD \
--stdlayout \
--authors-file=authors.txt \
myproject-git
Verifying the Migration Results
Once complete, compare the commit counts to make sure nothing was missed:
# Count revisions in SVN (run from a machine with the svn client installed)
svn log https://svn.example.com/myproject | grep -c "^r[0-9]"
# Count commits in the newly migrated Git repo
cd myproject-git
git log --all --oneline | wc -l
The two numbers rarely match exactly — SVN has merge revisions and property-only changes that don’t generate separate Git commits. A difference under 10% is normal; a difference greater than 30% warrants a closer look at the conversion process.
After Migration: Immediate Next Steps
Getting the code onto Git is the easy part. The more important task is making sure the entire team fully transitions — nobody accidentally commits to the old SVN repo:
- Notify the whole team, share the new Git repo link and cloning instructions
- Set the SVN repo to read-only (or lock it entirely) to prevent anyone from accidentally committing to SVN
- Update the CI/CD pipeline from SVN checkout to Git clone
- Verify that
.svndirectories don’t end up in the Git repo (add them to.gitignoreif needed)
The first week, you’ll get questions like “how do I clone this?” and “how do I switch branches?” — that’s normal. A short doc covering basic Git workflow is all you need. Once past that phase, the first thing teams usually notice is that creating a new branch takes milliseconds instead of copying an entire directory on the server — branching in Git is no longer “expensive” like it was in SVN.

