How to use Git with Subversion

If you know Git, then you know very well its productivity benefits over other source code control like Subversion. If you work on a project that uses Git entirely, then good for you. However, some software projects are still using Subversion. If you end up working on a project that still uses Subversion, you don’t have to use Subversion entirely to do your work. You can use Git for almost all of your source code control tasks. And all those tasks can remain transparent to the Subversion repository and to the other people on the project that uses Subversion. All they’ll ever see is that you are checking out and then committing back to the Subversion repository like from any svn client. But in reality it was from Git. All they will ever find out is how fast you perform merges, rebases, and cherry-picks – because Git makes all that easy compared to Subversion. They will also be amazed by how clean your commits are.

To use Git with Subversion, you use the git-svn-clone command to checkout the Subversion repository. Once checked-out, everything is now in Git and you can work on your code entirely using Git. To commit changes back to Subversion, you use the git-svn-dcommit command.

Below is a sample workflow on how to use Git with SVN.

1. Clone the Subversion repository

mkdir -p ~/work/
cd ~/work/
git svn clone project.git

The svn url is the same url you use when you run the svn checkout command. The subversion repository does not need to be the trunk. It could be a branch.

2. Create a branch off the master

cd ~/work/project.git
git checkout -b feature_branch master

By default, the cloned repository will create a master branch. Although you can work on the master branch directly, it is preferable that you create a branch off it and then use this branch to make your changes. Think of it like a feature branch. The reason behind doing this is so that you can maintain the master branch that will be in-sync with the svn repository while you have a separate branch to perform your work. This will allow you to regularly pull updates from the svn repository through the master branch and then merge or pick only some commits into your feature branch. And when you are ready to commit your feature branch to the svn repository, merge or rebase the feature branch to the master branch and commit it to subversion. The master branch serves as a gateway or staging between the Subversion repository and your Git branches.

3. Make code changes to the feature_branch

(edit files)
git add file1 file2 ...
git commit -m "trying this optionA..."

(test application)
(edit more files)
git add file1 file2 file3 ...
git commit -m "optionA didn't work. trying optionB..."
(test application)

While working on the feature-branch, you can perform as many commits as you want. In fact, this is preferable so that you can save your work and push them to a remote repository to serve as a backup. You don’t have to hurry to commit your changes back to subversion if you don’t need to just because you want to save your changes. You can use a remote git repository to serve as another site and save your work.

The commits you made can also be used if you need to revert back to them. Don’t worry about doing many commits even silly ones because when you finally commit your changes back to the svn repository, you will have the opportunity to clean them up before they get committed into subversion. This includes the comments in every commit. You will have the opportunity to modify them later when you are ready to commit to Subversion.

4. Merge changes from Subversion

If you need to get new updates from Subversion into your feature-branch, run the following steps:

# Switch to the master branch
git checkout master

# Pull changes from Subversion
git svn rebase

# Switch back to the feature branch and run rebase off master
git checkout feature-branch
git rebase -i master

The rebase command above will merge the master branch into the feature branch but in such a way that your commits are put back on top of the commits of the updates in the master. This rebase is a kind of merge where it’s like you’ve just created the feature-branch off the repository and then made your commits after it. Even though your commits were made earlier than the new updates in Subversion, a rebase will make it in such a way that your commits were made after. When you run git log to view the history, you will see it like your commits in the feature-branch were done after the updates from Subversion.

The -i option will perform an interactive operation under a VI session. It will show the history of commits in your feature branch. Below is a sample output.

pick eab79aa Made change #1
pick 97bd219 Made change #2
pick 5f7122f Made change #3
pick 33a8b6c Made change #4

# Rebase 59ef334..43910eb onto 59ef334 (4 command(s))
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# These lines can be re-ordered; they are executed from top to bottom.
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
# Note that empty commits are commented out

You may accept the commits as is or modify the commits by merging them together as fewer commits or one single commit for all.

Replace the word “pick” with “squash” to remove the commit from the history. Only the commit entry will be deleted but its changes will be merged into the previous commit. You can use the “s” letter as a shortcut for squash.

pick eab79aa Made change #1
squash 97bd219 Made change #2
squash 5f7122f Made change #3
squash 33a8b6c Made change #4

Repeat the rebase process every time you need to pull updates from the Subversion repository.

