I moved to Git SVN
For the last couple of years, I’ve been working with a company that uses Subversion. Yes, you are probably pointing now that we should have migrated to Git by now. Let’s not discuss that. Until now, Subversion didn’t get much in my way while performing my development tasks, but that slowly started to change. Probably a lot of people went through this as well.
My SVN Problems
My main problems with Subversion at the moment are:
- Working with multiple Subversion branches at the same time. I usually checkout each branch that I need in a separate folder. Yes, I could use svn switch, but it’s harder to isolate new feature development from bug fixing with only one source.
- Our Subversion repository is quite big now. You feel the pain (slowness) when comparing files in the history or annotating a file.
- Multiple commits relating to the same feature. I like to commit the several stages of a feature development. This helps to establish baselines on what’s working and what’s not in a timeline. But it’s a pain to look into the multiple commits of that feature when you are searching the history.
Solution
I’m also a Git user, and I know that Git solves these problems for me. Branches are cheap to create and you can stash your changes for easy switching between branches. Since you have a local copy of the repository, everything is faster. Using squash will combine all your feature development commits into a single commit for a cleaner history.
Instead of nagging the organization to move to Git (I’m doing that anyway), you can jump right away to Git, by using git svn. It ships with the standard Git installation and allows you to have a bidirectional connection between Git and Subversion.
This isn’t something new. It’s been around for a few years, but this is my first time using it. I have to say that it was not easy to setup thing the way I wanted. So I’m writing this post to remind myself of the steps I have followed in case I need them in the future. Of course, I also hope that these could help other people experiencing the same problems.
Setup
You’re mostly going to use the command:
git svn
Assuming the following information:
SVN Repository URL | http://svn-repo/myProject |
SVN Trunk URL | http://svn-repo/myProject/trunk |
SVN Branch URL | http://svn-repo/myProject/branches |
Init the Repository
Let’s start by creating our local repository. Type the following command:
git svn init --trunk=trunk http://svn-repo/myProject myProject
This will initialize a local Git repository. This command will not checkout anything yet. My Subversion repository follows a standard directory layout and I could use the -s
argument instead of manually indicating the trunk. I choose to manually indicate the trunk with --trunk
argument because I only want to check out the trunk. If you use the -s
everything sitting on branches and tags will be checked out. In my case I want to control exactly which branches , or tags I’m checking out.
If we want to include branches we can type:
git svn init --trunk=trunk --branches=branches http://svn-repo/myProject myProject
Better yet, if we only want the branch B1 and the brach B2 we can do it like this:
git svn init --trunk=trunk --branches={B1,B2} http://svn-repo/myProject myProject
Now look into the .git
folder and file config
. It should look like this:
1 2 3 4 5 6 7 8 9 10 11 12 | [core] repositoryformatversion = 0 filemode = false bare = false logallrefupdates = true symlinks = false ignorecase = true hideDotFiles = dotGitOnly [svn-remote "svn"] url = http://svn-repo/ fetch = myProject/trunk:refs/remotes/trunk branches = myProject/branches/{B1,B2}/*:refs/remotes/* |
You can always change the settings by manually editing this file.
When fetching specific branches there is a trick here. Notice that the generated file has a *
after the branches definition of {B1,B2}. This means, that git svn will fetch all subdirectories of the branches folder B1 and B2 and track each individual subdirectory as a remote branch. It’s a bit weird. Maybe I’m doing the original command wrong, but I had to manually remove the *
to track the branches properly. Make sure that you have this in the file:
1 | branches = myProject/branches/{B1,B2}/:refs/remotes/* |
This Stackoverflow question: How do I tell git-svn about a remote branch created after I fetched the repo? explains in a more detailed manner how to deal with branches.
Fetch the Code
To actually fetch the code from the Subversion repository type:
git svn fetch --no-follow-parent
In same cases, Git can create additional branches with the format {B1}@-{0-9}
. You usually don’t need this and you can prevent their creating by adding the --no-follow-parent
parameter. For a full explanation please check this Stackoverflow question: git-svn clone | spurious branches.
By the way this operation can be VERY VERY SLOW, depending on the size of your repository, it can take several hours to complete. If you are short on time, execute the commands before you go to sleep and you should have them ready when you wake up. To speed it up, you can use the parameter --revision
and specify a Subversion revision number. The fetch will only be performed from that point forward. The downside is that you don’t have the historical data before the specified revision.
Update the Code
When you need to update your Git repository with the Subversion one, execute:
git svn rebase
Push the Code
You don’t use push to send your local changes to the Subversion repository, instead use:
git svn dcommit
Final Thoughts
You can now enjoy all the benefits of using Git even if you are stuck with a Subversion repository. There are also a few limitations, but you can work around them. You cannot directly map multiple Subversion repositories to a single Git repository. This may be relevant depending on your Subversion structure. Also, committing your changes to Subversion may be a bit slow.
Anyway, I’m happy with the change. I feel that it increased my productivity, but that’s something you have to figure out by yourself. Just give it a try and see if it works for you. If not, you can always use your old Subversion repository.