Git SVN
This page is dedicated to Subversion integration in Git
References
- git-svn (1)
- Using a Subversion server on Yobi!
- Git and Subversion on Pro Git
- svn2git, Migrating a SVN repo to Git Repo
- Some handy tools at InterfacesFrontendsAndTools:
- git-sv, svneverever
- Git - SVN Crash Course (old version)
Configuration
My default .git/config file on Linux:
[user]
name = ...
email = ...
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
autocrlf = false
whitespace = -blank-at-eol, -space-before-tab,-blank-at-eof
[apply]
ignorewhitespace = change
[svn-remote "svn"]
url = ...
fetch = ...
branches = ...
tags = ...
My default .git/config file on Cygwin:
[user]
name = ...
email = ...
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
ignorecase = true
autocrlf = false
whitespace = -blank-at-eol, -space-before-tab,-blank-at-eof
[apply]
ignorewhitespace = change
[svn-remote "svn"]
url = https://www.collabnet.nxp.com/svn/atop
fetch = trunk:refs/remotes/trunk
branches = branches/*:refs/remotes/*
tags = tags/*:refs/remotes/tags/*
Workflow
Init / Clone
Get a clone of subversion repository with:
# git: git clone =>
# svn: svn checkout =>
git svn clone https://www.mysvn.org/svn/project -T trunk -b branches -t tags --username='USER'
git svn clone https://www.mysvn.org/svn/project --stdlayout --username='USER' # Identical
# Clean / compress git DB if necessary...
git gc
Fetch / Pull
Fetch all changes from SVN repo:
# git: git fetch =>
git svn fetch
Fetch and merge all changes from SVN repo:
# git: git pull =>
# svn: svn update =>
git svn rebase
git svn rebase -l # Only rebase against last fetched commit from upstream SVN
- In case of conflicts (see [1]):
- You are on a (no-branch) branch (with
git status
showing a .dotest file you can ignore) - Use the usual
git rebase --abort
(to bail out),git rebase --continue
(solved conflict),git rebase --skip
(no-op patch)
- You are on a (no-branch) branch (with
dcommit
Send local changes to SVN repo:
# git: git push =>
git svn dcommit
Branch
Create a branch using git svn branch
. For safety, always use option -n
first (dry-run):
git svn branch -n -m 'Some tricky expirements' mip_tricky # DRY-RUN
git svn branch -m 'Some tricky expirements' mip_tricky # ... if previous command ok
In case of non-standard project layout with several branch directory specification, we need to use option -d
to tell git-svn where to create the branch:
git config --get-all svn-remote.svn.branches
# branches/*:refs/remotes/* <-- we want to create our branch here
# Release_Branches/Dev/*:refs/remotes/Release_Branches/Dev/*
# Release_Branches/Indus/*:refs/remotes/Release_Branches/Indus/*
git svn branch -n -d branches -m 'Some tricky expirements' mip_tricky # DRY-RUN
git svn branch -d branches -m 'Some tricky expirements' mip_tricky # ... if everything's ok
Carefull! — If current commit is not in svn, git svn looks at the nearest svn revision that can be used as parent. This is particularly useful when branching off an existing svn branch or tags. However this fails if the svn tags have been changed into git tags or if they have been deleted:
git co tags/release_2.3
git svn branch -n -m 'branching off 2.3' mip_tricky # DRY-RUN
# Copying https://..../svn/myproj/tags/release_2.3 at r1478 to .... https://..../branches/mip_tricky
git br -r -D tags/release_2.3 # Let's see what happen if we delete our remote reference
git svn branch -n -m 'branching off 2.3' mip_tricky # DRY-RUN
# Copying https://..../svn/myproj/trunk at r497 to .... https://..../branches/mip_tricky
After deleting our remote reference, git svn does not use revision 'r1478 as starting point on tags release_2.3, but the revision r497 from the trunk.
Miscellaneous
Annotate changes in a file:
# git: git blame =>
# svn: svn annotate/blame =>
git svn blame fullpath # Only accept full path! (from project rootdir)
git blame file
Create git .gitignore from svn:
# Typically in project root:
git-svn show-ignore > .gitignore
More Workflows
- http://ujihisa.blogspot.com/2010/10/git-svn-low-risk-practice.html
- http://stackoverflow.com/questions/190431/is-git-svn-dcommit-after-merging-in-git-dangerous
Config with non-standard layout
In case the SVN repo does not follow the standard directory layout (i.e. trunk/, branches/ and tags), we can change the default config. Here an example of config:
[user]
name = beq06659
email = michael.peeters@nxp.com
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
autocrlf = false
whitespace = -blank-at-eol,-space-before-tab,-blank-at-eof
[apply]
ignorewhitespace = change
[svn-remote "svn"]
url = https://www.collabnet.nxp.com/svn/atop
fetch = trunk:refs/remotes/trunk
branches = branches/*:refs/remotes/*
# branches = Release_Branches/Customer/Audi/*:refs/remotes/Release_Branches/Customer/Audi/*
# branches = Release_Branches/Customer/S1NN/*:refs/remotes/Release_Branches/Customer/S1NN/*
# branches = Release_Branches/Dev/*:refs/remotes/Release_Branches/Dev/*
# branches = Release_Branches/Indus/*:refs/remotes/Release_Branches/Indus/*
# tags = tags/*:refs/remotes/tags/*
tags = tags/Customer/*:refs/remotes/tags/Customer/*
tags = tags/Industrial/Telebox/*:refs/remotes/tags/Industrial/Telebox/*
tags = tags/Industrial/*:refs/remotes/tags/Industrial/*
tags = tags/Intermediate/*:refs/remotes/tags/Intermediate/*
tags = tags/Mainline/*:refs/remotes/tags/Mainline/*
- Do's and Don't's
- We see that there are overlapping tag specification (tags/Industrial/Telebox/* and tags/Industrial/*). This is because some tags are created in the parent directory, and some more are to be found in the Telebox sub-directory. This will create a conflict though because git will create a reference named tags/Industrial/Telebox, and later will try to create a reference tags/Industrial/Telebox/... that fails because The first reference prevents creating the sub-directory. To solve, simply remove the reference and restart:
git branch -r -D tags/Industrial/Telebox
git svn fetch
- Git svn falls back in stupid mode (i.e. fetching the full history of each branch, even if given revision was already fetched in the past) if it is initialized without any option. So always init / clone the repo with option -s (standard layout). If the layout is non-standard, use
git svn init and edit the config.
- Never change the default location of branch. Never use a spec like
branches = branches/*:refs/remotes/branches/*
. When trying with such a spec, git svn was fetching and fetching again the same revisions (from r1!).
Tips
Remove orphaned svn branch
Convert tag branch to real git tags
- See [4]. My script:
#! /bin/bash
# Get .git directory
GITDIR=$(git rev-parse --git-dir)
if [ "$GITDIR" = "" ]; then
echo 'FAILED! Not in a git repository...'
exit 1
fi
# Backup current tag references
echo "Saving svn tags reference to $GITDIR/svn_tags_backup..."
SVNTAGBACKUP="$GITDIR/svn_tags_backup"
if [ -a "$SVNTAGBACKUP" ]; then
read -i N -p "$SVNTAGBACKUP already exists. Overwrite? [y/N] "
if [ "$REPLY" != "y" -a "$REPLY" != "Y" ]; then
exit 1
fi
fi
# Convert the tags
git-for-each-ref refs/remotes/tags > "$SVNTAGBACKUP"
< "$SVNTAGBACKUP" cut -d / -f 4- |
while read REF
do
# Adding tag comment does not work... go figure why
# COMMENT=$(git log --pretty=tformat:"%s" -1 "refs/remotes/tags/$REF")
# git tag -a "svn/$REF" -m "$COMMENT" "refs/remotes/tags/$REF"
git tag "svn/$REF" "refs/remotes/tags/$REF"
git branch -r -d tags/$REF
done
Force git svn to re-fetch a branch
- See [5]. Basically it amounts to
rm .git/svn/git-svn/mynewbranch -Rf && git svn fetch
.
A cleaner way might actually be to use git svn reset
:
rm -rf .git/svn/.caches # Clear the cache
git svn reset -r 300 # set 300 as last fetched revision, use -p to fetch 300 also.
git svn fetch
If given revision must be fetched again, use option -p
.
My first reset did fail with some error similar to the one we get when the cache is corrupted. Clearing and resetting/fetching again did the trick though.
Convert git diffs into svn diffs
- See [6]
Troubleshooting
Byte order not compatible
- Recently got the following error on cygwin:
Byte order is not compatible at ../../lib/Storable.pm (autosplit into ../../lib/auto/Storable/_retrieve.al) line 380, at /usr/share/perl/5.10.1/Memoize/Storable.pm line 21
[...]
Could not unmemoize function `lookup_svn_merge', because it was not memoized to begin with at /usr/lib/git-core/git-svn line 3197
rm -r .git/svn/caches
Git svn fetches several times the same revision
If git svn does not detect that a branch is a branch starting from the trunk, it will fetch the complete branch history from the beginning, possibly fetching a revision that was fetched already. This is because svn is very flexible on branch, and git svn has to take the pessimistic choice to make sure no data is lost.
- git svn fetches the same revision multiple times for non-trunk branches
- git svn fetch retrieves the same Subversion revision multiple times for branches
This post seems to tell us that we should change the refspec of remote svn branches (i.e. leave them in refs/remotes/*)
A possible solution is to limit the range of revisions (see [9]):
git svn log --all -1 | \
sed -n '2s/r\\([0-9]*\\).*/\\1/p' | \
xargs --replace=from git svn fetch -r from:HEAD
Another solution would be to fetch by range (0-100, 100-200, etc).
Lots of @rev
Likely to occur when tags have been moved around (see [10]).
A short script to remove them:
git tag -l svn/* | grep @ |
while read tag; do
DUPL=$(git tag --contains "$tag" | wc -l)
if [ $DUPL != 0 ]; then
git tag -d "$tag"
fi
done