When doing a rebase, you can squash commits now to clean up your git commits or use the actual commit comments you wanted to appear in Subversion. You don’t have to because you can do this later when you are ready to commit to Subversion. I usually reserve this in the final rebase unless the number of commits have become too many to maintain and I see no need to revert to or use any of them.

One thing to be aware about rebase is that it will change the history of your feature-branch. If you are pushing your feature branch to a remote git repository, you will need to use the –force option. This should not be problem if you are the only one using this branch. This becomes an issue when the feature-branch is shared by more than one git use and also performing updates.

5. Merge the feature branch to the master and commit to Subversion

When you are ready to commit your changes back to Subversion, merge your feature branch to master and commit to svn.

git checkout master
git svn rebase
git merge feature_branch
git svn dcommit

6. Continue using the feature branch

You can still continue to use your feature-branch even after committing it to Subversion. Same process applies. You update your master from Subversion and rebase your feature branch off it. This is to ensure that your feature branch is updated with the Subversion repository and that any new commits you make goes on top just like a freshly created branch off the Subversion repository.

# make sure you're on master branch
git checkout master

# Update master branch with latest from Subversion
git svn rebase

# switch to feature-branch and rebase from master
git checkout feature-branch
git rebase -i master

Merging or cherry-picking changes between Subversion branches

Another most commonly used workflow in source code control is merging between branches. This could be a full merge or partial merge like selecting only certain changes (commits) from one branch and putting them to another. This workflow is commonly used when you need to port some fixes from one branch to another without necessarily merging everything. All of this be done easily using Git.

To do this in Git, do a git-svn-clone of each of the branches you need to merge or cherry-pick commits. This will create a Git repository for each Subversion branch. For each git repository, create a branch for each master and then push them to a common Git repository. Once they are in a common repository, you can then query the commits for each and perform cherry-pick merges. Below is a step-by-step example.

1. Clone each Subversion branch

# Clone Subversion branchA
cd ~/work
git svn clone branchA.git

# Clone Subversion branchB
git svn clone branchB.git

# create a Git branch for each
cd ~/work/branchA.git
git checkout -b branchA master

cd ~/work/branchB.git
git checkout -b branchB master

You now have two directories and each is a Git repository for each Subversion branches A and B.

2. Create a common Git repository

cd ~/work
git init common.git

3. Set common.git as a remote repository for branchA.git and branchB.git

cd ~/work/branchA.git
git remote add origin ~/work/common.git

cd ~/work/branchB.git
git remote add origin ~/work/common.git

4. Push branchA and branchB into the common repository

cd ~/work/branchA.git
git push origin branchA

cd ~/work/branchB.git
git push origin branchB

5. Go to the common repository

From the common repository, if you need to merge changes from branchB into BranchA, checkout branchA and merge or cherry-pick commits from branchB.

cd ~/work/common.git
git checkout branchA

# view commits in branchB to find which one you wish to merge
git log branchB
git cherry-pick e065b0230d7b368cfb534fc1818d55adc8911e2c

The cherry-pick command above will merge that commit in branchB into branchA.

6. Commit the merged changes in branchA into Subversion

Go to branchA.git repository and pull the merged changes from common.git

# Go to branchA.git and run git-fetch
cd ~/work/branchA.git
git fetch
git checkout branchA
git pull origin branchA

7. Commit merged changes in branchA into Subversion

To commit back to Subversion, first update the master branch with the latest from the Subversion repository by running git-svn-rebase.

cd ~/work/branchA.git
git checkout master
git svn rebase

Switch to branchA and perform a rebase off the master so that your commits will go on top of the latest SVN commits.

cd ~/work/branchA.git
git checkout branchA
git rebase -i master

Finally, merge branchA into master and commit to svn.

git checkout master
git merge branchA
git svn dcommit

It looks complicated with a lot of steps. But I assure you that once you have understood the concept, you’d realize how elegant and powerful merges and cherry-picks can be done using Git.

You don’t need to clone for each Subversion branch. You can run a single clone for both branches. The git-svn-dcommit will cover for all branches (all or selectable) in the Subversion repository. This should work well too and could be simpler since you don’t have to maintain many repositories. But I prefer separate repositories for each svn branch so that I can maintain isolation between svn branches while working in Git.