Avoid the unstable branch

As software nears a release date, it is common to create a release branch in the source control system. Additional fixes and last-minute features are applied first to the main "trunk" or "head," then vetted and merged selectively into the release branch. Some developers may begin new work in the trunk for later releases, so they leave those changes out of the release branch. If stabilization goes on long enough, then some fixes for the release may no longer make sense in the trunk. Such limited fixes will be added carefully only to the release branch. In all other cases, the early release remains a direct ancestor of future releases. Both versions of the code increase in stability. All changes are examined at the time they are made for their applicability to two different code bases. No merges are postponed.

§    Origins of the unstable branch

When stabilization drags on too long, then it may be tempting to keep the earlier release in the trunk, and move future development into a branch. Those working on the release will worry only about the trunk, and those working on future development will be entirely responsible for the branch. This way those working on the release will be spared the burden of working with two different code bases. The burden is a significant one because every change for the release must make sense in two different contexts, or must be reconciled some other way. Reducing this effort might help meet a deadline.

Avoiding the daily effort of merges does not make the difficulties go away, but merely postpones them to a time when they are less well understood. Worse, there may be an expectation that a separate Configuration Management group can reconcile the two versions of the code mechanically with sophisticated source control procedures. In fact, that CM group will be forced to make design decisions without consulting the original developers. They will be creating and committing code that never existed before.

A future branch is guaranteed to be less stable because it forked before the trunk was stable enough to release. (If the code was already stable, then a fork was not necessary.) The future branch is likely to have more significant structural changes as well, so there will be a temptation to merge the simpler changes of the stable trunk into a less stable branch. Bug fixes will be lost, and new bugs will be introduced.

§    The impossible merge

A release trunk might add a bug fix after the future branch has moved the affected file to a new location. Since a developer working on the release is ignoring the branch, that change will not be applied to the file at the new location. Someone working in the future branch may be monitoring the release trunk, but may not recognize or understand how that fix applies to the branch.

A source control system like Subversion merges by a three-way difference algorithm to construct a patch. This patch sees all differences between the two code bases as a single massive change-set from a common root. A single large change-set may see only that a revised file disappeared and that an older version appeared in a new location. Even a more fine-grained merge, like Mercurial's or Git's, has no way to know that the fix in the old location still applies to the new location.

Perhaps one bug was fixed two different ways in the two versions. One bug could be fixed by a local patch for release, or fixed by restructuring the logic for the future. Combined, the two fixes could result in a new bug. Someone unfamiliar with the code will have to reconcile these kinds of contradictions quickly. Generally they only examine automatically detected conflicts, where the same lines are modified two different ways. But incompatible edits may be separated by a few lines. If the result compiles, then the merge is considered successful. Even if automated testing finds the new bug, finding the culprit will be troublesome and expensive.

§    Merging into the future

I have even seen one of these unstable branches called a "refactor branch," with the admirable intention of cleaning up some structural deficiencies in the code. If there is a word that means the exact opposite of refactoring, that word would be branching. Refactoring is the improvement of code by a series of small testable changes that always returns to a stable functioning system. A forked branch accumulates changes until they must be applied as a single massive patch to a different body of code.

Finding and fixing the contradictions in forked code is not a mechanical task for a CM department. To minimize the number of conflicts, they are much more likely to apply the patch backwards -- merging the smaller changes of the release trunk into the less stable future branch. So the one group that did not have time to worry about two different code bases ends up modifying both versions anyway. A merge is an admission that the future branch could not realistically track and reconcile changes as they were made in the trunk.

Like many disasters in software engineering, the unstable branch merge is a result of trading a perceived short-term gain for long-term pain. Since the developers working on the two bodies of code have less reason to consult each other, they are likely to lose some of that short-term gain as well. Fewer eyes are looking at the released code, and fewer bugs will get fixed. Software contradictions can be postponed, but they can also be neglected until they are too difficult to resolve.

Bill Harlan

May, 2010

Return to parent directory.