For the complete documentation index, see llms.txt. This page is also available as Markdown.

Versioning across branches

Versions are tracked on the server per branch. Each branch line versions itself independently, and git history reconciles them whenever lines merge. This page walks through the behaviours you will encounter in multi-branch development.

New branches

A new branch does not require any onboarding step. Its packages resolve their base versions from the release tags reachable in its history, and the first publish on the branch creates its own server records.

main:            sales published as 2.5.0
└── release/1.0  (branched from main)
      first build of a fix → sales 2.5.1.<build>

Parallel branches version independently

Two active branches do not coordinate. If both apply a feat: to a package last published as 2.5.0, both lines produce a 2.6.0.<build> — distinguished only by their build numbers. This is by design: artifact versions within a line stay meaningful, and uniqueness across lines is restored the moment the lines merge.

Merges reconcile through the version floor

When one line merges into another, the receiving branch's next build never goes backwards: its base version is lifted to the highest version visible in its history — server records or merged release tags, whichever is higher.

Just as importantly, a change only ever pays for its version once. Commits and pull requests that were already converted into a version on the source branch are recognised as "spent" and are not re-applied on the receiving branch. A cascaded change lands as a single patch — "same content, new artifact on this branch" — while new work on the receiving branch keeps expressing its own intent normally.

main:         feat(sales) shipped → 2.6.0

                    ▼  merge into release/1.0 (sales last at 2.5.2)
release/1.0:  next build → 2.6.1    ✓ floor lifted past 2.6.0, feat not re-applied
              (not 2.7.0 — the minor was already spent on main)

The same applies to explicit intents: a /version sales:major that produced 3.0.0 on main lands on a release branch as 3.0.1, not 4.0.0.

A typical hotfix round trip:

Already-built content is skipped

The floor stops a cascaded change from bumping twice. A change that is only a cascade — the receiving branch adds nothing of its own — produces no new artifact at all. When a merge brings in a package whose content is identical to a version already published on the source line, the build recognises it as already built and skips it:

The receiving branch keeps the source line's artifact instead of rebuilding an identical one. The package is rebuilt and versioned only when the receiving branch has its own change to it — then the floor lifts its base to the merged version and it takes a single patch, as above.

Mixed adoption across branches

It is perfectly fine for one branch to have adopted automated versioning while a parallel branch still uses manual versions — the sentinel lives in sfdx-project.json, so adoption is part of the branch's content:

  • While parallel, the adopted branch computes versions and the manual branch behaves classically. The project-level setting is shared, but manual packages always resolve through the user-override stage.

  • When the adopted branch merges into the manual one, the 0.0.0.NEXT sentinel merges with it (a normal three-way merge — only one side changed the line), and the receiving branch becomes server-managed for that package too. Its next build floors past the adopted line's published versions and continues monotonically.

  • The reverse merge leaves adoption intact, since the manual branch never touched the sentinel line.

Use merge commits between long-lived branches

Squash-merging a pull request within a branch is always fine — lazy adoption PRs, feature PRs and onboarding PRs can all be squashed.

Merging between long-lived branches (release lines, cascade flows, hotfix back-merges) must use real merge commits. A squash rewrites the incoming commits, which disconnects the source branch's release tags from the receiving branch's history — the version floor can no longer see what the other line already published, and intents are re-derived from the squashed message. The symptoms are duplicate versions across lines and double bumps.

Manual version bumps still trigger rebuilds

For packages you have not opted in, changing the version number in sfdx-project.json keeps its classic meaning — the package is rebuilt. This holds even in projects where other packages are server-managed: the descriptor comparison ignores the version field only for 0.0.0.NEXT packages, and any other descriptor change (dependencies, installation behaviour, scripts) triggers a rebuild for every package, managed or not.

Last updated

Was this helpful?