git2svn, a little bit of git voodoo.
Interacting between git and subversion is quite common. The git command git-svn can be used to import existing subversion repositories into git and commit synchronize commits between two repositories. While importing a subversion repository into git is common, importing an git repository into subversion is quite unusual. There are various tools to do so, but most of them require direct access to the svn repository. In fact it is possible to use git-svn to get that job done. Frankly it requires some git voodoo and knowledge of the graft-feature. We’ll do a sample import using grafts, but before we start, we’ll explain the theory behind the import.
We assume that we have a remote subversion repository. It is empty and we want to import an existing git repository into that subversion repository.
Our general idea to import the git repository into subversion is easy. We import the current subversion repository and then push the changes from the our existing git repository on top of that imported subversion repository. We than can use git-svn to commit the changes back to the subversion repository. Even thought this sound simple, it is not. Part of the problem is the fact that subversion usually deals with linear history, while git does not. (1) We have to keep in mind that we need to linearize the git repository while pushing changes to the imported subversion repository. (2) we have to make sure that every commit from the original git repository is pushed to the imported repository as a descendant of the last commit from the subversion repository to avoid conflicts. (3) the imported subversion repository and the existing git repository are not related to each other. As they don’t have a common ancestor in terms of a common commit (with according sha1s), we are not able to merge both repositories easily. We will use a technique called grafts to create a fake common ancestor, which will make it possible to merge both repositories.
1. Importing the svn repository into git
Our first step is importing the svn repository into git. We are using git-svn for that task.
# git svn clone svn://path/to/repo
We will get a git repository with a branch called master. As we later want to push our git repository on top of the imported svn repository, we need to remember the current id of the repositories head (we’ll discuss later why we need that). The command git rev-parse will give us the necessary information.
# git rev-parse HEAD
2. Importing the git repository into our imported svn repository
Let’s start with the voodoo part. We want to merge our imported svn repository and our existing git repository. But git doesn’t know how to merge them because both repositories don’t share a common ancestor. We can solve this problems by creating a fake common ancestor, but to do so we need all commits of both repositories in one repository. Therefore we fetch the content of the git repository into our imported subversion repository. To fetch the existing git repository into our imported svn repository we use the following git-fetch command:
# git fetch /path/to/git/repository master:tomerge
The resulting repository will look that way:
Both branches the one from the existing git repository and the svn imported one exist in parallel in the same repository.
3. Tell git what to merge…
We have two parallel branches. We now create the fake ancestor. This is called a graft. Unfortunately git’s grafts are not very well documented. Let’s visualize which commits we want to graft on each other:
So we will graft the existing git branch on top of the svn branch. The fake ancestor will be the HEAD of the svn branch and the first commit of the existing git branch. To get the first commit of the existing git branch we use the following command:
# git rev-list --reverse tomerge | head -n 1
Therefore the commit bab3… and 00f97… will be our grafts. We create the file .git/info/grafts with the content
We use gitk to check if our history seems to be linear. If everything is correct, just switch to the master branch and rebase your tomerge branch. We use rebase here to get a linear history. A merge might make it impossible for git-svn to push to the remote svn repository as imported commits cannot have commits which are not synchronized to the svn yet.
# git checkout master
# git rebase tomerge
# git svn dcommit