Consider a simple solution structure in source control such as that shown below. It is based at the root of the source control structure, and the projects of the solution are at the first level of the source control structure.

This is a typical naive usage of source control. It works in that it allows developers to share their changes and provides history for all changes over time. Unfortunately, this structure is not without drawbacks. I'll start with some of the issues with the structure of the project itself and work up to larger issues with the overall organisation.

One of the more immediate problems relates to handling multiple solutions. If you attempt to put multiple solutions into this structure you will end up with an intermixed structure where it is not immediately clear which projects belong to which solution. We can address this as shown below.



This structure introduces the following elements:
  • A Supporting Assemblies directory that is used to contain any external dependencies. This may also be named lib, external dependencies or similar.
  • A src directory that contains the source code under development. The name is a for me holdover from my Linux days but tends to be commonly used.
  • A separate directory for each solution in the src directory. This keeps them suitably isolated.
This structure assumes that the external dependencies are common to each solution. I tend to find this to be the case but this is not a constant. If you need separate dependencies for each solution you can use a structure more like this:



Here we have moved the src and Supporting Assembly directories to be within the solution directory. This allows the external dependencies to be varied independently.

Development realities are such that this structure is still less than ideal. For any non-trivial software, there are generally multiple versions that need to be maintained simultaneously, including:
  • The current development version. This is where primary development occurs and is generally a work in progress.
  • Released versions. Once software has been released it generally needs to be maintained. This typically involves bug and security fixes that are required across the version lifecycle.
  • Pre-release versions. As software gets closer to release it is common to create a version that is to be stabilised and made available for testing and evaluation. This version will not have new features added, changes are limited to making it meet the quality bar defined for a pre-release version. Pre-release versions include alpha, beta and release candidate versions that have differing levels of stability, performance and feature completeness.
  • To test the implementation of a new feature or structure change without impacting other developers. This is useful in allowing breaking changes to be made in a contained fashion.
The structures above no support for having multiple versions in play at once. Item history is one dimensional and cannot support multiple variants at a time. The solution is to use a concept called branching. An example structure with branching is shown below:



This introduces a new directory trunk, which is used for the current development version. This is the primary branch. We also have a secondary branch (New Branch). This is created by taking a copy of the trunk branch (or any other relevant branch if desired). Branching is supported by almost all source control systems (if your system lacks proper branching support consider replacing it with something that does not).

We can see above that the New Branch contains an additional solution that is not contained within the trunk. As a copy the New Branch may be varied independently of trunk. This allows changes to be isolated. However this in itself does not consitute everything that is needed. It is often the case that a change made in one branch needs to be made in other branches as well.

Consider a security fix made to the version of Solution A in New Branch made as part of a stabilisation effort in preparation for a release. This fix is also desirable in the trunk and any other branches with this issue. A sub-optimal solution would be to have the developer copy the fix into each branch. This fails on a number of grounds. This copy operation may be a significant amount of work if there are many branches or many changes. Additionally this would cause issues with controlling the contents of branches and would require all developers to be familiar with all branches. In large projects these limitations are unreasonable.

The solution provided by the large majority of source control systems is to provide merge capabilties between branches. This allows changes to be merged from a branch into another branch. The source control system will provide mechanisms to perform merges that can identify the variances and assist in integrating changes. Often the repository will contain metadata such that the source control system is aware of branching and the origins of each branch.

It should be noted that as branches diverge it becomes increasingly difficult to merge the changes. This is especially true as the structure changes. This is a fundamental challenge that merge tools can assist with but cannot eliminate.

Future posts will discuss branch strategies in more detail.