I can still remember the sweat running down the back of my neck all these years later. I was a young, spry teenager with a full head of luxuriant hair sitting in AP English waiting for the teacher to call on me.
See today, this day, it was up to us to present our interpretation of a poem by Robert Frost, the well known 'The Road Not Taken'. I agonized over the twenty lines of iambic tetrameter in front of me at the last minute, hoping, praying, that something, ANYTHING would jump off the page at me and give me a topic to discuss. Why would the traveler not come back? Is he dying? Why is he sighing? Is he sad about the yellow wood? My mind was racing.
All I could remember thinking was, 'why on earth do I have to bother with this cryptic stuff?' I was fresh from the Cliff's Notes of Walden when now I had another piece of dusty old prose to analyze. 'How would this ever benefit me in the future? When am I ever going to need this??'
Well, it took seventeen years and one head of formerly-luxuriant hair, but I finally have a practical application for Frost's poem. And yes, while it may be a stretch, if you have a better explanation for how I spent my formative years reading hundred-year old literature, then I'm all ears.
Recently, a colleague and I were discussing version control. He had been using Git on a current project and I was using Mercurial. He then asked me a pointed question for which I now regrettably realize I didn't have a sufficient answer. He asked me, 'what sort of daily workflow do you follow?' I asked, 'what do you mean, like commit, push, pull, etc.?' and he replied, ‘yes’, basically asking how I maximize my efficiency in a distributed version control environment.
When I explained my rather pedestrian daily routine, he sent me a link to a post explaining an effective way to do it in Git. After reading, I felt like the same teenage dope that stood in front of the class and explained that the yellow wood in Frost's poem meant rebirth (it doesn't). I essentially had no real explanation at all. I simply wasn't prepared.
So, this is my attempt at constructing a daily workflow, Mercurial-style, while simultaneously making use of my years in English class. Now if anyone can provide a reason why I read the Red Badge of Courage, I’m all ears.
Two branches diverged in a local repo
And sorry I had to push from both
And be one developer, long I stood
And looked through the history as far as I could
To where they were cloned in the undergrowth
The first thing I noticed in the blog post about Git was that its purpose was a rebase workflow. It favored the approach of branching to contain and isolate all your incremental changes and commits locally. Then when they were ready for primetime, they were rebased to the main branch and inflicted on an unsuspecting populace. The only problem was that using branches for this sort of thing would not work in Mercurial. At least not effectively.
Branches in Mercurial represent a parallel path of development within the same repository. For example, a branch, 'Release1' is creating from the default where all developers can apply bug fixes for Release1, while at the same time the default branch represents 'Release2' and continues on with its new feature sets. These branches can be created on the central repository and then pulled down locally. If you are working on a Release1 fix, you simply update your working directory to Release1 locally. Working on a new Release2 feature? Update to default.
The problem lies in the realization that when it is time to push, ALL commits in BOTH branches will need to be pushed. Sure, there are extension-based ways around this with shelving and transplanting, but the out-of-the-box behavior is to send all commits on their way. This means that if your fixes for Release1 must go out the door now, then they're taking everything with them, including all those work-in-progress Release2 features you've committed locally.
So, the Mercurial way is actually a combination of branching and cloning. My approach is to clone my repository into as many child repos as is needed, say one for every active branch in the central repo. This way, you can push from a repo of your choosing, while still holding back any changesets you committed but aren't ready to unleash. So, taking our scenario above, the setup would look something like this:
Granted, because the clones are uhh, clones of the main local repo, which is in turn a clone of the central repo, they will contain both branches. However, all you need to do is make sure each is always updated to their appropriate branch and you never need worry about which branch you're in when making a change. Trust me, this is not always easy and has burned me quite a few times.
Then took the other, as just as fair,
And having perhaps the new feature
Because it was buggy and wanted flair
Though as for that the coding there
Had diverged it into its own new creature
So, now you have two repo clones that are based off of your main local repository, which in turn, is based off the central repo. The newly cloned repo is even smart enough to know from whence it came, so now you are free to make changes to this cloned repo, commit at will, and then when ready, the feature or the bug fix can be pushed to the main local repo.
Before you do however, you will want to pull from the central repo to your local and then in turn, pull into your cloned repo. This way, all merging that needs done will be performed on the cloned repo, leaving your main repo as the staging area for shoves back into the central one.
Mercurial's approach of requiring merge commits can leave the repository cluttered with numerous commits with the message, 'Merge' with no real content. In addition, each developer's local commits will also be pushed, so every 'Ugh, still broken', 'Bug fix', and 'DO NOT navigate to this page yet' commits will be bared to all. However, there is an answer to this, but its not an easy one to accomplish. Each of these nonsensical merges and interim commits can be combined into one changeset. You can perform this concatenation in the clone repos and then push a single, solitary, meaningful changeset (or several as long as they're meaningful) to the main repos. Take a look at this link for further reading on ConcatenatingChangesets.
And both that morning equally lay
In revisions no commit had trodden black
Oh, I kept the default for another day!
Yet knowing how testing leads to dismay,
I doubted if I should ever update back
Once this setup is up and running, you’ll find that very little need be done on your central local repository. The idea of having to clone an entire repository, especially one per branch as we did above seems like overkill, but it does provide a clear and definitive way to keep your lines of work distinct and separate while at the same time giving you great flexibility in managing your code.
Now, for a practical example. To start off, notice my prompt in the terminal. Through the use of a very nice Mercurial plugin called Hg Prompt, I am able to print repository information in my prompt so that I am always aware of 1) what repo I’m in 2) what branch of the repo I am in and 3) what I have incoming and outgoing. This simple plugin makes working in a multi-repo and multi-branch environment extremely easy.
1. Notice the main repo (frost) has two branches, one for release1 and one for default, which represents the current line of development. What I’ve done here is clone my main local repo twice, one for each branch. So I now have a local repo cloned from the central (frost) and two additional ones cloned from that.
2. Next, I update the frost-release1 repository to the release1 branch and leave it there. This way I don’t have to worry about mucking around with changing branches. I then do the same to release2. Each repo is now dedicated to a branch.
3. Now, we are actually making changes to the repos. Notice that I’ve added a new file to the release2 repo. The ‘?’ in my prompt tells me I have an untracked file and the ‘!’ tells me I have changes to commit. All thanks to our hg-prompt extension.
4. Next, we change back to our main local repo. Here, I have configured two additional paths so that I can determine incoming changes to my main local repo from my clones. Notice the additional parameter to ‘hg in’, which allows me to specify a repo to find incoming changes. The paths are denoted in your repository-specific hgrc file as follows:
5. Now, I am able to see that I have one incoming changeset from one of my local clones.
6. Say that I need to get release 1 changes pushed quickly for a patch tonight. In regular One Repo Land, I would have to push all committed changesets in my local repo. But, here, we can simply pull from the repo we need and push the changes up. Running an ‘hg out’ on my main local repo will show that my only outgoing changes are for my release1 fixes. My release2 features are still safe and sound in their own repository. A simple push and the fix is in.
A few notes:
1. I understand it may seem a bit heavy-handed to make three clones for local work, but cloning in Mercurial is not only easy and cheap, but encouraged. Mercurial uses hard-linking on the file system to only track differences, not entire files throughout their history.
2. The reason I chose one main local repo and two clones instead of just two clones of the central repo is that this gives me a staging area for ready-to-push changes while also allowing me to pull and run the most current code MINUS my changes.
3. Again, I understand it is possible to avoid pushing all commits from every local branch, but this involves some maneuvering of changesets using extensions. I am trying to give a simple approach that will avoid that process.
So, that is my humble attempt at creating a practical rebase workflow in Mercurial while pretentiously incorporating turn-of-the-century lore. I always knew there was a reason I was sweating over those wordy old poems and tomes.
I shall be telling this with a sigh
Somewhere projects and projects hence:
Two repos diverged in a local wood, and I--
I took the one less developed by
And that has made all the difference