Recently I had to determine a universal product versioning syntax for our products and services. This was done to ensure a level of consistency between projects and products. Depending on the deployment, currently there has been a wildly different format used between products and services.
Given the recent adoption of Team Foundation Server 2010 (TFS), and the use of Team Build throughout the product and service range, the new numbering format and branching strategy is designed to support the functionality TFS provides.
The cardinal rule of build and release management is: “each build must be unique”. Without the ability to discern one build from another, a team cannot establish any baselines; nor can issues or defects be reported on, or resolved in, a specific (identifiable) release candidate.
To ensure every build is unique, every product or service release will be identified following a basic numbering format: [Major].[Minor].[Build].[Revision]. In addition to binary numbering (which accompanies files built as part of the build process), the build is identified also by the Build Configuration Name – the name and number form the unique build name within systems such as Team Foundation Server.
Note that Continuous Integration builds do not require an increment in version number (to avoid a useless increase in the signal-to-noise ratio of build numbers registered in TFS).
The project management team for each project will define the [major] and [minor] numbers for each release in line with project goals and the addition of features and functionality etc. Typically a production release will involve a minor number increment, although should the changes warrant it, a major revision is also likely.
The [build] number is an atomically increasing number which ensures the uniqueness of each release build and label. Note that CI builds should not increment the product number. Formal builds also create a label within source control, so that the exact source can always be retrieved at any point in the future.
The [revision] number should be incremented in the event of a rebuild e.g. due to a build failure or perhaps because a build doesn’t pass smoke testing (and needs to be fixed and rebuilt). The revision number should be reset to zero once the build is accepted.
Note that TFS 2010 uses build labels throughout work items in each Team Project (for example in Bug work items) so that work items can be tied to explicit builds which have been created. These, in turn, can also be linked to automation run executions, changesets, work items and so on.
[The Build Name]
Thus our standard production build will be represented by the following naming convention:
<[Build Configuration Name + Number(Maj.Min.Build.Rev)]> Example: Widget Release Build [18.104.22.168]
Understanding Source Control
The standard approach to source control management has solutions arrayed in a specific folder structure, often defined as follows:
The following definitions are applied:
|Root||The top level folder in a folder hierarchy|
|Trunk||Represents the main, ongoing development of a solution (or solutions)|
|Branches||Typically represents forks in the source control management, usually branched from the trunk|
There are several different valid approaches to source control branching and without going into detail, the most common are:
- Branch per Release
- Branch contains a specific product release build
- Useful for patching production releases
- Code-Promotion Branches
- Specific code can be isolated for specific testing
- Useful for feature isolation development
- Reduces instances of source file conflicts
- Branch per Task
- Rather than branching by feature, code can be isolated according to a specific task, such as vertical architecture changes (e.g. cross cutting concerns)
- Branch per Component
- For larger projects, individual components may be branched for independent development and release
- Branch per Technology
- Useful when dealing with multiple operating platforms or versions, development can be split in alignment with different technology alignment, or environments
Generally, these approaches stem from a rationale behind change isolation – i.e. reducing the impact of change on the development of a solution’s code base. To learn more about these branching strategies, refer to the “Further Reading” section below.
Branching and Versioning Scenarios
The following are diagrams and scenarios which outline our approach to product and release versioning under branching scenarios.
Since our approach to release management dictates a unique build number per release, and typically an increment in the Major or Minor build number, we may branch source control at any time, continuing the product numbering in parallel.
This means that there may be concurrent builds of a solution with a similar – if not identical – version, differentiated by the build configuration/build definition naming.
When a release candidate is produced – and ultimately put into production – the remaining branches (or trunk, as applicable) are then incremented (typically with the minor version) so that ongoing development now represents the next logical product number.
Persisting Version Numbers
Anytime a Branch is merged back into the Trunk, the version number of the Trunk is persisted, not the branch version. The Branch can be resurrected at any time in the future, by branching from the merge change set.
If a Branch is merged into another Branch, typically the version number of the target Branch is kept.
A Release Versioning Example
Prior to release:
Branch A: 1.2.358.0 (branched from 1.2.298.0)
Branch B: 1.2.349.0
After a release from Trunk:
Trunk: 1.3.345.0 [Released as 1.2.346.0]
Branch A: 1.3.358.0 (branched from 1.2.298.0)
Branch B: 1.3.349.0
After a release from Branch A:
Trunk: 1.4.355.0 (branch A merged and released as 1.2.354.0 from the Trunk)
Branch B: 1.4.361.0
One of the most common scenarios involves patching a released product. Whilst normal development has continued on (on the source control trunk), a branch is created from the released code base, patched, and then the new updated release is deployed to production.
A Common Patching/Branching Scenario
Below you may observe two different approaches (labelled as options A and B) which can be undertaken to support “feature” branch releases – one scenario avoids a trunk release (option B); both are valid options no matter how many branches exist in parallel – they just require multiple source file merges.
In this scenario, the feature branch doesn’t feature the release patch until the final merge to trunk, prior to release
In this scenario, the release is made from the branch, then merged into the trunk
In general, it is considered a “best practice” to merge changes to the trunk and release as this avoids potential merges with other branches, and also means rolling changes can be included in other releases. It’s also far less messy in the long run.
Multiple Branch Releases
It is also possible to release and merge concurrent branches, although this carries a fairly large overhead, requiring merging of three (or more) different revisions of files. This has been captured in the following scenario:
In this scenario, Branches A and B are merged to the Trunk before being released
Additionally, rather than releasing from the Trunk, it is also possible to merge two branches into a “feature release branch”, prior to merging down to the Trunk. This is captured in the following scenario:
This release contains a “feature merge” branch, combining the changes from Branch A and B
Chris Birmele’s Branching and Merging Primer