Skip to content

Instantly share code, notes, and snippets.

@leesmith
Created August 29, 2012 15:39
Show Gist options
  • Save leesmith/3514545 to your computer and use it in GitHub Desktop.
Save leesmith/3514545 to your computer and use it in GitHub Desktop.
Agile git and the story branch pattern

-- December 18, 2008 at 23:39 PST

I've been using git for source code management for over a year now and I'm totally hooked. I won't rave about all the usual reasons WhyGitIsBetterThanX since it's been done already. Instead, I'm going to share how I use git for easy agile development.

The basic idea is to never do anything in the master branch except use it to move changes between the remote repo and local branches. Keeping master clean takes very little effort and will save your bacon when you get into trouble. The example I'll use here is working on a story to render title text in a bold style on a page.

1. Locate story "titles are in bold text" in Pivotal Tracker and click Start.

What do you mean you're not using Pivotal Tracker?

2. Get going with a clean start.

$ git checkout master
$ git pull
$ rake db:migrate   # if there are new migrations
$ rake              # if you don't have a CI server

3. Make a branch to work in.

$ git checkout -b bold_titles

4. Test-drive the story.

Since you are committing locally, you can commit often. Sometimes I like to commit every time I get a test to pass, or after crucial steps in a refactoring.

$ git commit -am "Don't bold empty titles"

We should all know the advantages of frequent commits: when an experiment fails, it's easy to get back to a previous state where things worked; or when refactoring, you can see how things were before you started changing things around. With git you can also do things like cherry-pick commits, so if you want to grab a single test and its implementation and extract it as a patch or whatever, you can do that.

When you are done...

$ rake
$ git commit -am "Render titles with bold style"

5. Merge with master

When you're done with the story, you need to integrate your changes with the master branch. You'll have to fetch and merge the changes from origin/master into your branch.

a. Review changes

I like to review changes to make sure I didn't leave any debugging cruft in, and as a final check on code quality.

$ git diff master

b. Merge changes

$ git checkout master
$ git pull
$ git checkout bold_titles
$ git rebase master

The four commands above may seem a bit convoluted, but all you're really doing is merging updates from the remote repo to your story branch by way of the master branch. Now, if you have any merge conflicts, they are all in the story branch where you can work on resolving them in your normal work environment without disturbing the master branch.

If you took enough time resolving merge conflicts that someone else checked in changes to the remote repo, repeat this step until you're fully merged locally.

Also, notice I used rebase instead of merge to integrate the master branch changes, but that choice is up to you. Whether you use rebase or merge is a matter of convention for your project. Doing a rebase will replay your changes on top of the changes you just merged with. That should have a net effect of zero on the final product but has the advantage of keeping all your changes contiguous in the history, and won't generate a separate merge commit when you merge back to the master branch since the changes come in as a fast-forward.

You can also rebase with the -i (or --interactive) option to squash several (or all) of your commits into a single commit, or to organize your changes as a reduced number of commits. Frequent commits are great to manage your own work and give many checkpoints to roll back to if needed. But adding dozens of commits to the project for a simple feature can be overwhelming for others on your team, and can make it difficult to navigate or understand the project history. For example:

$ git rebase -i master

That will show you an $EDITOR session like this:

# Rebasing 1234abc..3333ccc onto 1234abc
#
# Commands:
#  pick = use commit
#  edit = use commit, but stop for amending
#  squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
pick 1111aaa Use <strong> tag to bold titles
pick 2222bbb Don't render empty titles as bold
pick 3333ccc Use font-weight property on h1.title instead of <strong>

The commits are listed in chronological order, oldest on top. If you want to combine several commits into one, edit the word pick at the start of the line to say squash, and that commit will be merged with the one above it. Since you are seeing all your commits and their messages, you can probably say something useful about the whole set as you package it for pushing to the shared repo. Sometimes you'll want to squash down to just one commit, others you may want to structure the package as a small number of related commits, say one commit for your feature addition, and another for a refactoring your feature relies on.

Important: Rebasing rewrites history and should never be done to reorder changes you have previously pushed to another repo or shared publicly.

c. Be green

Once done merging, make sure you're still green.

$ rake

d. Merge early, merge often

If the work on your branch takes long enough, you might want to perform a merge several times. As other people push to origin/master, you can pull those changes and integrate them into your branch incrementally, rather than waiting until the end when you're done. Whether to do this is a judgement call, and would depend on factors like the nature of the work you're doing the the kind of changes you'd be integrating. But one of the more valuable practices of agile development is continuous integration, and spending too much time accumulating change on a branch that is cut off from the rest of the development stream can eventually make for a difficult merge at the end of your story.

6. Push

Once you have integrated your changes with those from master, you can merge them back to the master branch and from there to the remote repo.

$ git checkout master
$ git merge bold_titles
$ git push

7. Cleanup

$ git branch -d bold_titles

Delete the story branch, click Finish on the story in Pivotal Tracker, then get up and stretch and get some water.

Caution

Probably the biggest gotcha particular to Rails development on more than one branch is dealing with database migrations. It's easy to get confused between the state of the database and which migrations are in what branch. I'll take one of two approaches, depending on how complicated things are. One is to rollback the migration on the current branch before switching to another branch that doesn't have that migration yet, then run pending migrations on the new branch after switching. But if you're going to be switching around branches with different migrations a lot, the best thing is to integrate the migration changes so they exist on both branches and it ceases to be an issue.

Discussion

Okay that's a lot of stuff going on. What's the point? Well, the point is dealing with the unintended. (I was going to say "unexpected", but if you're developing software or working on a team, you should expect crazy things to occur occasionally.) There is obviously overhead in managing multiple branches, and you may wonder what you're getting for the cost of that overhead. There are a couple things that come to mind that I'll mention here, but I'd like to hear more in the comments from people about what situations they deal with this way (or other ways).

The first thing this approach helps with is being able to make frequent commits without breaking the build or disrupting the work of teammates. Working with a centralized SCM system like Subversion can force you to choose between checkpointing your work and keeping the state of the project consistent. I know agile developers who swear that making frequent commits to the shared repo is the way to go, and just deal with having to keep the build green and functional on every commit. But I find that approach too constraining, and it can impose an uncomfortable style on how I do my work. I find being able to do frequent commits without worrying about disrupting my teammates' work with half-finished features lets me concentrate on getting things done, instead of thinking about how big a change I can make without messing up someone else's work. And I also don't have to worry about the converse either. Don't you hate pulling down changes that include a migration and wondering if the relevant model changes to let that migration do the right thing have been made yet?

But why the separate story branches? Isn't it just as convenient to do all your work in your local master branch and do your local commits there? Up to a point, sure. But keeping a separate branch for work means that you always have a good baseline around for reference, it lets you rebase to manage your changesets easily, and it makes dealing with merge conflicts a bit more sane.

And if you have to share or backup the state of your work in progress, it's very easy to push your local branch to a branch in the remote repo without disrupting anything.

There's also the advantage that it's easier to deal with being interrupted. Say you're working on a story that's going to take you all day and someone finds a nasty bug in production. Time to drop everything and get it fixed right away! If you have your work in progress on a branch, it's simple to do a checkpoint commit, switch back to master, create a new branch for the bugfix, fix the bug, push the fix, then get right back to where you were before.

Push

I've been using the story branch pattern for development for most of the last year, and have found it useful, convenient and a lifesaver when things get weird. I've also watched teammates that do their work in the master branch, and it is more work for them to deal with issues and avoid making unintended messes. Like all useful practices it takes a little bit of effort up front, but it saves a lot more effort when it really matters.

@jasonm23
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment