The Importance of a Uniform Application Version Control Strategy
Perhaps no software engineering tool is quite as universally adopted throughout the industry as version control – nor is there one done in as many flavors. Given that we define software engineering as “the multi-person development of multi-version programs”, version control is a key tool to keep things under control.
There is an often-unspoken truth when discussing version control: the technology is only one part of the practice for any given organization – there is almost always an equal amount of policy and usage convention on top of that. Here are some of ours.
GitFlow Is the Way
We cover the use of the GitFlow standard below and at Mercury, that is the hallmark policy and adopted convention all engineers follow.
No Long-Lived Branches
Well-managed code bases declare on specific branch in the repository to be the “Source of Truth”. While arguments can be made between GitFlow and Trunk-based version control (with feature/release branches clouding the picture), “master” is our Source of Truth. Beyond the supremacy of this single Source of Truth, any discussion that an organization has about branch management policies has to acknowledge that every piece of work-in-progress in the organization is equivalent (or literally) a branch. And thus, we get to the topic of long-lived branches.
In short, active development branches (“non-permanent” branches) should be very short-lived – they should only exist as long as is required to execute the work (user story) that led to the creation of the active development branch. “Active development branch” has a different name depending on the organization; what we mean is the branch an engineer creates in response to pulling a new user story or ticket. Since we purposely size our user stories to be relatively small and executable in 1-2 days, this sets an expectation on the life of individual development branches.
There are a lot of reasons for this and there has been a large body of study to back it up, but our rationale comes down to two key points:
- the longer a branch is open, the more difficult (because of merge conflicts) merges are and
- pull requests for a sea of code changes (lots of work committed over a long time in one commit) are BRUTAL
Generally, We Prefer Monorepos
“Monorepo” refers to the strategy of keeping code for many projects stored in the same repository. Given that we are a client services organization we have to clarify that we cannot (due to intellectual property restrictions) literally keep all of our organization’s code in a single repo. “Monorepo” at Mercury refers to keeping all of a client’s code in the same repo, even if we work on multiple products for a client. While we don’t think the monorepo is the perfect answer for all situations, we do generally prefer it to the “manyrepo“.
There are a few reasons for this bias (and honestly, a lot of it is preference) but after years of trying individual repos per client code base and a single repo per client, we find less confusion and ease of development for the monorepo variant. Oftentimes a client’s products interact with (or even depend on) each other and cross-repo machinations are not as clean as just having the entire contained system in a single repo. This choice also fosters greater opportunities for and likelihood of code reuse (rather than reinventing across related repos).
“I don’t care if it works on your machine! We are not shipping your machine!”
Vidiu Platon
Git In Azure DevOps
Mercury uses Azure DevOps for a range of Agile, DevOps and ALM features – one of those key features is Git repositories for source/version control. A typical repo will look like this:
GitFlow Workflow
Along with using Git we flow a very specific workflow pattern published by Vincent Driessen at nvie, you can read his publication here: A Successful Git Branching Model. This model allows us to handle anything a client bring to the table. We are able to do Continuous Integration with Developers pushing to a Dev branch along with having a stable representation of Production at all times in Main (formerly known as master) for any hotfixes. The following diagram illustrates our standard version control and branching strategy:
Dev Branch
The dev branch is closed to direct development but will be regularly updated by merges with feature branches through Pull Requests. This branch is what will be deployed to the Development and Staging environments for testing to catch errors before they are deployed to Production.
When changes have been merged into the dev branch the Build will run and then hand off the artifacts to the Release to be deployed to the Development and Staging environments.
Main Branch
When an application is ready to be released then a Pull Request from the dev branch to the main branch will be created. Once approved and all policies are met, the main branch will run through the build process. When the build is successful, the artifact created will be sent to the Release and passed to Production stages for deployment.
Hotfix Branch
In the event that a critical fix is needed in production then a temporary hotfix branch can be made to bypass the standard CI/CD process. This should only be done in emergencies and should not be used to circumvent the best practices put in place.
A branch will be created directly off of the main with a name prefixed with ‘hotfix-‘. A Pull Request can be made directly to the main branch for these changes. After all of the policies have been fulfilled and the branch has merged into main, the main branch will build and follow the Production release process.
After verifying functionality in production another PR should be created to merge this change into the dev branch following standard PR practices to the dev branch.
Conclusion
While we have certainly left out several nuances and team-specific details in this post, it ably summarizes Mercury’s GitFlow-based version control strategy that we apply across all teams and products. While we cannot claim to never have issues with this strategy, it has greatly improved the reliability of day-to-day commits, builds and releases and also dramatically reduced the pain of former long-lived branch merges.