My goal as a developer is to deliver a great product in the least amount of time possible without sacrificing quality. In recent years I’ve refined my workflow to facilitate my needs. Having a strong workflow is an important factor when trying to produce a high quality deliverable. I’d like to highlight some of the things I’ve learned along the way that have helped me deliver good code.
That being said, I believe that you should own your workflow. What may work for you may not seem natural to another developer. There is no “right” way. The “right” way to me is the methodology that allows you to create something quickly with high quality and does not create problems for other developers.
One of the most important tools we work with here at Sprout is Mercurial. Coming from the git world, I wasn’t the biggest fan when I first started using it. I have since found a way of using it that’s intuitive to me. With that out of the way, Mercurial has grown on me. My workflow consists of four basic operations:
And repeat if necessary.
When starting a new project on an existing codebase, I fork our default branch. I then work on this forked clone as long as necessary until the feature is completed. By completed I mean that the project’s goals have been hit, all the unit tests for coverage have been written and pass, and the integration is working.
hg clone ssh://email@example.com/sproutsocial/my-repo my-repo-fork
Do Some Work
Make some commits, build something!
When I am confident that my local project/code is in a stable enough state, bugs have been smashed, and things won’t be changing, I then roll my code into a single logical changeset. The first step is to rebase the code onto upstream changes. This effectively replays my code on top of upstream changes; if there are any conflicts they will need be resolved during the rebase. Note, I always clone the repository that I’m rebasing to a backup copy in case the rebase goes bad. If you haven’t already, enable the rebase extension in your .hgrc file.
[extensions] rebase =
You may also need to change the phases of the changesets if they’ve been shared or pushed to another repository. I did indeed have to change the phases so for sake of this example here is the command I used to change my phases locally:
hg phase -f -d 5182
Essentially this tells Mercurial that the changesets are mutable. Since my code is shared on Bitbucket the phases of these changesets were set to “public”, so they are immutable. The command above forces “-f” them into the draft state “-d”.
Here is what a typical commit history looks like before the rebase. My changesets start at revision number 5182 with the parent being 5181.
You can see the parent here is 5181. I then run the rebase command:
hg pull --rebase ssh://firstname.lastname@example.org/sproutsocial/my-repo
After running the rebase command my commits are now on top of the upstream changes:
Notice how the revision number of the parent of my first changeset is now set to 5197 and my first commits revision number is 5198.
Collapse using Histedit
Now that my code is rebased on top of the upstream changes and all conflicts have been resolved it’s time to squash my historical commits into a single logical changeset. Again, be safe and make a cloned backup of your now rebased fork in case the histedit goes bad. You will also need to install the histedit .py script and enable the extension in your .hgrc file.
[extensions] histedit = /Users/rob/sprout/hg_repos/histedit/hg_histedit.py
Then simply find the revision number from which you want to collapse and get going.
hg histedit -r 5198
After running the histedit command I am presented with this menu:
I am presented with several options for each listed changeset. Typically I just fold all but the first changesets into the initial changset and leave the commit message history. In this case I want to change the first commit’s message to “Collapsed” to illustrate this example. After the histedit completes my history now looks as such:
What does this give you?
For me it gives us the ability to avoid merges in our main deployed branch. In case anything goes wrong when deploying we can simply strip the top-most commit (now a rebased and collapsed) from the repository and quickly revert back to a previous state without having to untangle merges. It also gives the ability to do a very simple diff operation on your forked repository against the main line code, highlighting which files have been touched and what changes are incoming.
There are certainly things I am hand-waiving here. In particular, often when rebasing one will encounter conflicts. This is unavoidable. If you were to merge the upstream changes into your local clone you would have to resolve conflicts as well. By doing a rebase you are forcing all conflict resolution to happen before moving your code onto the main deployed branch. You will also lose your commit history. For me this is well worth the price of having a single logical changeset. You can always leave your uncollapsed clone lying around in case you want the commit history.
That’s it! As I alluded to when I started, this may not be for you. I really like this workflow mostly because after a large project that spans several weeks or months, I have one logical commit to move to the deploy branch. Simple is beautiful.