I’ve switched from SVN to Git (more git-svn actually) close to a month ago and that had to be a leap of faith. Rationally convincing someone that a DVCS is better is pretty hard because overall the life in SVN land is not that bad or does not appear to be. Note that I am using git-svn so I don’t benefit from all the power of DVCSes. While nothing replace actually trying it, I thought it was worth the time to explain what I like about this new tool to help people jump too.
This is not a post on why merging is superior in Git compared to SVN (this is something you need to experience), it’s a post on how Git is making my life easier.
This post is split is a few sections:
- some intro
- how to import a SVN repo into Git (feel free to skip this tutorial of you are interested in what I liked in Git compared to SVN)
- use case: multitasking in isolation
- use case: backporting bug fixes
- use case: writing better commits and commit histories
- resources
General
If I had to summarize, Git gives me more freedom than SVN. I am not constrained by the tool in any way:
- it can follow whatever workflow I want
- it is fast
- as a net result my commits are clearer
I’t hard for me to say that but since the move I do enjoy committing stuffs (I know, that’s pretty scary).
The bootstrap
Here is a small tutorial section for people willing to import a project from SVN to Git and keep a bridge between the two. Due to a bug in git-svn for https imports, I am using Git 1.6.5.1 and not the 1.7.1 version.
mkdir project; cd project;
git svn init --trunk=my/svn/repo/project/trunk/ \
--tags=my/svn/repo/project/tags \
--branches=my/svn/repo/project/branches \
my/svn/repo/project
You can optionally create a file containing a conversion between SVN logins and the committers names and email addresses
jdoe = John Doe <jdoe@foo.com> agaulois = Asterix <asterix@gaule.fr>
In the logs, every time git-svn finds agaulois, it converts it to Asterix <asterix@gaule.fr>. If you want to do that, you need to create this file. Don’t be afraid to miss a couple of logins, if you do, git-svn will stop and ask you to add it. In your Git repository directory, run
git config svn.authorsfile ~/dir/myauthors.txt
The next step is to fetch all the information. This is long, very long. The good news is that you can stop it and restart later.
git svn fetch
Once that is done, you are good to go. To update your Git repo with the new commits from SVN do
git svn rebase
To commit your set of local commits to SVN, do
git svn dcommit
Many people, especially in the open source community, consider DVCS as a bad thing because it encourages committers to keep their work locally and not share with others. In reality, it does not. People who share frequently will continue to do so, people who don’t still don’t and should be fired. Same as usual. In practice for me, I dcommit every 4 to 6 hours.
I do recommend to import one SVN project per Git repository. You will typically get several Git repos per SVN repo. The rule is import the biggest unit that you tag / branch in isolation in SVN. For example, for Hibernate, I’ve several Git repos:
- Hibernate Core (which contains all the modules)
- Hibernate Validator
- Hibernate Search
- JPA API
- Bean Validation API
- Bean Validation TCK
All of these have generally independent release cycles and version numbers. Apparently it is possible to aggregate Git repositories via the notion of superproject but I have not tried.
One golden rule: you cannot share a Git repository and pull changes back with someone else AND use it to commit in a SVN repository. That will be a mess because git-svn rewrites the commit unique identifiers. Forget sharing repos when you use git-svn unless you are abandoning SVN and are doing a one time import.
Multitasking in Isolation
The absolute coolest feature is the ability to work in total isolation on a given topic for very cheap. I am not necessarily talking about the ability to work offline on an island (though that’s nice). I am talking about the ability to work on several subjects in parallel without complex settings.
Let’s take an example. I was working on a new feature for Hibernate Search’s query DSL. I branched master to dsl and started to work, including committing small chunks of work (more on this later). While working on it, I found a bug in the existing query engine. No problem, I literally stopped working on the new feature, put stuff aside (git stash). created a new branch off master named bug123 and fixed the bug. When I was done with the bug fix, I applied it on master and the dsl branch and resumed my work there. There is the workflow:
git checkout -b dsl #create the dsl branch and move to it #work work commit work commit work git stash #put not yet committed stuff aside git checkout master git checkout -b bug123 #create bug fix branch #work work #fix bug 123 git commit git rebase master #apply commits of master on bug123 (not necessary in this case as I did nothing in master) git checkout master git merge bug123 #merge bug123 and master git branch -d bug123 #delete the useless branch git checkout dsl git rebase master # apply commits of bug123 git stash pop #reapply uncommitted changes #work
It looks like a lot of operations but, it’s very fluid and very fast!
What’s the benefit? I’ve fixed a bug in isolation of my new feature even if the same files where impacted. I’ve committed the bug fix isolated: I can easily reapply it to maintenance branches (see below). Had I used SVN, I would have fixed the bug and committed “new feature + bug fix 123″. I would not have backported the fix to our maintenance branch nor would have my co-workers because of the complexity to separate the new feature from the bug fix. In Git the process is so smooth that I even use it to bug fix typos in comments in isolation from my main work.
I should point out that switching branch is super fast and done in the same directory. You IDE quickly refreshes and you are ready to work in the same IDE window. For me that’s a big plus over having to checkout a maintenance branch in a separate directory, set up my IDE and open a second IDE window to work in a different context of the same project: I work on five different projects on average, I can’t afford a proliferation of IDE windows. With Git, the context switching comes with much less friction and saves me a lot of time.
Backporting bug fixes
In SVN land, to backport a bug, I either:
- generate the patch off of the SVN commit, and apply it on a checkout of the maintenance branch
- manually read the commit diff and select which change I want to apply (generally because somebody has committed the fix alongside a new feature or because it has committed the feature in 7 isolated commits)
In the first scenario (the easiest), it involves
- generating the patch
- saving it as a file
- optionally checking out the maintenance branch (ie get a coffee)
- opening my new IDE window
- apply the patch
- commit the change with a log message
In Git, you:
- checkout the maintenance branch (2s)
- run the cherry-pick command (git cherry-pich sha1) over the commit or commits you want to copy from the main branch (logs are copied automatically though you can change them if needed)
So easy you actually do it
Writing better commits and commit histories
A feature I do like is the ability to uncommit things and rewrite / rearrange them. This is something you only do on the commits you have not yet shared (in my case not yet pushed to SVN). That looks like a stupid and useless feature but it turns out I use it all the time:
- I can commit an unstable work, explore a couple of approaches and come back if needed
- I can fix a typo or bad log message
- I can simplify the commit history by merging two or more commits (I typically merge commits I used as unstable checkpoints)
- These operations typically require 5 seconds or less
The net effect of being able to do that is:
- I write better log messages
- I commit more often / in smaller pieces, making my changes more readable
- If the pieces happen to be too small I merge them before synching with SVN
You can also do some more micro surgery. If you are changing code and realize that these are really two or three sets of changes and should be committed separately (changes from the the same file potentially). You can literally select which file / which line to commit. The tool GitX let’s you do that very easily.
Git can do that because it does not track files, it tracks changes. You can stage some changes for commit (two new files and changes in three files), continue working on the same set of files and commit the state as defined when you initially staged it. Your subsequent changes can then be committed later. This is a subtle difference of approach (content management vs file management) but now that I have used it, I like it better. As a consequence, if you change a file, these changes won’t be committed automatically next time you commit. You need to include them (that’s what the -a option is for when you run git commit).
Resources
I absolutely recommend you to read Pro Git:
- that’s a top quality book
- use case oriented
- and it’s also available for free online http://progit.org (though go buy it too, it’s well deserved)
Aside from that, I do use
git help command
very often, their documentation is pretty good. Otherwise, Google is your friend, there are many resources out there.
I don’t need / miss additional tools to work with Git. The command line is good enough and often less confusing (IntelliJ’s integration confused me, so I don’t use it unless I need to compare files). I do like to use GitX a graphical tool for two purposes:
- it displays branches and commits graphically
- it lets you easily stage specific lines of a file for later commit (the micro surgery tool)
That’s it folks,
I hope you enjoyed the read and that I’ve encouraged you to give it a try. git-svn made the try a no brainer really. I’ve lost probably 16 hours to learn, try and understand Git. I’m confident I will get them back within the next three to four months (my ROI is covered
). You can also try it on any directory, I am now using Git to keep a revision history of all my presentations. Remember, no need to set up a server or anything complex. Run git init and you are good to go.
Disclaimer: this is not a thorough comparison, just the feedback of a one month old user.


