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:
- No churn of constantly updating to the latest.
- Not having to worry about breakages in the latest edge. If a test breaks, you know (in theory) that you’re the one that broke it.
- People can easily test your changes in their application without testing everything else introduced by the particular revision of edge that you forked.
Of course, this is a trade off, as there are advantages to working on edge as well:
- Get to leverage the very latest infrastructure changes in Rails.
- Lesser chance of something that works in your version breaking once it’s merged into a later revision.
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