Mar 2nd, 2009
An efficient workflow for developers in Agile teams that handles features and bugs while keeping a clean and sane history.
At Hashrocket we use git both internally and in our Agile mentoring and training. Git gives us the flexibility to design a version control workflow that meets the needs of either a fully Agile team or a team that is transitioning towards an Agile process.
There are many usable git workflows. Indeed, git is really "a tool for designing VCS workflows" rather than a Version Control System itself. Or, as Linus would say, git is just a stupid content tracker.
This is by no means a normative document or my attempt to define The One True Workflow. I have found this workflow to be productive and relatively painless, especially for teams that are still learning and transitioning towards a more Agile process. I invite you to comment below and describe the git workflow that works best for you.
Anyone interested in using git in an Agile environment should read Josh Susser's Agile Git and the Story Branch Pattern. This workflow is based largely on his.
The process of squashing commits into more atomic and incremental units has
been described by Justin Reagor in A "Squash" WorkFlow in Git. Justin's post
also references a git rebase -i
walkthrough by MadCoder that provides a good
explanation the interactive rebase process.
Thanks also to Rick Bradley of OG Consulting and Josh Susser of Pivotal Labs for many fruitful and often hilarious discussions about git, Agile and ponies.
Our git feature development workflow consists of these steps:
- Pull to update your local master
- Check out a feature branch
- Do work in your feature branch, committing early and often
- Rebase frequently to incorporate upstream changes
- Interactive rebase (squash) your commits
- Merge your changes with master
- Push your changes to the upstream
First, and while in your master branch (git checkout master
), pull in the
most recent changes:
git pull origin master
This should never create a merge commit because we are never working directly in master.
NB: Whenever you perform a pull, merge or rebase, make sure that you run tests directly afterwards. Git may not show any conflicts but that doesn't mean that two changes are compatible. Also run tests before you commit (of course).
We begin with the topmost available story in Pivotal Tracker. Let's say that it's #3275: User Can Add A Comment To a Post. First, check out a feature branch named with the story id and a short, descriptive title:
git checkout -b 3275-add-commenting
The id allows us to easily track this branch back to the story that spawned it. The title is there to give us humans a little hint as to what's in it. Do some work on this branch, committing early and often (for instance, whenever your tests pass). Rebase against the upstream frequently to prevent your branch from diverging significantly:
git fetch origin master
git rebase origin/master
NB: This is often done by checking master out and pulling, but this method requires extra steps:
git checkout master
git pull
git checkout 3275-add-commenting
git rebase master
Once work on the feature is complete, you will have a branch with a lot of small commits like "adding a model and a migration", "adding a controller and some views", "oh crap - adding tests" and so on. This is useful while developing but larger, incremental commits are more easier to maintain. We will use an interactive rebase to squash them together. Also, squashing these commits together will allow us to pretend that we wrote the tests first…
We want the rebase to affect only the commits we've made to this branch, not the commits that exist on the upstream. To ensure that we only deal with the "local" commits, use:
git rebase -i origin/master
Git will display an editor window with a list of the commits to be modified, something like:
pick 3dcd585 Adding Comment model, migrations, spec
pick 9f5c362 Adding Comment controller, helper, spec
pick dcd4813 Adding Comment relationship with Post
pick 977a754 Comment belongs to a User
pick 9ea48e3 Comment form on Post show page
Now we tell git what we to do. Change these lines to:
pick 3dcd585 Adding Comment model, migrations, spec
squash 9f5c362 Adding Comment controller, helper, spec
squash dcd4813 Adding Comment relationship with Post
squash 977a754 Comment belongs to a User
squash 9ea48e3 Comment form on Post show page
Save and close the file. This will squash these commits together into one commit and present us with a new editor window where we can give the new commit a message. We'll use the story id and title for the subject and list the original commit messages in the body:
[#3275] User Can Add A Comment To a Post
* Adding Comment model, migrations, spec
* Adding Comment controller, helper, spec
* Adding Comment relationship with Post
* Comment belongs to a User
* Comment form on Post show page
This also follows Tim Pope's git commit message best practices. Now, save and close your editor. This commit is now ready to be merged back into master. First rebase against any recent changes in the upstream. Then merge your changes back into master:
git checkout master
git merge 3275-add-commenting
And finally, push your changes to the upstream:
git push origin master
Bugfixes will use the same workflow as feature work. Name your bugfix branch
after the bug id and give it a descriptive name. Prefix the branch name with
"bug" to help you keep track of them, for instance: bug-3845-empty-comments- allowed
.
Do work in your bugfix branch, committing early and often. Rebase frequently against the upstream and again when you are finished. Then, use an interactive rebase to squash all the commits together. Use the bug id and title for the subject of the new commit. Include "BUG" or "BUGFIX" to make these commits easier to identify. For instance:
[#3278] BUGFIX: Empty Comments Should Not Be Allowed
NB: With a bugfix, squash the commits down into one and exactly one commit that completely represents that bugfix. Half of a bugfix is useless.
In a truly Agile process, as Josh Susser explained to me, you simply deploy directly from your latest "green" build. While this may seem impossible, there are real world examples of the viability of Continuous Deployment.
For most of the teams we train, this sort of extreme agility is a goal rather than a way of life. They are transitioning towards a fully Agile process from a more rigorous one and often have a significant investment in Quality Assurance. For these teams, I recommend a compromise solution: Use a remote QA branch that is fast-forward merged from the latest "green" (all CI tests pass) master build for QA deployments.
If you don't already have a QA branch, create one from the current master (assuming it's green) and then push it to your git host:
git checkout -b qa
git push origin qa:refs/heads/qa
To update an existing QA branch with the latest changes from master, do this:
git checkout qa
git merge master
git push origin qa
Ensure that the master branch is green when you merge. Also ensure that this merge is always a Fast Forward merge. If it is not, it means that commits have been made in the QA branch that are not in the master branch. QA should be merged into from master only, never worked on directly.
While an Agile team will be deploying continuously, most teams will require a more rigorous vetting process before a build is deployed to production. Tags provide a simple way for QA to vet a particular build.
Once QA has signed off on a build as ready for production, a tag should be
made and deployed. In git, a tag can be created with git tag <name>
. It is
important that tag naming is consistent and it is useful if they sort in a
meaningful way (latest last, for instance). Two good options are a timestamp
or product version. Use whichever is more meaningful to your team.
The process of creating a timestamped production tag can be automated with a simple script or a git alias like this one:
git config alias.datetag '!git tag `date "+%Y%m%d%H%M"`'
With this alias, git datetag
will create a new tag from HEAD
with the
current timestamp. You will want to be in the QA branch. If the QA branch has
moved beyond the last commit vetted by your QA team, be sure to checkout that
commit first.
Posted by Rein Henrichs Mar 2nd, 2009
Correct me if I'm wrong, but I belive that this:
is equivalent to
git pull --rebase=i origin master
Also, since you rebased onto
origin/main
above, you are directly ahead of main. This means merging/rebasing is just a fast-forward. You mentioned a similar equivalence in your "NB: This is often done by . . . ". In that case, the following:should be equivalent to
git rebase HEAD main
, which doesn't require a switch. Since you're already on your feature branch, you can just useHEAD
.This means that once you want to push commits, you can use three commands to do it: