Last week, I shared some of insight I’ve learned as a software consultant about branching and merging. Specifically, I shared some of the complexities than can arise, such as increased complexity, possible loss of work, and introduction of bugs through failed merges. This second part focuses on strategies for successfully using branching.
The terminology used in this article is defined at the bottom.
Standard Branching Strategy
Currently, our standard branching structure revolves around three main branch types, as shown in the below image.
- Main – This is the main parent branch. No code changes should be made directly into this branch.
- Dev – This is the main development branch, and is a child of Main. All general development work happens in here. For smaller projects, the Main and Dev branches can be combined.
- Release1.0, Release 1.1, etc. – One branch is created from Main for each release of the product before QA, and QA fixes are made directly in this branch. This allows stability of the product and makes it easy to push out emergency fixes. Although these branches can be maintained indefinitely, it is recommended that only 2 (previous and current versions) are kept, and the others archived or deleted.
- Feature – This is a special kind of branch which is created from Dev in order to add major features/enhancements which would otherwise prevent other development from happening in Dev. These branches are temporary and should be removed as soon as the feature is completed and changes are RI to Dev. Note that branching from Dev allows it to serve as an integration branch for features. This way, when there are issues merging between random fixes in Dev and large features in our feature branches, those conflicts can be resolved and unit tested in Dev rather than in our Main branch.
One person should be assigned to oversee regular merging for each project – otherwise it will never happen until significant merge debt has accumulated. Emergency fixes made in a release branch should have RI done immediately after the fix by the developer.
Handling exceptions/baseless merges
Occasionally, there are situations where merges will need to happen between child branches. Some examples:
- An emergency fix is made in Release1.0, but Release1.1 has already been created and is in QA.
- A fix which was made in Dev is needed to fix an emergency issue in Release1.0.
These situations should be the exception rather than the rule. In both of the above cases, such issues might have been avoided with a shorter release cycle and increased code quality. The TFS Branching and Merging guide notes that “If there seems to be a need to do baseless merges regularly, it likely points to a flawed branching strategy.” However, when the need arises, the following should be noted:
- These merges need to be done manually and with caution. Do not blindly trust the merge tool.
- Think about how to merge this change back into the parent branch. For example, in case #1 above, the fix should first be merged from Release1.0 to Release1.1, and then RI from Release1.1 to Main.
For more from our software consultants, check out this post about coding in less than ideal conditions.
- Reverse Integration (RI) – Pushing a change-set from a child to a parent branch. This occurs in order to update the Main branch with changes from Dev or Release branches.
- Forward Integration (FI) – Pushing a change-set from a parent to a child branch. This occurs most often in Dev branches, which should be continually receiving changes from the Main branch.
- Baseless merge – A merge between two branches which do not have a parent/child relationship.
- Merge debt – A metric for understanding the amount of changes which have not been RI/FI between branches. The higher the amount, the more difficult it is to do the merge in the future. TFS has tools to calculate and report on this.
- More information about branching and merging best practices can be found in the Visual Studio Team Foundation Server Branching and Merging Guide.
- Professional Team Foundation Server 2012