Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active December 1, 2025 20:11
Show Gist options
  • Select an option

  • Save Jaykul/5687dc25b081e61e41de2a5635b506aa to your computer and use it in GitHub Desktop.

Select an option

Save Jaykul/5687dc25b081e61e41de2a5635b506aa to your computer and use it in GitHub Desktop.
Versioning

Versioning Software

Software builds must be labeled with a unique, incrementing version number.

We follow "Semantic Versioning" because our industry as a whole has decided it's useful.

Version numbers should be predictable, so that (as developers) we know —and control— what version of the software we're working on.

Each build must produce a label that is unique enough that we can track a binary back to its build and the commit it was based on.

A Simple Approach to SemVer with Feature Branching

We use a centralized git workflow commonly known as mainline, feature branch or topic branch, with pull requests for code review prior to merging changes to the main branch.

Tags (with a specific prefix) on the default branch set a specific version for that commit, and builds from that commit will always have that version number.

We can generate a unique, predictable build number for any untagged build by starting from the most recent tagged version, and simply counting commits.

If the build is on a non-default branch, the branch name should be mapped to a pre-release identifier, and each commit increments a counter identifier, and we get a version like -feature.2. Once the code is merged to the default branch, the feature version number can be incremented automatically -- or we can tag the merge commit to change the version.

To make the process more predictable, we can use the commit message or the branch name to indicate an increment of a major or minor version number, so we don't have to wait to tag the merged commit. We can use:

  • A prefix such as Conventional Commits uses, like feat:
  • A trailer like semver: feature or semver: major
  • A prefix on the branch name like feature/, fix/, release/

NOTE: For versions like windows assembly file versions that do not support semantic version -prerelease tags, we may use the revision number (the 4th digit) to carry the pre-release counter, or a build identifier. However, we still put the full semantic version into the "Informational Version" on the assembly.

Finally, if fixes need to be made to a released version (i.e. without releasing additional features), we can create a release/ branch from any tagged release, and use the same approach to increment a patch version number.

Below, there is an example diagram showing some changes being done following this mainline workflow with the same changes shown, for contrast, as they might be done in a modern Git-Flow or a staged release workflow.

Background

Semantic Versioning

The official Semantic Versioning spec, as listed on SemVer.org, specifies that a "version number" is just a series of three numbers, like "18.10.0" or "2.1.3". A "semantic version" is a version number with optional labels for pre-release and build metadata. The rules for incrementing a semantic version are pretty simple:

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards compatible manner, and
  3. PATCH version when you make backwards compatible bug fixes. Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
  • A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers immediately following the patch version.
  • Build metadata MAY be denoted by appending a plus sign and a series of dot separated identifiers immediately following the patch or pre-release version.

We follow the identifier syntax for pre-release and metadata identifiers as prescribed by (SemVer 2), which was revised to ensure that semantic versions will be canonical. Thus:

Identifiers are dot separated. Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes.

NOTE: When dealing with systems that only support SemVer 1 -- notably NuGet 2.0 as used by PowerShell and Chocolatey -- we leave off metadata, and convert identifiers by zero-padding the counters and removing the dots.

Feature Branching

Feature branching is a simple form of mainline development where a "feature" or "topic" is any kind of change to a project: whether it's a bug fix, new functionality, or even just more documentation. In agile terms, a topic represents a "releasable iteration" or a work item in our backlog.

In mainline development, the default branch (usually called main or previously master or trunk) is special, and represents the official project history. It is always in a state that could be deployed, and indeed, the tip of your default branch inherently represents the current release. Only releasable iterations are merged to this branch, as this triggers the deployment pipeline, including integration testing.

In Feature Branching, a developer creates a branch to start an iteration, and merges it to the default branch only when the work is complete.

NOTE: compare this to Martin Fowler's description of Continuous Integration (also known as trunk-based development) where you merge back and forth between the default branch and open feature branches frequently, and use feature flags to hide incomplete work.

Releases are represented by tags on the repository --usually on the default branch.

When release processes are long, or when there are arbitrary release cycles, or a long-term servicing release, it is acceptable to create a release branch. Short term patching branches should only be created on-demand, for issues found during the release process -- and should have the full version of the intended patch as their branch name, like "release/1.2.5". They must be tagged upon release, and then merged back into the default branch. Some teams will call these "hotfix" releases -- but they should follow the same pattern.

Long term servicing branches are not appropriate for continuous delivery apps like web apps and consumer/mobile apps. They are created with a name like "release/1.2" so that multiple releases can be made from them. Each release should still be tagged upon release, and merged back into the default branch.

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment