Archive

Archive for 2009/12/11

Bridging SVN to Git

At work I have a problem. The problem is that I am doing many things at the same time… Now usually this is not a problem but in this case it is. The reason why this is a problem is because I’m the developer and maintainer of a few components that are in both production (I need to bugfix etc), development (I need to add enhancements etc) and research (trying out design and concept) and I need to maintain several versions of the component in each of these. But the core problem is that I don’t have the right tools to do this effectively. Since we are using SVN as a version control system I’m somewhat limited in how I can work.

To make it a bit more annoying I also have 2 laptops that I work on. One which is my personal one which is more customized, have the applications I want and I can install what ever I want on. The other one is my work laptop which must have certain applications, I can not add what ever I want and a few times a day automatically updates my Windows OS and reboots. My work laptop is honestly a pain in the ass sometimes.

Anyway. There are three things I would really like to achieve which I can’t when using only SVN:

  1. I want to be able to commit my work without having access to a central server
  2. I want to be able to create branches and merge between them without keeping track of when they were made or last merged and also I want to create an arbitrary number of branches to test stuff offline
  3. I want to be able to easily transfer my work from one laptop to another without going through the central server including a merge when I do so. E.g. If I made a small change on my personal laptop and then switched to my work laptop and did more work without the change make in my personal laptop and then want to change that work back to my personal laptop I don’t want to overwrite what I did there previously, I want to merge it!
  4. I want someone else to be able to contribute to the component so that if someone else noticed something while I was away and fixed it then they should be able to allow me to pull it in without sending me an email saying here is the diff (bleeeh!) without me having to update from the svn repo and get conflicts

That’s what I want(!) and fortunately I can achieve all this with Git (the rest that comes with it is just extra!). I should mention that there is something called git-svn but Since I don’t want to work with a whole repo, just a part of it, I didn’t want something as heavy as git-svn. Git-svn supports almost everything and tries to “merge” the git and svn commands which is not what I want… I find it confusing and unnecessary.

Now the problem I had to solve is how do I create a bridge where I can have my own structure with a central repo for things that are ready to be commited to SVN? So after a few minutes I realized that his is what I want:

So this is a step-by-step guide to how I achieved this; maybe someone out there will find it useful.

1. Check out your directory from the SVN repo
Check out the directory you want to bridge. SVN creates a one “.svn” directory in each subdirectory that is part of the SVN tree (including the top directory). This directory is changed on every action that you do in that directory (and every parent directory will know about it). Git doesn’t do that, it sees things differently (fortunately! you will understand why later), git has only 1 directory which it will create. Anyway so check out the directory you want to track. Below is an example:

:~> svn co svn://hostname/repo/trunk/app
A     app/file1
A     app/file2
...
A     app/fileX
Checked out revision 2823.
:~>

2. Initiate a Git repo inside the SVN repo
Ok, so after we have checked out the SVN directory we need to initialise the Git repo inside the directory that was created by SVN.

:~> git init
Initialized empty Git repository in .git/
:~>

3. Have SVN ignore the .git directory
Since we don’t want to track the .git directory in SVN we have to let SVN know that we should ignore it. This is done by putting a propset on the top level directory (where the .git directory is). Here is how it is done:

:~> svn propset svn:ignore ".git" .
property 'svn:ignore' set on '.'
:~>

Node the last character in the command (the period), it sets the property on the current directory.

4. Have Git ignore the .svn directories
When we push and pull back and forth we don’t want git to care about .svn directories because they change every time any of the SVN tracked files change and we want to be able to push/pull to the staging directory without git getting confused. This is how it is done in Git:

:~> echo -e ".svn\n*/.svn/*" >> .git/info/exclude
:~>

A few things to note; the first .svn is to ignore the top-level .svn directory. The “*/.svn/*” pattern is to ignore all the rest of the .svn subdirectories. Here you can add more patterns; as an example I put the string “.svn\n*/.svn/*\n*.beam\n*.app” to ignore Erlang binaries and the Erlang application file (which my Makefile generates). If you are using other languages you can just adjust to whatever you want to ignore. Personally I’m only interested in tracking source files

5. Add the files you want to make available to Git
Now all your files are tracked by SVN and SVN is also ignoring git. Git however is not tracking anything. So do this by explicitly adding all the files you want to have available in the Git “cloud”, or if your exclude is specific enough you can do like this example:

:~> git add ./*
:~> git commit -m "Initial commit"
10 files changed ...
...
create mode 100644 file10
:~>

That’s it. Now you can pull this directory from where ever you want and commit to the stage directory until you are ready and can pull/push between computers. Have fun! 🙂

!WARNING!
There is (what I consider a really serious) bug in Git. This is how it happens:

  1. Create two directories E.g. foo/ and bar/ and initiate a git repo in both
  2. Create a file “baz” in both directories E.g. ‘echo “data” > foo/baz’ and ‘touch bar/baz’
  3. Go to foo/ and add the file to the git repo and commit it. ‘git add baz; git commit -m “whatever” ‘
  4. Go to bar/ and do a pull; ‘git pull ../foo/’ Git will silently overwrite the file in bar/

This behaviour is utterly stupid; git should warn of existing files or, even better, try to merge the files! I haven’t found where to submit a bug report yet but I will as soon as I find out where.

Hope this is useful 🙂 Good luck!

Advertisements
Categories: Philosophy, Software