Version control is key to any DevOps pipeline. Software evolves quickly as numerous developers work on frequent iterations. Repositories demand careful management to track and manage code changes properly. Otherwise, the project spirals out of control as developers struggle to find the right components for a build.
But DevOps is hardly a straight-line process. The value of DevOps and other CI/CD paradigms lies in experimentation, rapid changes, trial and error, and efficient testing -- tasks that can take projects in exciting new directions. Each varied task presents developers with a new branch in the workflow -- and new complexity to repository management.
What is branching?
Just as a tree spreads into an array of individual branches, each leading to a unique leaf, a software project splits and proliferates into myriad different versions as it develops.
Branching strategies help developers manage varied tasks, keep projects on track and enable different IT teams to develop a single project in different directions simultaneously.
Some developers might work on bug fixes, others might work on adding new features, and some might experiment with creative new ideas for changes and enhancements. Each effort represents a potential software branch, which developers can then test, evaluate and merge into the main ongoing project -- or discontinue and discard.
Because branches enable different work to occur on the same project simultaneously, effective branching can bring efficiency to the DevOps process.
How does branching work?
A branch is a point of divergence as well as a copy of the codebase at a given point in time. Developers or IT teams can work with branches to accomplish necessary tasks, such as bug fixes or new features, without affecting other branches or the main codebase. Developers can then merge and consolidate the branches to enable collaboration. This ensures the branches never diverge too far from the main codebase.
Organizations can establish branches for any purpose, but there are generally five major use cases for branching:
- Ordinary development. Typical code development occurs within a main or regular development branch. This is the day-to-day work of building code in accordance with requirements and aligning it with the established roadmap, such as integrating expected features, user stories and testing. Branching strategies that support ordinary development focus on developers, testing and deployment coordination.
- Fixes. Fixes can be addressed as ordinary development or as unique branches. This enables some developers to continue working while others fix and optimize the software or otherwise deal with trouble tickets or reported issues. Fix branches focus on merging and testing.
- Unusual changes. Changes occur for many reasons, such as user or stakeholder requests, changes in the competitive landscape, or new enabling technologies -- for example, the release of a new underlying platform or engine, such as Unity. These changes might be small or large, and often demand efforts outside the ordinary development cadence. A change branch and workflow cater to these events.
- Experimentation. Iterative development creates the opportunity to experiment without committing changes in the product roadmap. For example, developers or stakeholders can offer up creative new ideas, while developers test the performance and integrate new libraries or frameworks. While changes are sometimes folded into their own category, experiments can be used as unique branches for developers who want to try things that might not make it into production.
- Emergencies. This is a bug fix intended to address a serious incident or issue that cannot be dealt with through ordinary development or fixes. Branching strategies that accommodate emergencies must enable developers to move a change to release while paying attention to ordinary development.
Branching strategy considerations
A branching strategy is the way a development team creates and uses branches in conjunction with a version control system and repository. As a minimum, a sound branching strategy should do the following:
- Support collaboration by enabling parallel development so that multiple developers can work on different project tasks simultaneously.
- Support version control by complying with prevailing repository and version control systems.
- Support the existing workflow from production or release.
- Optimize the development workflow and enable faster release cycles.
- Integrate with existing development tools and practices.
Selecting a branching strategy is a crucial decision for a software project. There are numerous strategies and variations with unique tradeoffs, and different projects might adopt different branching strategies. When creating a strategy, project managers must consider factors such as the following:
- user and stakeholder requirements for updates and releases;
- the development method or paradigm;
- the development tool set; and
- the project's scale.
Ultimately, any branching strategy should align with the existing CI/CD pipeline -- not the other way around. A strategy that does not fit with the development pipeline can be difficult to implement, and can disrupt and delay development rather than enhance the effort.
Businesses can adapt and tailor branching strategies to meet a project's unique needs. There are five generic strategy types:
- Trunk and release branching. Release branching creates a branch for the desired release candidate. Developers create and maintain the main code line or trunk branch, and then create a branch to release or deploy to production. Release branching requires developers to apply other branches, such as changes or fixes, to both the release branch and the main code line.
- Feature branching. Feature branching creates a branch to implement a new feature or user story in the project. When the feature branch merges to the project, it adds the new feature. Flags are often used with feature branches to enable or disable the feature while it's in development -- or it's disabled so that users don't see or use the feature until it's ready.
- Change and development branching. Sometimes associated with feature branching, change or development branching represents a longer-lived branch that contains changes and enhancements. Change and development branches are not the result of defects or the addition of new features, which have their own respective branches. Change and development branches can receive input from numerous developers and must undergo extensive testing and validation.
- Fix branching. Fix branching implements bug fixes or optimizations. A fix branch represents a longer-lived, broader and more comprehensive fix for low- and medium-priority issues -- or a short-lived emergency hotfix for critical issues, such as unexpected server instability. An organization might use two different fix branching types for regular fixes and hotfixes.
- Task branching. Task branching addresses development by envisioning every fix, change or feature as a task, which an issue tracking platform, such as Jira, can follow and reference. Each issue corresponds to its own branch. By coupling tasks with issues, developers and project managers can readily see which code addresses which issue.
These branching strategies can be implemented using several popular branching flow paradigms, such as the following:
GitFlow first appeared in 2010 to enable long-term trunk and development branches -- though the strategy supports every branching strategy type. Teams perform development work, such as new features or regular bug fixes, in development branches. Merges only occur when the developers are satisfied with the development branch. GitFlow also handles emergency bug fixes or hotfix branches.
While the GitFlow strategy proved popular, its support for large numbers of long-term branches makes branch maintenance and merges problematic. Today's GitFlow users must focus on short-term feature branches and frequent merges to reduce potential limitations. Many organizations have stepped away from GitFlow in favor of other strategies.
GitHub flow, an alternative to GitFlow, addresses every change as a feature branch. Developers use GitHub flow to create new feature branches from the trunk, and can test and validate feature branches before or after a merge. Developers can then merge feature branches back into the trunk branch in a releasable form. The trunk can generate a release branch for additional release preparation, such as packaging into a container image file. Consequently, this is a simpler approach for branch maintenance and merges.
One crucial limitation with GitHub flow is trunk vulnerability -- a bad merge can leave the trunk undeployable. Trunk protection measures, such as backups prior to merge, are essential. Neither GitFlow nor GitHub flow support continuous integration as smoothly as development teams might demand.
GitLab flow is a Git-based strategy that relies on clearly defined policies and practices to combine feature branching with an issue tracking system. Thus, every branch and its associated development work can be traced to a specific issue -- whether it's a bug, a new feature or an emergency issue -- with the issue tracking system.
GitLab flow merges all features and fixes to the trunk branch, which generates a stable branch and a production branch. When a trunk branch is ready for deployment, it can merge into the production and release branch. Established guidelines and best practices manage this process.
Trunk-based development (TBD) is a variation of GitHub flow where developers rely on a releasable trunk and draw branches from the trunk. TBD requires developers to commit and integrate changes daily. This keeps branches short-lived, means changes are small with less effect and emphasizes frequent collaboration between developers. TBD supports many kinds of branches, but generally aligns well with CI/CD requirements.
Longer-term efforts can use mechanisms such as feature flags to toggle work in progress on or off. In effect, the developer creates a switch in the code where in one condition, the software works as normal, and in the other, the state executes the new code and disables the old. This enables branches to merge daily with the new features disabled until the work is complete. Once tested and validated, IT teams can remove and clean up the old code.
Merging and validation
Unlike the tree metaphor, developers frequently select and merge parallel software development branches to form a project's main code line. This merge keeps a project on track as a single cohesive arc. However, merges can be difficult. Any branching strategy must consider the merge's speed and effectiveness. Larger and longer-lasting tasks can make merges more difficult and highlight the benefits of smaller, shorter-lived branches. Branching strategies and tools can play a huge role in a merge's success or failure.
Validation follows the merge and is critical to test the merged project's main line. This demands careful CI implementation and automated testing techniques. Branch testing validates each branch before a merge to reduce problems in the final merge, but there is no substitute for testing post-merge.