IntroductionAs
Fred Brooks rightly pointed out, there is no
silver bullet capable of hitting the beast of software development. While new requirements arise, ideas and new bugs are found, programs are living and changing. The path that code travels from version to version can be extremely complicated and tortuous. Many people are involved in its creation: developers, testers, business analysts, customers, etc. Despite the fact that there are many different types of development - outsourcing, product development, open-source, etc., the problems facing the team remain about the same. Software is a complicated thing, the consumer wants to get it as quickly as possible (and cheaper). The quality should be acceptable. The development team is faced with a serious task - to establish effective interaction. One of the most important means of collaboration within the development team is the code that they write.
At the moment, distributed version control systems (
DVCS) are widely distributed on the market. However, the lion’s share of the market is kept by traditional and easier-to-use centralized systems, such as, for example, SVN. A version control system, or rather its competent use, plays a key role in ensuring effective interaction. Remember how long you read a book about your VCS? A team in which there are no people capable of building competent interaction through VCS, based on the needs of the project, will not be envied.
')
Release ManagementLet's imagine the perfect release management. The release manager can evaluate the status of the code and select the implemented functionality to be included in the release. This functionality should be ready and tested. Also, the release manager can include bug fixes from the previous release. The unavailable, unstable and untested functionality should not be released. If QA-specialists receive information on the instability of a particular functional, the release manager should be able to remove it from the release. Often there is a need to transfer defect fixes to the version that is already working for the end user, because for some reason it cannot switch to a new one.
If you change the point of view a little and look at the process of working on the code by the developer, he should sit in his sandbox and not be influenced by destabilizing commits by his colleagues. Ideally, developers should only exchange complete and stable sets of changes. So it's easier to understand what has been done, right? However, commits should not dictate to the developer the style of his work, and he should always be able to insert only partially completed functionality.
The problems described above have several solutions. One of them is the right choice and proper use of the project version control system. One more thing is an understanding of possible brunching (branching) strategies and the price you have to pay for all this luxury.
The use of branching allows us to kill two birds with one stone: to stabilize the release version (a process better known as bug fixing) and at the same time to extend the application with new functionality for the next release. These are the two main, though not the only, ways of using branching on a project.
Retreat about code versioningAs a rule, version control systems store a history of changes in the form of a line (centralized) or a
graph (distributed). A branch (brunch) is simply a line of code development that has a common history with other branches and exists in parallel with them. Jeff Atwood in his
blog compares branches with parallel universes. In such a universe, at some point, the story went differently relative to others. This gives us limitless possibilities that are balanced by the limitless complexity of our universes.
As a rule, one of our stories is the main one and wears the proud name of a trunk or mainline. By analogy with a tree, other branches depart from it. Sooner or later, ready-made (or not quite) functional and error fixes fall into this thread.
Branch per releaseConsider the first of these cases, when a separate branch is created for each release. This is done in order to correct defects found after the release of the release or during its testing. This process is usually called stabilization. At the same time, the fixes themselves (bugfixes) do not remain only in the release branches, but are transferred to the mainline (if the release history and the mainline are not too diverged), making it more stable. The code in the release branch is isolated from the destabilizing effect of developing new functionality and does not block it. By itself, the release branch provides an easy way to support the release version. When release support stops, its branch is frozen. In the meantime, the project goes on, mainline continues its development, being the point at which new functionality accumulates for the next releases.
In the same way, you can support releases for different customers, highlighting a branch for each, if for some reason they cannot deliver the same version. I want to note that the support of different variations of the same version is a laborious task and should be avoided as far as possible.
Branch per featureThe next case is the
allocation of a separate branch for the development of a new functional . As a rule, it is one logically complete functional area, or just a feature. The new functionality is merged with the main branch only after it is fully completed, which allows you to avoid the negative impact of unfinished work on other lines of development. After the new functionality is ready and merged with the main branch, other development branches should be integrated with the mainline so that the delayed integration effect does not accumulate. The use of branches for releases and development allows us not to wait until the testing and release stabilization ends, but to immediately start developing the functionality for the next one.

You can also create sub-branches for release and development branches, if you still need isolation levels. In all cases, the creation of a new branch should understand the price of its support, which will be mentioned a little later.
Integration between branchesThe main branch (mainline, trunk) is the main integration point using code. Anyway, all the changes made by the developers get here. However, it should not turn into a dump of unstable and unfinished code. That is why it is recommended to develop new features in a separate branch, integrate it with the main one, test it and only then merge the changes. In other words, the mainline should contain a fairly complete code that can serve as the basis for the stabilization release branch. Also, bug fixes from the release branches, after passing through the mainline, fall into the branches for development, thus, work is being done on a more stable code. The good rule is that we should not give unstable changes to other branches and that we should accept stable changes from other branches.

