This article defines a complete, production-ready branching model for systems built from multiple independently versioned software components managed as Git submodules. It covers both layers in detail:
- The component repositories (the individual software components).
- The parent / superproject repository (the actual product or system that composes the components).
The model prioritises safety, clear release contracts, predictable integration, fast hotfixes, and controlled experimentation while ensuring that the parent repository always consumes exact, immutable versions of its components.
Part 1 – Component Repositories
Each component is an independent Git repository that follows the same disciplined rules. Components are the units of independent evolution and release.
1.1 Branch Categories
| Category | Naming Pattern | Lifetime | Purpose | Notes |
|---|---|---|---|---|
| main | main | Long-lived | Production-ready code. Always releasable. | Always present |
| develop | develop | Long-lived | Integration branch for the next planned release. | Always present |
| hotfix | hotfix/* | Long-lived base + short-lived branches | Critical production fixes. | Base always exists; individual branches are temporary |
| feature | feature/* | Short-lived | New functionality or non-critical changes intended for the next release. | Created on demand, deleted after merge |
| experiment | experiment/* | Usually short-lived; long-lived only in exceptional cases | Exploratory work, spikes, prototypes, or temporary divergent approaches. | Optional; use sparingly; can be omitted |
Key principles
- main, develop, and the hotfix base are the only permanent long-lived branches.
- Feature branches are strictly short-lived. They exist only while work is in progress.
- The experiment category is optional and should not be overused. Most work belongs in feature/*. Use experiment/* only when the work is genuinely exploratory, may need to live longer than a normal feature, or must be preserved even if never productised. In ordinary development the category can be omitted entirely.
1.2 Tagging Rules
Tags exist only on main and on hotfix/* branches (specifically on commits that land on or will land on main).
- Never create a tag on develop, feature/*, or experiment/*.
- A tag always represents a released, immutable version of the component.
- After a release or hotfix is merged into main, the tag is present on main.
1.3 Semantic Versioning
Components use strict Semantic Versioning:
- MAJOR – Incompatible changes that break existing consumers (API, behaviour, data contracts).
- MINOR – Backward-compatible new functionality or significant improvements.
- PATCH – Backward-compatible bug fixes. Safe for any consumer to apply.
Recommended format: vMAJOR.MINOR.PATCH (example: v2.4.1). Pre-release identifiers (-rc.1, -beta.2) and build metadata (+build.42) are allowed when needed, but the three core numbers form the public contract.
1.4 Component Workflows
Normal feature development
- Create from the latest develop:Bash
git checkout develop && git pullgit checkout -b feature/descriptive-name - Implement, push, open a pull/merge request targeting develop.
- After review and successful CI, merge into develop (squash or rebase+merge according to team policy).
- Delete the feature branch.
Release from develop to main When develop contains the set of changes intended for the next release:
- Optionally create a short-lived release/x.y.z branch from develop for final stabilisation, version bumping, and changelog updates.
- Merge the release candidate (or develop itself) into main.
- Tag the resulting commit on main:Bash
git checkout maingit merge --no-ff develop # or the release branchgit tag -a v2.5.0 -m "Release 2.5.0"git push origin main --tags - Ensure the same changes are present on develop (merge back if a release branch was used).
Hotfix process Hotfixes address urgent issues present in a released version on main.
- Create the branch from the exact production tag (preferred) or from the tip of main:Bash
git checkout -b hotfix/fix-critical-issue v2.4.1 - Apply the minimal fix, add tests, bump the PATCH version.
- Merge into main, then tag:Bash
git checkout maingit merge --no-ff hotfix/fix-critical-issuegit tag -a v2.4.2 -m "Hotfix 2.4.2"git push origin main --tags - Immediately merge the same fix into develop so future releases contain it.
- Delete the short-lived hotfix/* branch.
Experiment handling
- Create from develop (or rarely from main if the experiment concerns production behaviour).
- Keep the branch only as long as the experiment is active.
- Outcomes:
- Successful → extract useful work into a normal feature/* branch or open a clean PR into develop.
- Unsuccessful → delete the branch or leave it with clear documentation explaining why it exists.
- Never tag an experiment branch. Anything that must be released must first enter the normal develop → main flow.
1.5 Edge Cases for Components
- Concurrent hotfixes – Multiple hotfix branches may exist simultaneously. Each must be merged independently into both main and develop. Version numbers must be coordinated (usually sequential PATCH increments).
- Hotfix that also belongs on a future major version – After merging the hotfix into main and develop, a separate cherry-pick or reimplementation may be required on a longer-lived experiment or future major branch.
- Breaking change discovered late – If a change already on develop turns out to be breaking, either revert it on develop or advance the MAJOR version when releasing to main.
- Abandoned feature – Simply delete the branch. No special cleanup is required beyond ensuring it was never merged.
- Long-running experiment that must be preserved – Document the reason clearly in the branch (README or commit messages). Prefer converting valuable parts into regular features rather than leaving permanent divergent branches.
Part 2 – Parent / Superproject Repository
The parent repository is the actual product or system. It composes multiple component submodules and defines how they work together. Its branching model is richer because it must manage system-level releases, integration testing, and coordinated version bumps of many components.
2.1 Branch Categories for the Parent
| Category | Naming Pattern | Lifetime | Purpose |
|---|---|---|---|
| main | main | Long-lived | Production-ready system. Always releasable. |
| develop | develop | Long-lived | Integration of the next system release. |
| release | release/* | Long-lived (per major/minor line) | Stabilisation, final testing, and preparation of a concrete system release. |
| hotfix | hotfix/* | Short-lived | Critical system-level fixes (or coordinated component hotfixes). |
| feature | feature/* | Short-lived | System-level changes (new integrations, configuration, orchestration). |
| experiment | experiment/* | Usually short-lived | System-level spikes or alternative compositions. |
The key addition compared with components is the release/* category. These are long-lived branches that represent a concrete release line (for example release/2.5 or release/3.0). They exist for the duration of stabilisation and may continue to receive patches after the initial release if a long-term support policy is required.
2.2 Critical Rule: Submodule Pinning
The parent repository must always reference components by tags.
- Never pin a submodule to a branch name (main, develop, etc.) or to an untagged commit in normal development or release flows.
- Always update the submodule pointer to a concrete tag (v2.4.1, v3.0.0-rc.2, etc.).
- This guarantees reproducibility, auditability, and that every parent commit records an exact, immutable set of component versions.
When a developer needs a newer component version, the workflow is:
- Ensure the desired component tag exists.
- In the parent repository:Bash
git submodule update --remote path/to/component # or manual checkout of the tag cd path/to/componentgit checkout v2.5.0 cd ../..git add path/to/componentgit commit -m "Bump component-x to v2.5.0"
2.3 Parent Workflows
Normal system development
- Feature work that affects only the parent (orchestration, configuration, glue code) lives on feature/* branches created from develop.
- When a component needs a new version, the parent updates the submodule pointer to the new tag on a feature branch or directly on develop (after review).
Preparing a system release
- When develop is ready for stabilisation, create a release branch:Bash
git checkout developgit checkout -b release/2.5 - On the release/* branch:
- Freeze or carefully control further component bumps.
- Perform final integration testing.
- Update system-level version, changelog, and release notes.
- Resolve any last-minute issues (these may require new PATCH tags on components).
- When ready, merge release/2.5 into main and tag the system release (e.g. v2.5.0).
- Merge release/2.5 back into develop so that future work continues from the stabilised base.
- The release/2.5 branch may remain open for a period if LTS patches are expected; otherwise it can be deleted after the merge to main.
System-level hotfixes
- Create hotfix/* from the production tag on main (or from the corresponding release/* branch if LTS is active).
- If the fix requires a component change, first create and release a component hotfix tag, then bump the submodule pointer on the parent hotfix branch.
- Merge the parent hotfix into main, tag the new system version, and also merge into develop and any active release/* branches that need the fix.
Experimentation at system level
- Use experiment/* for trying alternative component combinations, new architectural approaches, or large-scale spikes.
- These branches may temporarily pin components to non-release tags or even to branch tips for rapid iteration, but any experiment that is promoted must be cleaned up to use only proper tags before merging into develop or a release/* branch.
2.4 Edge Cases and Special Situations for the Parent
- Component not yet tagged – Do not merge a parent change that depends on an untagged component commit into develop or any release/* branch. Require the component team to produce a proper tag first (even a pre-release tag if necessary).
- Coordinated multi-component release – When several components must be released together, create the component tags first, then update all submodule pointers in a single parent commit on the appropriate branch (develop or release/*).
- Breaking component change – The parent must deliberately accept the new MAJOR version. This usually happens on a release/* or feature/* branch after integration testing. Document the breaking change in the system changelog.
- LTS / long-lived release lines – Keep the corresponding release/x.y branch alive. Hotfixes that apply only to that line are merged into the release branch and tagged from there; they are also merged into main and develop when appropriate.
- Submodule pointer drift – CI for the parent must verify that every submodule is at a tagged commit and that the recorded SHA matches the tag. Reject any PR that introduces an untagged submodule pointer (except on pure experiment branches).
- Emergency override – In a true production emergency it is permissible to temporarily pin to a specific commit SHA while a proper hotfix tag is being prepared. The temporary state must be replaced by a tagged version as soon as possible and the incident documented.
- Parent-only changes with no component updates – These still follow the normal feature → develop → release → main flow. The submodule pointers remain unchanged.
- Removing or replacing a component – Treat as a deliberate system change: update the parent on a feature or release branch, adjust build and documentation, and ensure the old component tag remains reachable for historical builds.
Part 3 – Interaction Between Parent and Components
- Components evolve and release independently according to their own schedule.
- The parent decides which component versions to consume by updating submodule pointers exclusively to tags.
- A system release is the combination of a specific set of component tags plus the parent’s own code and configuration at a given parent tag.
- Hotfixes may cascade: a component hotfix tag is produced first, then the parent consumes it and produces its own system hotfix tag.
- Experiments at either layer remain isolated until deliberately promoted through the normal integration paths.
Part 4 – Summary Cheat Sheet
Components
- Branches: main, develop, hotfix/*, feature/*, optional experiment/*.
- Tags only on main / hotfix commits that reach main.
- Semantic versioning enforced.
- Features → develop → release → main + tag.
- Hotfixes start from a production tag → main + tag, then into develop.
Parent / Superproject
- Branches: main, develop, release/* (long-lived per release line), hotfix/*, feature/*, optional experiment/*.
- Always pin submodules to tags — never to branches or untagged commits in normal flows.
- System releases are prepared on release/* branches, then merged to main and tagged.
- Component version bumps are explicit commits that change submodule pointers to new tags.
This dual-layer model gives each component freedom to evolve while giving the overall system strong guarantees of reproducibility, clear release history, and safe emergency procedures. Teams can adopt the full set of categories or start with the core set and introduce experiment and long-lived release/* branches only when the need arises.






