main— protected, PR-only. Always contains the code of the currently supported release version.develop— protected, PR-only. All feature work lands here; publishes pre-release packages.release/X.Y— made fromdevelopfor a release; its PR goes intomain.hotfix/…— made from a release tag for a fix to the released version; its PR goes intomain.merge/vX.Y.Z— made frommainafter a release or hotfix, to bringmainback intodevelop.- PR merge strategy: merge commits (squash and rebase are disabled).
All published packages share one version (lockstep, ADR-0008). It comes from the root version.json, which Nerdbank.GitVersioning (NBGV) reads; projects have no version.json of their own.
-
developcarries the number of the next release plus a preview suffix:"version": "0.1.0-preview.{height}".{height}is the git height: the number of commits from the one that set this version up to the one being built, both included, counting only commits that change a package input listed inpathFilters(src,version.json,Directory.Build.props,Directory.Packages.props,OpenUrzednik.Packages.slnf, the strong-name keyOpenUrzednik.snk, and the packed iconsassets/branding/icon.pngandassets/branding/icon-nbp.png). -
Only
mainis a public ref (publicReleaseRefSpec). Every other branch,developand release branches included, appends the commit id:developpublishes versions like0.1.0-preview.12.g1a2b3c4d5e. -
A release removes the suffix:
"1.1.0-preview.{height}"→"1.1.0". SemVer sorts a prerelease below the release with the same number, so every release sorts above its previews:1.1.0-preview.1 < … < 1.1.0-preview.37 < 1.1.0 < 1.1.1 < 1.2.0-preview.1 -
After a release,
developmoves to the next minor ("1.2.0-preview.{height}"). A PR that adds a breaking change moves it to the next major instead ("2.0.0-preview.{height}"). Until 1.0.0,developstays at"0.1.0-preview.{height}", and breaking changes don't change the version. -
Dependencies between the packages are minimums. A
ProjectReferencebecomes a NuGet dependency on the same version or later:OpenUrzednik.Nbp1.1.0 depends onOpenUrzednik.Core>= 1.1.0. Update all OpenUrzednik packages together. -
1.0.0 is the one exception: that release changes the number (
"0.1.0-preview.{height}"→"1.0.0") instead of removing the suffix. The versions published before this scheme (0.1.0-preview,1.0.0-preview.N,1.0.N-preview.g<commit>) are unlisted on nuget.org, and thev0.1.0-previewtag from that time is ignored. Unlisting doesn't change how versions sort: a project already on a1.0.Npreview must update to 1.0.0 explicitly.
- Work happens on feature branches → PR (merge commit) into
develop. - Merge into
develop→ if the merge changes a package input,nuget-pre-release.ymlpublishes a preview of every package, all with the same, new version. Itspathsfilter is thepathFilterslist, so merges that change only docs, tests or CI publish nothing, and each published preview sorts above the one before. A Dependabot bump of a test-only package still publishes a preview, becauseDirectory.Packages.propsalso holds the versions of the packages' own dependencies. - A release follows the steps below.
With Claude Code, /prepare-release runs steps 1–5 with extra checks, and /prepare-release after checks the published packages and does step 7. The steps below are the same process by hand.
-
Create
release/X.Yfrom the latestdevelop. -
In the root
version.json, remove the preview suffix:- "version": "1.1.0-preview.{height}", + "version": "1.1.0",
-
Check the packages locally. Builds of the release branch still get
1.1.0-g<commit id>, because onlymainis public, so pack withPublicRelease=trueinto a folder that doesn't exist yet (deleteartifacts/releasefirst):dotnet pack OpenUrzednik.Packages.slnf -c Release -o artifacts/release -p:ContinuousIntegrationBuild=true -p:PublicRelease=true
Every
.nupkginartifacts/releasemust have the version1.1.0. -
Push the branch, then write the release notes: run
/release-notes 1.1.0, reviewdocs/release-notes/v1.1.0.md, commit and push it. They come from the release labels of the PRs since the last release; the packages'releaseNoteslink to the GitHub Release made from this file. -
Open a PR from
release/X.Yintomain(labelskip-changelog; its contents are already listed by their own PRs), once the release's milestone has no open issues (GITHUB-SETUP.md). Thebuild (net10.0)job fails the PR ifversion.jsonhas a prerelease suffix or the notes file is missing. -
Merge (merge commit) →
nuget-release.ymlpublishes every package as1.1.0, tags the repov1.1.0and creates one GitHub Release with the notes file as its body. It stops before publishing if the version still has a prerelease suffix, if the packages don't share one version, if that version was already released from another commit, or if the notes file is missing. -
Back-merge (below), setting
develop'sversion.jsonto the next minor ("1.2.0-preview.{height}").
We assume full API compatibility between 1.0 and 1.1 (per SemVer — a minor bump must not
break compatibility), so within one major version there is no need for a separate support line or for NBGV
branch-naming. For the previous major version, see Support for the previous major version. The full process:
-
Create a branch from the tag of the last released version:
git checkout -b hotfix/nbp-bug123 v1.0.0
-
Set the root
version.jsonto the next patch,"1.0.1". NBGV doesn't raise the patch by itself: a version with three parts is used as written, and the git height goes only into the assembly file version. -
Fix the bug and open a PR hotfix →
main(required, sincemainis protected), labelledbug. Then run/release-notes 1.0.1, and commit and pushdocs/release-notes/v1.0.1.md: the notes list the hotfix PR itself, so it must exist first, and the PR'sbuild (net10.0)job fails until the file is there. -
Merge (merge commit) →
nuget-release.ymlpublishes1.0.1, tagsv1.0.1, creates the GitHub Release. -
Back-merge (below), so the fix reaches
develop.developkeeps its own version (e.g."1.1.0-preview.{height}").
After every release or hotfix, main goes back into develop, so develop always contains main and the next release PR shows only new work:
git fetch origin
git switch -c merge/v1.1.0 origin/main
git merge --no-commit origin/develop # on a version.json conflict, keep develop's side (--theirs here)
# after a release: set version.json to the next minor, e.g. "1.2.0-preview.{height}"
# (unless develop already carries a higher version, e.g. "2.0.0-preview.{height}")
git diff origin/develop -- version.json # after a hotfix: no output; after a release: only the next-minor change
git commit
git push -u origin merge/v1.1.0Check version.json with that git diff even when git reports no conflict: after a release, git takes main's stable version silently.
Open a PR from merge/v1.1.0 into develop, label it skip-changelog, and merge it with a merge commit.
Never open a PR straight from main into develop: develop would get the stable version from main's version.json and publish 1.1.0-g<commit> previews, and resolving a conflict in GitHub's web editor would commit to main.
A vulnerability is reported privately (SECURITY.md): acknowledge it within 14 days, and aim to publish the fix within 90 days of confirming it.
- Create a draft GitHub Security Advisory for the report (Security → Advisories), and prepare and review the fix privately, e.g. in the advisory's temporary private fork, so nothing is public too early.
- When the fix is ready to ship, push it as a
hotfix/…branch and release it with the hotfix procedure above. The hotfix PR is public and runs the required checks, so open it only then (hotfix/…from the last release tag, the next patch version, release notes, back-merge). For the previous major's support line, see below. - Publish the advisory: ecosystem NuGet, every affected package, the affected and patched version ranges, a CVE when it is warranted, and credit for the reporter unless they decline.
- Deprecate the affected versions of every affected package on nuget.org (Manage package → Deprecation, the critical-bugs reason, alternate package: the patched version).
- Tell the reporter that the fix and the advisory are out.
Fixes go to the latest minor of the latest major only. When 2.0.0 is released, 1.x still gets security fixes for 6 months, from a support branch release/1.x made from the last v1.* tag. Before that release, the branch needs ^refs/heads/release/1\.x$ in publicReleaseRefSpec (version.json), a trigger in the release workflow, the publishing environment's branch rule, CI triggers and ruleset protection for PRs into it, and a change to ADR-0008, which today makes main the only public ref (#119). A 1.x security fix then goes from a hotfix/… branch made from release/1.x into release/1.x, and is released from there.
Nerdbank.GitVersioning must have PrivateAssets="all" so it doesn't end up as a dependency of the published package. It is referenced once for all libraries in src/Directory.Build.props; the version is managed centrally in Directory.Packages.props:
<PackageReference Include="Nerdbank.GitVersioning" PrivateAssets="all" />Branch protection, required status checks and publishing environments are described in GITHUB-SETUP.md. The decision behind this flow is ADR-0008.