CC by-nd by W. T. L.

I won’t go here in depth on how to use git, the official hub has already tutorials to get you started. I’m more interested here in the workflow you can apply and the tools you can use when working on Mono with git-svn (although it can be mostly used in a general way too).

Note: this post is heavily based on the materials written by the accessibility team.

Note²: Prior of everything, your should first read this guide which explains how to setup your git-svn local copy using an existing git mirror. At this point, I expect that you have successfully completed that step.

Mirroring your work

The workflow I will describe here is rather destructive as you are going to end up merging some bits of git history when sending your commit to Subversion. As such, I strongly advise that you have another git mirror of your work somewhere to act as a backup with all your history.

You could either go public with your own server/repo.or.cz/github/gitorious/whatever or simply use a local copy situated e.g. on another drive.

In any case, this repository is not really meant for public consumption (i.e. people directly working on your git tree) as it’s going to be quite unstable with frequent history breaks.

When you are decided on where you want to host your mirror, add a remote to its location with the git remote command :

git remote add name foo@yourdomain.org:mcs.git

When you want to update your remote copy, just issue git push with the remote name and mirror option :

git push --mirror name

Synchronizing with trunk

The first thing to know is actually how to pull Subversion revisions back into your local repository. As with any command that have to deal with Subversion, the common prefix to use is git svn. This action must be done on the master branch so if you are on another one, switch back to master before doing anything else with git checkout.

Here, the command we are interested in is git svn rebase. This command will actually fetch the revisions from trunk, convert them to git commits and replay your local changes (if any) on top of them :

git svn rebase

Making changes

The equivalent of trunk in git is the master branch. This branch should remain clean of any change and should only be used when you are ready to commit your work to Subversion.

In git, branches are everywhere and are the most straightforward way to organize your changes hierarchically. As such, anything you plan to do should be separated in its own branch with a name like feature-xyz. You can also have feature branch that depends on another feature branch, I will talk about these one later and especially how to merge them back.

The command :

git checkout -b feature-xyz

Will switch you to a new branch where you can happily do your stuff.

What works well here is to follow the scheme : code a piece → commit → test → fix → commit. It will especially helps when you have to crawl back through your history at a later point with, for instance, git bissect.

Notice that commit message style at this point don’t matter, use the style that suit you the best because you are the only one who will read them.

During the lifetime of your development branch, it’s likely that some change introduced in Subversion will conflict with what you have done. As such, it’s always a good idea to frequently sync your local copy with Subversion (see above) and then rebase your development branches on top of master.

The git rebase command exactly serves that purpose and allows to rewrite partially or totally the history of a branch. In practise, this is as simple as issuing from the development branch :

git rebase master

As you see, git rebase is powerful tool but it’s also a destructive one in the sense that it rewrite your history. Rewriting history is the sort of thing that make git crazy when you are trying to pull from a repository modified that way. This is why your git backup should be considered unstable and why you have to always use a mirror or a force option with git push.

Merging work and sending it to Subversion

Let’s say your are happy with what you did, now you would like to send all that stuff to Subversion. Normally, your branch should be filled with small commits with message mainly consisting of “Ooops”, “Added part foo”  or “Debugging” which you definitely don’t want to see in the Subversion commit.

That’s why we are going to do a clean summary of the change you made, update the ChangeLogs accordingly and then use a pretty automagically generated log message for the Subversion commit.

First of all, switch back to master branch. Then issue the following command :

git merge --squash feature-xyz

What this do is that it takes the most recent version of the branch tree, generate a diff and then apply it to your master tree without committing. If you check with git status, you will see that these change are notified as “Going to be committed”.

That’s when we use our first tool : clng.py.

I suggest making an alias to it or putting it in a PATH directory to be able to invoke it directly from the command line.

As you can notice, the script expects some environment variable to be set. EDITOR tells which editor you want to use to edit ChangeLog (e.g. emacs), CHANGE_LOG_NAME contains the name that should be used in the ChangeLog (e.g. John Doe) and, finally, CHANGE_LOG_EMAIL_ADDRESS contains an email address that will be put in the ChangeLog next to your name (e.g. john.doe@foobar.com).

When you have set up those environment variable properly, then from the root of the repository (i.e. where the .git directory is), just call the script with no parameter which will prompt you to edit the ChangeLog corresponding to the files you have changed. That time, use a meaningful entry.

When all ChangeLog have been edited, you still have the option to fine tune them (if you made a typo for instance). When you are finished, call the following command to validate the ChangeLogs :

git add -u

Here now comes the second script, clm.py, that will gather and pretty print what you added to each ChangeLog. It uses the same environment variables than previously.

Then, simply type git commit and copy&paste the commit message given by the script.

The two last steps are, first, to run again git svn rebase to make sure that no change got in between while we were doing the merge and, finally, launch the dcommit command to issue your commit to Subversion :

git svn dcommit

Damn, I stumbled upon a bug

It’s quite usual that during development time, you will notice several bugs in the code that is already on Subversion. Of course you would like to commit the fix right away because it’s an easy enough one. Only problem is that you are, most of time, stuck in the middle of something else with several changes in your working tree that haven’t been committed yet !

Enter git stash. This command will create a temporary branch were all your current changes are committed and then clean your working tree. That way you can painlessly switch back to your bug fixing branch, make some commits to solve the problem, and then push the fix to trunk with the same procedure as described above.

Now, it would be nice if what you were doing before take advantage of that fix (maybe it’s even a prerequisite). Good news is that you can use the same trick as in the sync section with git rebase to make your branch starts from the commit you just created.

Working with a Subversion branch

When you commit a fix, you probably want it to also live in the current stable branch of your software (Mono in our case). That require two things. First, you have to tell git-svn where the branch live and, second, you have to backport the fix.

Normally when you clone the repository in the guide above, you also get all the remote branches with it. If that’s the case then all is good and you can work from that point. If you don’t have the remote branch you are interested in, two options. Either you use git fetch to retrieve all the missing symbols (can take a good deal of time) or you directly put in the config the path of the branch you are interested in. Here is for instance the section to setup your Mono 2.6 branch :

[svn-remote "mono-2.6"]
url = svn+ssh://jlaval@mono-cvs.ximian.com/source
fetch = branches/mono-2-6/mcs:refs/remotes/git-svn/mono-2-6

[svn-remote "mono-2.6"]
url = svn+ssh://foo@mono-cvs.ximian.com/source
fetch = branches/mono-2-6/mcs:refs/remotes/git-svn/mono-2-6

[branch "mono-2.6"]
remote = .
merge = refs/remotes/git-svn/mono-2-6

The only step remaining is to duplicate the change you made to trunk to this maintenance branch which can be easily achieved with the git cherry-pick command. After the (eventual) conflicts are resolved, just issue git svn dcommit to validate this change.

Merging branch of branch

Sometimes, it happens that you are developing two things at the same time and that one of it is based on the second which translate by the fact that one of the branch depends on the other branch.

In that case, you are certainly going to end up committing the first branch first, continue a bit polishing the second one and ultimately commit it too. Problem is that, when the first branch get committed, the second one should in turn follows trunk/master happening.

Fortunately, git rebase comes again to the rescue with the onto switch. Simply merge and commit the first branch as described in the section above and then, from the second development branch, issue :

git rebase --onto master

It will move your second branch to depend on master which should happen flawlessly since the first changes are now mainline.

Conclusion

This post is of course far from exhaustive and if you have any more tip, share it in the comments.