Migrating SVN to Git with git-svn: Preserving Commit History and Branches for Legacy Projects

Git tutorial - IT technology blog
Git tutorial - IT technology blog

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 a git-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 .svn directories don’t end up in the Git repo (add them to .gitignore if 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.

Share: