5 Branching Strategies
Defining a branching strategy can be tricky and requires identifying the needs of each repository. While there is no best branching strategy, this part provides a few examples of what could be considered as potential branching strategies.
There is no need to define one single branching strategy for all of the repositories of a company or institution. The choice should be made by type of activity.
This guide explains what a branch is and how it works.
5.1 Basic git structure
A Git repository is typically organized around a primary branch, main, from which feature branches are created. These feature branches isolate development work and are eventually merged back into main through a pull request.
flowchart TD
main["Main branch"]
feature-1("Feature 1 branch")
feature-2("Feature 2 branch")
PR1>"Pull request"]
PR2>"Pull request"]
main --> feature-1
feature-1 --> PR1
PR1 --> main
main -.-> feature-2
feature-2 -.-> PR2
PR2 -.-> main
linkStyle 0 stroke:#0066FF,stroke-width:2px
linkStyle 1 stroke:#0066FF,stroke-width:2px
linkStyle 2 stroke:#FF6600,stroke-width:3px
linkStyle 3 stroke:#0066FF,stroke-width:2px
linkStyle 4 stroke:#0066FF,stroke-width:2px
linkStyle 5 stroke:#FF6600,stroke-width:3px
This branching strategy can be used in the case of a team working on a specific activity of a single clinical study within a repository.
It could potentially be used for a study or a product with several distinct activities using tagging releases.
5.2 Usage of a devel branch
A devel branch is usually a development branch located between the main and other specific development branches. It is used to integrate multiple features and updated before all of those are merged with the main branch. The main objective is to ensure stability of interdependent updates. It is possible that two different features, which work fine in separate branches, may cause conflicts when merged into the same branch. The devel branch is used to integrate all updated features and make any necessary final adjustments before merging into the main branch. It works as a safety net before the final merge to main.
flowchart TD
main["Main branch"]
devel(["Devel branch"])
feature-1("Feature 1 branch")
feature-2("Feature 2 branch")
resolve["Resolve conflicts"]
pr1>"Pull request"]
pr2>"Pull request"]
main --> devel
devel --> feature-1
devel -.-> feature-2
feature-1 --> pr1
feature-2 -.-> pr2
pr1 --> devel
pr2 -.-> devel
devel --> resolve
resolve -->|"Resolve conflicts"| devel
devel ==>|"Merge to main"| main
linkStyle 0 stroke:#0066FF,stroke-width:2px
linkStyle 1 stroke:#0066FF,stroke-width:2px
linkStyle 2 stroke:#0066FF,stroke-width:2px
linkStyle 3 stroke:#0066FF,stroke-width:2px
linkStyle 4 stroke:#0066FF,stroke-width:2px
linkStyle 5 stroke:#0066FF,stroke-width:2px
linkStyle 6 stroke:#0066FF,stroke-width:2px
linkStyle 7 stroke:#FF6600,stroke-width:3px,stroke-dasharray: 10 5
linkStyle 8 stroke:#FF6600,stroke-width:3px,stroke-dasharray: 10 5
linkStyle 9 stroke:#FF6600,stroke-width:4px
flowchart TD
main["Main branch"]
devel(["Devel branch"])
feature-1("Feature 1:<br>Remove<br>ADAE.ARELGRP1")
feature-2("Feature 2:<br>Update ADSL<br>(still uses<br>ADAE.ARELGRP1)")
pr1>"Pull request #1"]
pr2>"Pull request #2"]
conflict["Conflict detected<br>after merging<br>both features"]
resolve>"Resolve conflicts<br>in devel branch"]
main -->|"1. Create devel"| devel
devel -->|"2. Create"| feature-1
devel -->|"2. Create"| feature-2
feature-1 -->|"3. Merge"| pr1
feature-2 -->|"3. Merge"| pr2
pr1 -->|"4. Merge to devel"| devel
pr2 -->|"4. Merge to devel"| devel
devel -.->|"5. Conflict appears!"| conflict
conflict -.->|"6. Resolve"| resolve
resolve -.->|"7. Apply fix"| devel
devel ==>|"8. Merge to main<br>(conflict resolved)"| main
classDef conflictBox fill:#FFE5E5,stroke-width:2px
classDef resolveBox fill:#FFF4E5,stroke-width:2px
classDef develBox fill:#E5F3FF,stroke-width:2px
class conflict conflictBox
class resolve resolveBox
class devel develBox
linkStyle 0 stroke:#0066FF,stroke-width:2px
linkStyle 1 stroke:#0066FF,stroke-width:2px
linkStyle 2 stroke:#0066FF,stroke-width:2px
linkStyle 3 stroke:#0066FF,stroke-width:2px
linkStyle 4 stroke:#0066FF,stroke-width:2px
linkStyle 5 stroke:#0066FF,stroke-width:2px
linkStyle 6 stroke:#0066FF,stroke-width:2px
linkStyle 7 stroke:#CC0000,stroke-width:3px,stroke-dasharray: 5 5
linkStyle 8 stroke:#FF6600,stroke-width:3px,stroke-dasharray: 8 4
linkStyle 9 stroke:#FF6600,stroke-width:3px,stroke-dasharray: 8 4
linkStyle 10 stroke:#FF6600,stroke-width:4px
A devel branch is very useful in the following cases:
Several different people are working at the same time on different features
The features can present interdependencies
In these cases, the devel branch will serve as a convergence point for the different features and reduce the integration time when merging into the main branch. The conflicts are detected early before being presented into the main branch. A devel branch also reduces the risk of having late conflicts when features are merged late to main (everything is merged in devel first). Resolving all conflicts in the devel branch will also make sure to have a clean main branch.
In the case of a single individual or a small team working together, or when tasks have no interdependencies, the devel branch could be avoided.
An example of workflow using a devel branch is detailed at this link.
5.3 One main branch duplicated per milestone
Across a clinical portfolio, multiple studies and milestones can share a common set of base programs, while some milestones require additional specific code. One approach is to store all programs in the main branch and create a dedicated copy when finalizing a milestone.
flowchart TB
main["Main branch"]
csr(["CSR"])
dmc1(["DMC #1"])
dmc2(["DMC #2"])
main -.-> csr
main -.-> dmc1
main -.-> dmc2
flag_csr[("Tag #1")]
flag_dmc1[("Tag #2")]
flag_dmc2[("Tag #3")]
csr -.-> flag_csr
dmc1 -.-> flag_dmc1
dmc2 -.-> flag_dmc2
Several possibilities can be applied. The most common is to use a tag release on the main branch to identify the milestone. It will save the main branch and clearly identify it as a milestone reached. This link provides more details about tagging releases on GitHub. Similar actions can be performed on other source code management platforms.
Another way is to duplicate the main branch and use a flag to identify the milestone, with the possibility to lock it. A branch can be locked using a protection rule (see #13).
5.4 One main branch per milestone
A single study often needs to deal with different activities, sometimes happening concurrently. One potential way to use git in these cases could be to have:
1 repository for the study
1 main branch per activity (CSR, DMC #n, DSUR, etc)
This branching strategy would allow for working separately on each activity, keeping them intact, but also to work on both at the same moment if needed. The following examples shows a repository dedicated to a single study with the 2 following main branches:
CSR: for daily ongoing activities related to the CSR preparation
DMC #1: specific milestone happening soon
Two issues are handled at the same moment. Issue #1 is dedicated to an efficacy endpoint that should not be part of the DMC, while Issue #2 is strictly dedicated to the DMC activity and will not be part of the final CSR outputs.
flowchart TD
csr["CSR"]
dmc["DMC"]
branch-1("Branch #1")
branch-2("Branch #2")
pr1>"Pull request"]
pr2>"Pull request"]
csr --> branch-1
dmc --> branch-2
branch-1 --> pr1
pr1 --> |"Merge #1"| csr
branch-2 --> pr2
pr2 --> |"Merge #2"| dmc
5.4.1 Common issue
If something common to more than one of the main branches should be made, it is possible to make as many pull requests as required. The only tricky part is to define the source branch. In this example, we take the CSR.
flowchart TB
csr["CSR"]
dmc["DMC"]
branch-c("Common issue branch")
prc>"Pull request"]
prd>"Pull request"]
csr --> branch-c --> prc
branch-c -.-> prd
prc --> csr
prd -.-> dmc
linkStyle 0 stroke:#0066FF,stroke-width:2px
linkStyle 1 stroke:#0066FF,stroke-width:2px
linkStyle 2 stroke:#0066FF,stroke-width:2px
linkStyle 3 stroke:#FF6600,stroke-width:3px
linkStyle 4 stroke:#FF6600,stroke-width:3px
5.5 Hot fixes
Hot fixes are urgent corrections that need to be applied to a specific milestone that has already been released or tagged. These fixes are typically critical bugs or issues discovered after a milestone has been finalized and need immediate attention.
It should focus only on the resolution of the critical issue, and prevent it from happening again. It should be clearly documented and tested, even given the urgency of the fix.
Hot fixes are essential for maintaining the integrity of released milestones while allowing development to continue on the main branch without disruption.
This article gives more details about hot fixes and how to avoid them.
When working with milestone branches, hot fixes can be applied in different ways depending on the branching strategy used:
5.5.1 Hot fixes with tagged milestones
When milestones are identified using tags on the main branch, a hot fix should be created from the specific tag, applied, and then merged back to main. This ensures the fix is available for future milestones while also addressing the specific tagged milestone.
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#87CEEB'}}}%%
gitGraph
commit id: "Initial"
commit id: "Milestone 1"
commit id: "Tag: v1.0"
commit id: "Continue dev"
branch hotfix-1.0.1
checkout hotfix-1.0.1
commit id: "Hot fix"
checkout main
merge hotfix-1.0.1 tag: "PR: Hot fix"
commit id: "Tag: v1.0.1"
5.5.2 Hot fixes with milestone branches
When using separate milestone branches (e.g., CSR, DMC), hot fixes should be created from the specific milestone branch, applied, and then merged back to that milestone branch. If the fix is also relevant for other milestones or the main development branch, it can be cherry-picked or merged to those branches as well.
flowchart TB
milestone["Milestone branch<br/>(e.g., DMC #1)"]
hotfix("Hot fix branch")
pr>"Pull request"]
milestone --> hotfix
hotfix --> pr
pr -->|"Merge hot fix"| milestone
main["Main branch"]
pr -->|"Cherry-pick or merge"| main
This article provides more details about cherry-picking when using a devel branch.
5.6 Pros and cons
Many other strategies can be defined, and the choice depends on the needs of each study team. Also, some of the strategies displayed above can be combined (for instance using a devel branch with a milestone main branch strategy).
Here is a summary of the pros and cons of the different strategies presented above:
| Strategy | Pros | Cons |
|---|---|---|
| Devel branch |
|
|
| Main branch duplicated |
|
|
| One main branch per milestone |
|
|