Example Git Workflows: Maintaining a Long Lived Topic Branch

18 Apr 2008

In our previous post in the series, we took a look at how a Rails core committer could best pull in a branch submitted by a contributer. Today, we’re going to be looking at things from the opposite side: How a contributer can best maintain a branch intended for pulling.

Note that contrary to the initial assumptions of many, git pull is not necessarily the be-all end-all solution for getting your work included upstream. For simple bug fixes and features, it’s generally easier for all involved if you submit git format-patch --stdout output. The real gains of a merge based workflow come when you start to collaborate with others on a topic branch. Having said that, let’s get on with the example.

Let’s start with the basics. We’ll be using GitHub for the upstream repository in this example. If you haven’t already done, so grab the clone the latest upstream over at GitHub.

$ git clone git://github.com/rails/rails.git
$ cd rails

Next, set up a public repository. With GitHub, this is straightforward: sign in and click the fork button on the rails/rails.git repository page. Once that’s done, take note of your push URL and add it to your repository. Here, we use the name mine, to distinguish it from the origin remote which is the upstream we cloned from.

$ git remote add mine git@github.com:yourname/rails.git

With that set up, we can get to work. Start by checking out a branch for your feature. Below, we use git fetch to download the latest updates, then create a new branch my_wonderful_feature based off the master branch of the remote origin, which is our upstream.

$ git fetch
$ git checkout -b my_wonderful_feature origin/master

Obviously, unless it’s a bug fix or feature targeted for the stable line, we should start by building off the tip of the upstream master branch, right? Well, not necessarily; that’s really a holdover thought process from Subversion. There are several advantages to picking a recent tag like v2.0.2 and building off of that instead:

Of course, this is a trade off, as there are advantages to working on edge as well:

The right choice depends both on your feature and the current state of the upstream. Given that the last release of Rails happened back in Subversion and the next is just around the corner, it generally makes sense right now to base things off of a recent commit. However, once 2.1 hits, I’d seriously advise people to consider branching off of v2.1.0 instead. Even if you do choose the edge route, don’t be afraid to back up a few revisions if the current one has broken tests or otherwise doesn’t suit your needs.

Okay, so you’ve thought it through and decided on origin/master for the my_wonderful_feature branch you’re implementing. You’ve created your branch, done a few commits, and you’re ready to publicize it. Using the mine remote we created earlier, this is a simple process:

$ git push mine my_wonderful_feature

If you want the public branch name to be named differently, that’s a snap too:

$ git push mine my_wonderful_feature:my_humble_request

With that done, your branch is ready to be pulled. Here’s the command a core committer would use to merge your work, which can be mentioned in the Lighthouse ticket, or on #rails-contrib, or however you want to communicate it to the powers that be:

$ git pull git://github.com/yourname/rails.git my_wonderful_feature

Great. So a few days later, you get some feedback, and decide to do some further work on your branch. If you’re a Subversion veteran, your first reaction is probably to update to the latest edge before continuing:

$ git pull
$ git merge origin/master

But wait! Is there a reason you need to update? Frequently updating complicates the history graph and adds noise to the logs:

Merge branch 'master' of git://github.com/rails/rails

There are certainly times when it’s appropriate to merge the latest upstream, like if there are conflicts that need to be resolved before it can be added upstream. When these times come, go ahead and merge, but give a descriptive commit message:

$ git merge v2.0.3 -m 'Synchronize with 2.0.3 release'
$ git merge origin/master -m 'Leverage new Rails.public_path method'
$ git merge origin/master -m 'Resolve conflicts with upstream'

If you’d like to try merging (for example, to test that tests still pass) but discard it when you’re done (for example, you’re planning on doing more work on the branch before publishing), use the following pattern. HEAD@{1} means “the previous commit referenced by HEAD”, that is, the commit before the merge.

$ git merge origin/master
$ rake test
$ git reset --hard HEAD@{1}

All right, you made the needed changes and your contribution was merged upstream. Here’s the syntax to delete a branch from your remote repository so it doesn’t clutter things up:

$ git push mine :my_wonderful_feature