Consider the situation depicted in the picture:
- At some point in the Mainline has accumulated a sufficient amount of the finished functionality for the release of Release 1.x. A branch was created for it, and after testing and stabilization, the release went to the customers.
- In parallel with this, the development of a new functional started: feature A and feature B, each on its own branch.
- The bugs found by the releasers in Release 1.0 were fixed on the release branch, and Release 1.1 was released. The bugfixes from it were merged with Mainline, from where they fell into branches for feature A and feature B. Thus, the work was done on a more stable code.
- One of the customers for their own reasons could not upgrade to version 1.1 and was faced with a number of specific defects. This was fixed on a branch specifically made for him - Release 1.0.x.
- The development of feature A was completed, and, after integration and testing, these completed changes entered the Mainline. The feature B branch receives these changes immediately after they hit the Mainline in order to work on the most current version of the code.
- A decision is made to release a new Release 2.x, including feature A, and a branch is created for it, on which the service for this release is provided - 2.1, 2.2. Moreover, the bugfixes for release version 2.2 are not merged with Mainline, since the histories of these lines of code development have already diverged too much.
In assessing the pros and cons of this approach, one should take into account:
- Feature branches do not stand in the way of Continuous Integration
- Semantic conflicts are not specific to branching.
- Feature toggling and Branch-by-abstraction have several disadvantages compared to the Feature branches.
Integration via Mainline is not the only way to integrate - integration is possible directly between branches.
Martin Fowler calls this method
Promiscuous Integration . For this integration method, communication within the project team is very important.
Branch stabilitySuch a model has a gradation of stability, where the most stable release branches are, the mainline is less stable, and the most unstable are the development branches. As a rule, in the diagrams the most stable branches are displayed above all, and unstable - below all.
Branching overheadThe following costs are associated with branching:
- Mechanical - these are the actions that need to be done to create a branch, switch from branch to branch, merge changes, etc. As a rule, such actions are laborious for centralized systems and relatively simple for decentralized ones.
- Intellectual - these are the efforts that have to make to keep in mind all existing branches and their purpose. As a rule, there are tools that facilitate this task. This may include the training curve for employees associated with the development of a version control system.
- Price for testing - the use of parallel development can seriously increase the price of manual testing. Deferred testing reduces costs, but at the same time has several disadvantages. Any automatic testing significantly reduces the cost of testing when using branching. In general, this point depends on the testing strategy adopted on the project.
Types of dependencies between branches and how to solve themThe following dependencies may occur between branches:
- Architectural - if we change the architecture on one branch, other branches may depend on these changes.
- Functional - some new functionality cannot be completed or has no special value, until the other functionality on which it depends is completed.
- Dependencies on the correction of defects - in the case of the correction of a defect on one branch, there may be several branches that should receive this change.
There are several typical solutions for working with such dependencies:
- Sub-branching - dependent functionality is implemented in a separate sub-brunch and then merged with all interested branches.
- Stop - development on a branch is frozen until the desired functionality is ready.
- Architectural abstraction — by abstraction, boundaries are created in the system that isolate different parts of the functionality. In this case, the problem is solved not only at the level of the version control system, but also at the level of the application design.
- Using stubs - the system uses fakes / stubs, which are replaced with real functionality as it is ready.
- Release, patch, re-release - the system is released not fully finished and patches are brought to perfection (this practice in some industries is called paid beta testing).
ConclusionIt is important to understand that a competent modular design of an application can greatly reduce or negate the need for branching and is a powerful tool for solving problems associated with parallel development.
Branching allows us to simultaneously conduct two types of development: stabilization and implementation of new functionality. However, this is not the only way to use it. For example, individual branches can be allocated to separate iterations or to isolate different commands.
The correct choice of branching strategy depends on the needs of the project and the possibilities / limitations of the version control system used (which, however, no one forbids changing). Real-world constraints that are imposed on a process are often impossible to solve without the possibility of parallel development. However, illiterate understanding and use of branching often leads to
anti-patterns that complete this material.
Anti branching patterns:
- Merge Paranoia - developers are afraid to combine the code, so the negative effect of delayed integration is accumulating.
- Merge Mania - developers spend more time merging changes than developing.
- Big Bang Merge - branches do not exchange finished changes, so one giant union occurs at the end.
- Never-Ending Merge - the union never stops, as there is always something to unite.
- Wrong Way Merge - combining a later development branch with an earlier version.
- Branch Mania - creating a large number of branches for no reason at all.
- Cascading Branches - creating branches without merging them with the mainline at the end of development.
- Mysterious Branches - creating a branch for no reason.
- Temporary Branches - creating a branch with a changing reason for its existence: the branch becomes a temporary workspace.
- Volatile Branches - start a branch in an unstable state or transfer unstable changes to other branches.
- Development Freeze - stop all development to create branches, merge or create releases.
- Berlin Wall - using branches to divide people in a team, instead of dividing the areas they are working on.