Migrating from Subversion to Git Jump to heading
A Subversion conversion is the one migration where the source and the result have no shared vocabulary. Subversion branches are directory copies, its tags are also directory copies, its revisions are repository-wide rather than per-file, and its authors are usernames with no email address. Converting is well-trodden, but the defaults produce a repository where every commit is attributed to jsmith <jsmith@localhost> and the branches are remote-tracking refs nobody can push. This recipe produces a clean result, within repository migration and consolidation.
When to use this approach Jump to heading
- A Subversion repository still holds history you need to keep.
- Authorship must be attributable to real people afterwards.
- Branches and tags created in Subversion should exist as Git refs.
- You can accept that commit ids are new β nothing carries over.
- If you only need the current files, export the working copy and commit it once; the conversion below is about history.
Step 1 β Build the author map before anything else Jump to heading
Identities are baked in at conversion time. Fixing them afterwards means a second rewrite.
# Every author in the Subversion history
svn log --quiet https://svn.example.com/repo \
| awk '/^r[0-9]+/ {print $3}' | sort -u > /tmp/svn-authors.txt
wc -l /tmp/svn-authors.txt # The map format: svn-username = Real Name <email>
{
while read -r u; do printf '%s = %s <%[email protected]>\n' "$u" "$u" "$u"; done
} < /tmp/svn-authors.txt > authors.txt
# Then correct it by hand β this is the part that needs a person
head -5 authors.txt # Verification: no entry still has a placeholder address
grep -c '@example.com' authors.txt Step 2 β Mirror the Subversion repository locally Jump to heading
Converting over the network is slow enough that a failure halfway through is expensive. A local mirror makes reruns cheap.
svnadmin create /tmp/svn-mirror
printf '#!/bin/sh\nexit 0\n' > /tmp/svn-mirror/hooks/pre-revprop-change
chmod +x /tmp/svn-mirror/hooks/pre-revprop-change
svnsync init file:///tmp/svn-mirror https://svn.example.com/repo
svnsync sync file:///tmp/svn-mirror # Verification: the mirror holds every revision
svnlook youngest /tmp/svn-mirror
svn info https://svn.example.com/repo | grep '^Revision' Step 3 β Convert, telling the tool about the layout Jump to heading
# Standard trunk/branches/tags layout
git svn clone file:///tmp/svn-mirror \
--stdlayout \
--authors-file=authors.txt \
--no-metadata \
converted # Non-standard layouts need explicit paths
git svn clone file:///tmp/svn-mirror \
--trunk=main-line --branches=feature-branches --tags=releases \
--authors-file=authors.txt --no-metadata converted # Verification: commit count should match the revision count reasonably closely
git -C converted rev-list --count --all
svnlook youngest /tmp/svn-mirror --no-metadata drops the git-svn-id trailer from every message. Keep it only if you intend to keep synchronising with Subversion; for a one-way migration the trailer is noise that outlives its usefulness.
Step 4 β Turn Subversionβs refs into real Git refs Jump to heading
The conversion leaves branches and tags as remote-tracking refs, which nobody can use directly.
cd converted
# Tags: Subversion tag directories become refs/remotes/origin/tags/*
git for-each-ref --format='%(refname:short) %(objectname)' refs/remotes/origin/tags |
while read -r ref sha; do
name=${ref#origin/tags/}
git tag -a "$name" -m "Imported from Subversion tag $name" "$sha"
git update-ref -d "refs/remotes/$ref"
done # Branches: everything else under refs/remotes/origin
git for-each-ref --format='%(refname:short) %(objectname)' refs/remotes/origin |
while read -r ref sha; do
name=${ref#origin/}
[ "$name" = trunk ] && continue
git branch "$name" "$sha" 2>/dev/null
git update-ref -d "refs/remotes/$ref"
done
git branch -m trunk main 2>/dev/null || git branch -M main # Verification: real refs, no leftovers
git tag | wc -l
git branch | wc -l
git for-each-ref refs/remotes | wc -l # expect 0 Step 5 β Verify against the source Jump to heading
# The working tree at the tip must match a Subversion export exactly
svn export file:///tmp/svn-mirror/trunk /tmp/svn-export --quiet
diff -r --exclude=.git --exclude=.svn /tmp/svn-export converted && echo "trees identical" # Spot-check an old revision too, not only the tip
rev=500
svn export -r "$rev" file:///tmp/svn-mirror/trunk /tmp/svn-r500 --quiet
sha=$(git -C converted log --format='%H' --all | tail -n +1 | sed -n "${rev}p")
git -C converted archive "$sha" | tar -t | head -3 # And that authorship is real
git -C converted shortlog -sne --all | head SAFETY WARNING β do not switch off the Subversion server the day the conversion completes. Keep it read-only for at least a release cycle: conversions routinely surface a branch nobody mentioned or a tag referenced by an old build, and with the source still readable those are lookups rather than losses. Announce the freeze, redirect writes, and archive rather than delete.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Why is the commit count different from the revision count? Jump to heading
Subversion revisions are repository-wide, so a revision that touched only a branch directory or created a tag produces no commit on the converted trunk. Expect the Git count to be lower, and verify by comparing trees rather than counts.
Can we keep committing to Subversion during the conversion? Jump to heading
You can, using git svn fetch to pick up new revisions, but running two systems in parallel is where conversions go wrong. A short freeze is almost always cheaper than a period where both are writable and someone has to reconcile them.
What about Subversion externals? Jump to heading
They have no Git equivalent and are not converted. Decide per external whether it becomes a submodule, a subtree, or a vendored copy β the trade-offs are in pinning and updating Git submodules safely and converting a submodule to a subtree.
Related Jump to heading
- Repository Migration & Consolidation β the parent topic and the verification discipline.
- Rewriting Author Emails During a Migration β fixing identities that were wrong at conversion time.
- Archiving a Repository Without Losing History β retiring the Subversion server safely.