Thank you for this post, especially for the svn git integration bit. In case you are an Eclipse user you may also want to have have a look at the EGit project. But AFAIK they do not offer svn integration yet.
I experienced exactly the same within the last months using git svn – I really love the possibility of switching to git without having all the other team members forced to use it too.
As for EGit I can only speak for myself but I really like the command line interface of git. I tried some GUI tools but in most use cases I am happy with the shell
One hopes git will be incorporated into eclipse soon. There’s no looking back once you have gotten bit by git.
Really like this post.
I’m interested in problems you mentioned with IDEA git support. What did not work in detail? Anyway, what do you think: which IDE has best git support? Thanks
It’s more at the philosophy level.
When I look at the available menus and command names in IntelliJ, it seems the SVN world has been mapped onto the Git world (for example, unstaged / staged). At the end of the day I was totally unsure what to do and did not want to screw things up. So I came back to the command line or GitX.
I find that GitX provides a lot of what I need graphically on a daily basis.
BTW when you execute things on the command line, IntelliJ seems to not notice (at least for a while) and show files as not committed.
I like and use Git with SVN projects for all the same reasons! You might be interested by a ‘simpler’ command to init and checkout an SVN project, simply go:
git svn clone -s my/svn/repo/projectThat will actually do the init and fetch. The -s option tells Git SVN that the project uses the standard SVN layout, so no need to specify where trunk, tags and branches are. And you can stop and resume using
git svn fetch.-s did not work well for me at the time, that’s why I used the manual option.
Me again. I’ve had an interesting issue with Git SVN today and I thought I would share it.
In short, sometimes
trunkis not linked tomasterand running the following command fixes it:git reset –hard remotes/trunk
I was getting some problems where the ‘git svn fetch’ would bomb out with the following “useful” error message:
Assertion failed: (svn_path_is_canonical(component, pool)), function svn_path_join, file subversion/libsvn_subr/path.c, line 115.
error: git-svn died of signal 6
Modifying the following of .git/config
branches = projects/cl-benchmark/branches//*:refs/remotes/*
tags = projects/cl-benchmark/tags//*:refs/remotes/tags/*
to
branches = projects/cl-benchmark/branches/*:refs/remotes/*
tags = projects/cl-benchmark/tags/*:refs/remotes/tags/*
Fixes it (thanks Carlo!)
Excellent post. You’ve hit on a number of reasons that I myself have used to convince our project leaders to begin transitioning from SVN to Git. I wanted to comment on one thing though – your “Golden Rule”.
You don’t necessarily need to abandon SVN altogether. Here’s how we’ve been working during our transition phase – where some of us are using Git, and others are using Subversion.
Setup:
1) Either git svn init, or git svn clone the SVN repo (as you instruct). This then becomes the “Git/SVN Bridge.”
2) Fix up any of the branches, tags, etc… here. The svn/* refs are considered remotes, so if you want tracking branches, check these remotes out and create appropriate local branches. Also, check the tags out, and create actual tags. You MUST create local branches for any SVN branches that you wish to synchronize between Git and SVN.
3) Now, create a new bare repository (git init) somewhere, and from the bridge, push all the branches to the bare repo (git push –tags ).
4) All Git users now clone this bare repository (including you). Don’t do actual work from the bridge. The bridge will only be maintained by one (or a few) people that understand how to synchronize Git and SVN.
Sync’ing:
1) To update SVN trunk with changes on master, and vice-versa:
From the bridge:
a) git svn fetch (get new SVN changes)
b) git checkout master
c) git pull master (get Git changes from bare repo)
d) git checkout svn/trunk (checkout detached head)
e) git merge –no-ff –log master (merge changes from master). –no-ff insures an actual commit, –log copies individual log messages from each commit on master (–log is optional). git commit –amend can then be run if you want to edit the commit message.
f) get svn dcommit (This pushes your merge commit to SVN. Note that the commit was on a detached head, and is no longer accessible). All of your work on master (since the merge-base of master and svn/trunk) gets committed as a single change, and is now available to SVN users.
g) git checkout master
h) git merge svn/trunk (Gets the new updates from SVN – with the altered commit message – and merges to master)
i) git push barerepo (makes the SVN changes available to Git users)
That’s a lot of work, but by committing on the detached head, you essentially make that commit object inaccessible. This is a good thing, because the final git merge svn/trunk to master brings in that same change set, but with the new commit message (with the git-svn-id line). By the end, all of your Git users have access to the SVN changes, and vice-versa, and you avoid the massive amounts of merge conflicts that would result from duplicate changesets if you did the merge from svn/trunk to master, and then dcommitted from master.
The important thing is that there’s only one “bridge,” probably maintained by only a small number of people (who understand the sync process). Multiple branches (maintenance branches, for example) can be kept in sync this way by making the appropriate substitutions. The Git users (aside from the one responsible for the synchronization) never worry about SVN, and the SVN users never worry about Git.
Unfortunately, I can’t take full credit for this. This workflow is presented in Jon Loeliger’s excellent book “Version Control with Git”. I’ve added a couple extra steps that have worked very well for us.
Quick question or you @Josh,
Your technique means that n git commits are converted in one big SVN commit right?
That’s correct… and really the only downside to this approach. If your ultimate goal though is to completely transition (at some point), then it’s not that big of a deal. All of the individual Git commits are still preserved.
It’s not really possible to sync the commits separately in both directions. You can’t just rebase them onto the subversion branch, because they may not apply individually, so you’d have to create branches for them in subversion. And that would drive both subversion and you crazy pretty soon.
This way it’s exactly as if you had a branch in subversion — trunk only sees the changes squashed to a single commit — except the branch itself is only available in git.
Hi Emmanuel, Josh,
I’ve got a slightly different Git/SVN bridge setup documented here:
http://blog.tfnico.com/2010/10/gitsvn-5-centralized-git-svn-mirror.html
We also have the fetch-repo + bare-repo, but we don’t have to do the somewhat tedious detached head procedure. The fetching repo is automatically rebased with the latest commits from Subversion via an svn hook. This is what our typical workflow looks like:
> git pull –rebase (get latest changes from bare repo)
> git commit
> git commit
> git pull –rebase (again to see if there’s any changes in svn)
> git update-ref refs/remotes/git-svn refs/remotes/origin/master (annoying but necessary step to update the git-svn pointer to latest commit in bare repo)
> git svn dcommit (can push multiple commits to svn)
We can also work in branches, but we always *rebase* back to the master branch because history has to remain linear.
Try it out, we’ve been using it at work for 4-5 months now, no big problems so far.
Have you tried brotherbard’s GitX fork? Nice stuff, and since the original GitX is kinda dead, it looks like this is the ‘new’ GitX: http://brotherbard.com/blog/2010/03/experimental-gitx-fork/
Don’t let the ‘experimental’ tag scare you off. It’s quite stable!
Nope I haven’t tried. I’ll have a look.