I recently had a chance to use the Git interface to Subversion (git-svn), and it’s the best way that I’ve found to work with Subversion (SVN). Using git-svn is better than using plain SVN. The primary reasons I feel this way are that it provides a convenient staging environment and superior local branching capabilities.
The staging environment provided by Git means that I have a local version control system that no one else can see. This allows me to take continually check things in without worrying about mucking up the general repository. I like this because I can make changes quickly and check in whenever I make progress and things mostly run correctly. This is great because I hate it when things are improving and then I make some boneheaded change to a bunch of files and can’t figure out how to get back to a working state.
When I’m ready to check into the shared repository (in SVN), I can do this as a separate operation. What’s nice is that I can squash, modify, and reorder local commits so that my development history is clean of the rabbit trails that inevitably pop up. Theoretically, the staging environment allows me to not need to run full test suites before locally committing to ensure that I don’t break the build. This was not really a gain that I utilized though.
The second advantage was quick local branching capability. This allowed me to have several streams of development going that were mostly independent of each other. This was invaluable for quick bug fixes, and helped when work was blocked due to a client member being unreachable. Another great advantage was the ability to have my master branch be clean and always be ready for a demo. Git branches differ significantly from SVN branches, in that they are much faster to work with, require less space, and, perhaps most importantly, Git offers better merging capabilities.
Everyone else on your team can still use SVN normally, and you will see their changes if you use the correct workflow. There are numerous sites that describe this workflow and variations of it, so I won’t detail this. Just Google ‘git svn workflow’.
Issues
I ran into a couple of problems that I’d like to discuss, and my final solution. One issue was that I developed for quite awhile in Git and then needed to put this into the client’s SVN repository. There are numerous risks that I should have mitigated by doing this as I was going along with the project. I did not commit against my better judgment because I thought that my code structure might change significantly. However, it never really did. In the future I won’t hesitate to do this now, as the pain of messing around with version control is not really all that high.
I initially was interested in keeping my development history around, but this turned out to be pretty hard based on some specific things that I did. I think that the main reason for this is that I wanted my commit messages to have the correct name and email. I’m pretty sure I changed this setting in ./.git/config, but something must have gone awry. Hence, I ran a one-liner at the command line similar to the following:
git filter-branch --env-filter "export GIT_AUTHOR_NAME='<my name>';
export GIT_AUTHOR_EMAIL='<my email>';
export GIT_COMMITTER_NAME='<my name>';
export GIT_COMMITTER_EMAIL='<my email>'" HEAD
However, when then subsequently running git-svn commands, I received this error: Can’t call method “full_url” on an undefined value at /usr/lib/git-core/git-svn line 425.
My understanding of what this error means is that git-svn can’t find a join point between local development and your remote branch. Running the git-svn init or clone commands sets this up correctly, but what I was doing messed it up. I think I lost quite a bit of time by not understanding that rewriting the history at the end (which I typically did) would cause git-svn to get out of sync. I also messed around with grafts for awhile, which probably didn’t help matters any. I think in retrospect I probably should have only rewritten commit history for commits that I did locally, and not mess around with grafts at all. This would have probably allowed me to just git svn rebase
to keep my history and have a flurry of checkins with the right information. But at this point, I was alright with losing my development history since I had already spent a bit of time working on it.
Once I was ready to import my changes, I found the following process to be helpful. Basically I add SVN directories, sync with these directories, copy over the files in master from my local directory, then commit the changes and dcommit to SVN. First, let’s add the standard SVN layout to the SVN repo. If you’re worried about messing something up, doing this process locally first is helpful, and then doing it to a sandbox directory might be advisable. See this page for an example of the former.
> svn co <repo/folder>
> cd <folder>
> mkdir trunk tags branches
> svn add trunk tags branches
A trunk
A tags
A branches
> svn commit -m "Base directory structure."
Adding trunk
Adding tags
Adding branches
Committed revision 1.
Then in a clean directory, you can execute:
# the --prefix=svn/ gives you remote branches prefixed with svn/
# which helps to namespace them
> git svn clone <repo/folder> -s --prefix=svn/
Initialized empty Git repository in <folder>/.git/
Using higher level of URL: <repo/folder> => <repo>
W: Ignoring error from SVN, path probably does not exist: (160013): Filesystem has no item: File not found: revision 100, path '<folder>'
W: Do not be alarmed at the above message git-svn is just searching aggressively for old history.
This may take a while on large repositories
r1 = <treeish> (svn/trunk)
Checked out HEAD:
<repo/folder> r1
> cd <folder>
> git branch -a
* master
svn/trunk
I’d modify your .git/config file to have the correct username / email if necessary at this point.
Then you can copy all of the files from your original working directory (the one where you did all of the development) except for the .git directory. If you copy this over, you’ll have to start all over again. At this point:
> git status
# all of your files
> git add .
> git commit -m "Changes from my local repository."
Created commit <treeish>: Changes from my local repository.
...
And finally:
# in case someone updated the SVN directory that you're working with (unlikely)
> git svn fetch
> git svn dcommit --dry-run
Should see something about pushing to <repo/folder/trunk>
> git svn dcommit
A file1
# etc....
This should add your files in the trunk folder in the SVN repo. Hope this helps!