release_process.md
Lance maintains a linear commit history with a controlled release process.
main branch first.main branch.release/vX.Y. These are typically created once every two weeks.gitGraph
commit
branch feature
checkout feature
commit
checkout main
merge feature
branch bugfix
checkout bugfix
commit id: "bugfix"
checkout main
branch "release/v1.4"
checkout "release/v1.4"
commit tag: "1.4.0-rc.1"
commit tag: "1.4.0"
checkout main
merge bugfix
commit id: "merged"
checkout "release/v1.4"
cherry-pick id: "merged"
commit tag: "1.4.1-rc.1"
commit tag: "1.4.1"
checkout main
commit tag: "2.0.0-beta.1" id: "breaking"
checkout "release/v1.4"
cherry-pick id: "breaking" tag: "1.5.0-rc.1"
branch "release/v1.5"
checkout "release/v1.5"
commit tag: "1.5.0"
Lance uses version numbers inspired by semantic versioning, but with flexibility for practical release management. Specifically, minor releases can be cut from existing release branches when the main branch is targeting a major release.
Lance uses version numbers with prerelease identifiers:
X.Y.Z (e.g., 1.3.0)X.Y.Z-beta.N (e.g., 1.3.0-beta.1, 1.3.0-beta.2)X.Y.Z-rc.N (e.g., 1.3.0-rc.1, 1.3.0-rc.2)| Language | Stable release | RC release | Beta release |
|---|---|---|---|
| Rust | crates.io | Not published (use git tag) | Not published (use git tag) |
| Python | PyPI | fury.io | fury.io |
| Java | Maven Central | Maven Central | Maven Central |
| Protobuf | Buf Schema Registry | Buf Schema Registry | Buf Schema Registry |
| Release Type | GitHub Release Type | Start Commit (exclusive) | End Commit (inclusive) | Explanation |
|---|---|---|---|---|
| Stable (Major/Minor from main) | Release | release-root/X.Y.0-beta.N | vX.Y.0 | All changes from main + RC fixes |
| Stable (Minor from release) | Release | vX.(Y-1).Z (last stable) | vX.Y.0 | Changes since last stable on source release branch |
| Stable (Patch) | Release | vX.Y.(Z-1) | vX.Y.Z | Only changes in this patch release |
| RC (Major/Minor from main) | Pre-Release | release-root/X.Y.0-beta.N | vX.Y.0-rc.N | All changes for the release |
| RC (Minor from release) | Pre-Release | vX.(Y-1).Z (last stable) | vX.Y.0-rc.N | Changes since last stable on source release branch |
| RC (Patch) | Pre-Release | vX.Y.(Z-1) | vX.Y.Z-rc.N | Only changes in this patch release |
| RC (Iterations) | Pre-Release | Same as initial RC | vX.Y.Z-rc.N | Same comparison as initial RC (not against previous RC) |
| Beta (Main branch) | Pre-Release | release-root/X.Y.Z-beta.N | vX.Y.Z-beta.N | Changes since last stable release RC cut in main branch |
| Beta (Release branch) | Pre-Release | vX.Y.(Z-1) | vX.Y.Z-beta.N | Changes since last stable release |
X.Y.Z-beta.N-beta.0 (unreleased)
-beta.1+release/v{major}.{minor} (e.g., release/v1.3)rc.1 → rc.2 → stable → beta.0 → rc.1 (for patches)All changes must be committed to the main branch first:
This ensures main always has the complete history and release branches only contain subsets of main's changes.
%%{init: {'theme':'base', 'themeVariables': { 'fontSize':'14px'}}}%%
flowchart LR
subgraph main["Main Branch"]
direction LR
M0["1.3.0-beta.2
📍 release-root/1.4.0-beta.N
📍 release-root/2.0.0-beta.N"] --> M1["1.4.0-beta.0"]
M1 --> M2["1.4.0-beta.1
🏷️ v1.4.0-beta.1"]
M2 --> M3["2.0.0-beta.1
🏷️ v2.0.0-beta.1"]
end
subgraph release["Release Branch: release/v1.3"]
direction LR
R1["1.3.0-rc.1
🏷️ v1.3.0-rc.1"] --> R2["1.3.0
🏷️ v1.3.0"]
R2 --> R3["1.3.1-beta.0"]
R3 --> R4["1.3.1-rc.1
🏷️ v1.3.1-rc.1"]
R4 --> R5["1.3.1
🏷️ v1.3.1"]
R5 --> R6["1.3.2-beta.0"]
end
M0 -.->|"create RC
(no breaking changes)"| R1
Flow explanation:
1.3.0-beta.2 has release-root/1.4.0-beta.N (created when cutting v1.3.0-rc.1, pointing to this commit) and release-root/2.0.0-beta.N (created when breaking changes bumped major version, pointing to same commit) → M1 bumps to 1.4.0-beta.0 (unreleased) → M2 publishes 1.4.0-beta.1 (preview, tagged) → M3 publishes 2.0.0-beta.1 after detecting breaking changes (tagged)release/v1.3 created from M0, starts at 1.3.0-rc.1 (tagged) → 1.3.0 (stable, tagged) → 1.3.1-beta.0 → 1.3.1-rc.1 (tagged) → 1.3.1 (stable, tagged) → 1.3.2-beta.0release-root/1.4.0-beta.N and release-root/2.0.0-beta.N point to M0 (same commit), showing that 2.0.0 is a major version bump from the 1.3.0-rc.1 baselineNote: All commits are linear on their respective branches. beta.0 = unreleased, beta.1+ = published previews.
%%{init: {'theme':'base', 'themeVariables': { 'fontSize':'14px'}}}%%
flowchart LR
subgraph main["Main Branch (at 2.0.0)"]
direction LR
M1["2.0.0-beta.0"] --> M2["2.0.0-beta.1
🏷️ v2.0.0-beta.1"]
end
subgraph release13["Release Branch: release/v1.3"]
direction LR
R1["1.3.0
🏷️ v1.3.0"] --> R2["1.3.1-beta.0"]
R2 --> R3["1.3.1
🏷️ v1.3.1"]
end
subgraph release14["Release Branch: release/v1.4"]
direction LR
S1["1.4.0-rc.1
🏷️ v1.4.0-rc.1"] --> S2["1.4.0
🏷️ v1.4.0"]
S2 --> S3["1.4.1-beta.0"]
end
R3 -.->|"create minor release
(main is at 2.x)"| S1
Flow explanation:
2.0.0-beta.N (major version)1.3.1 and needs a new minor release with featuresrelease/v1.3 (not from main) because main is at a different major versionv1.4.0 compare against v1.3.1 (latest stable on source branch)Purpose: Publish preview releases for testing before creating release candidates.
Steps:
main (or any release branch)true (test first)falseResult: Creates a beta tag (e.g., v1.4.0-beta.1) and publishes preview artifacts to fury.io, Maven Central, and Buf Schema Registry.
Release Notes: For the first beta (beta.1), release notes include all changes since the release-root tag. For subsequent betas (beta.2+), release notes only include incremental changes since the previous beta.
<details> <summary>How beta versioning works</summary>For main branch: Automatically checks for breaking changes and bumps version:
1.4.0-beta.0 → 1.4.0-beta.1)1.4.0-beta.1 → 2.0.0-beta.1)2.0.0-beta.1 → 2.0.0-beta.2)For release branches: Bumps beta number (beta.N → beta.N+1)
Use cases:
How it works: Mark PRs with the breaking-change label in GitHub. The workflow automatically detects these and bumps the major version when publishing beta releases from main.
What counts as breaking:
Release root tags mark the base commits for breaking change detection. The tag naming reflects the beta version series on main, while the tag points to the RC commit being compared against.
Tag Format: release-root/{major}.{minor}.{patch}-beta.N
When created:
release-root/1.4.0-beta.N pointing to the commit before the RC branch was createdrelease-root/2.0.0-beta.N pointing to the SAME commit with the SAME base RC versionKey properties:
release-root/1.4.0-beta.N and release-root/2.0.0-beta.N point to the same commit on main (the commit before the RC branch was created)When a minor release is created from an existing release branch (not from main), a minor-release-root tag is created to track the comparison base for release notes.
Tag Format: minor-release-root/{major}.{minor}.0
create-release-branch workflow with source_release_branch parameterv1.3.1)determine_previous_tag to find the correct comparison baseExample: When creating release/v1.4 from release/v1.3 (where latest stable is v1.3.1):
minor-release-root/1.4.0 with message v1.3.1Breaking change detection happens on every beta publish from main branch:
release-root/{current_version}-beta.N
1)Starting from v1.3.0-rc.1 cut, main at 1.4.0-beta.0 with release-root/1.4.0-beta.N (Base: 1.3.0-rc.1):
1.4.0-beta.0 + no breaking → 1.4.0-beta.11.4.0-beta.1 + breaking → 2.0.0-beta.1
release-root/2.0.0-beta.N pointing to same commit, message still "Base: 1.3.0-rc.1"2.0.0-beta.1 + breaking → 2.0.0-beta.2
2.0.0-beta.2 + no breaking → 2.0.0-beta.3Key insight: Multiple beta version series can share the same release-root commit, with major version bumped only once when first detected.
</details>Purpose: Create a new major or minor release from the main branch.
Steps:
truefalse to create release branch and RCrc.2, rc.3, etc.v1.3.0-rc.2)Result:
release/v1.3) with RC tag (e.g., v1.3.0-rc.1)1.4.0-beta.0)v1.3.0) and publishes to PyPI, crates.io, Maven CentralCreate Release Branch workflow:
1.3.0-beta.2)2.0.0-rc.1), bumps main to 2.1.0-beta.01.3.0-rc.1), bumps main to 1.4.0-beta.0release/v{major}.{minor} branch from main HEADApprove RC workflow:
rc.N to stablerelease-root/{version}-beta.N tagbeta.0 (e.g., 1.3.0 → 1.3.1-beta.0)Purpose: Create a new minor release from an existing release branch when main is targeting a major release.
When to use: When main branch has breaking changes and is targeting a major version (e.g., 2.0.0), but you need to release new features for users who aren't ready to upgrade to the new major version.
Prerequisites:
2.0.0-beta.N where patch = 0 and minor = 0)release/v1.3)Steps:
release/v1.3true1.4.0-rc.1)false to create new release branch and RCResult:
release/v1.4) from source branchv1.4.0-rc.1)v1.3.1)Create Release Branch workflow (with source_release_branch):
X.0.0-beta.N where patch = 0)release/v1.3)1.3.1-beta.0 → 1.4.0-rc.1)release/v1.4) from source branch HEADKey differences from main branch flow:
Purpose: Release critical bug fixes for an existing release.
Steps:
release/v1.3)release/v1.3) and dry_run: truefalse to create the patch RCrc.2, rc.3, etc.v1.3.1-rc.1)Result:
v1.3.1-rc.1) on release branchv1.3.1) and publishes to PyPI, crates.io, Maven Centralbeta.0 (e.g., 1.3.2-beta.0)X.Y.Z-beta.N between releases (auto-bumped after stable)v1.3.0)# 1. Main at 1.4.0-beta.0 (unreleased after RC cut for v1.3.0)
# 2. Want to publish preview for testing
Workflow: Publish Beta
branch: main
Result:
- Looks for release-root/1.4.0-beta.N → found
- Extracts base: 1.3.0-rc.1 (major: 1) from tag message
- No breaking changes detected
- Bumped to 1.4.0-beta.1
- Tagged v1.4.0-beta.1
- Created GitHub Pre-Release with release notes from release-root/1.4.0-beta.N to v1.4.0-beta.1
- Published artifacts to fury.io
# 3. More changes, publish again (with breaking changes)
Workflow: Publish Beta
branch: main
Result:
- Looks for release-root/1.4.0-beta.N → found
- Extracts base: 1.3.0-rc.1 (major: 1) from tag message
- Breaking changes detected, current major (1) == base major (1)
- Bumped to 2.0.0-beta.1 (beta resets on major bump)
- Created release-root/2.0.0-beta.N → same commit, message "Base: 1.3.0-rc.1"
- Tagged v2.0.0-beta.1
- Created GitHub Pre-Release with release notes from release-root/2.0.0-beta.N to v2.0.0-beta.1
- Published artifacts to fury.io
# 4. More changes, publish again (still has breaking changes)
Workflow: Publish Beta
branch: main
Result:
- Looks for release-root/2.0.0-beta.N → found
- Extracts base: 1.3.0-rc.1 (major: 1) from tag message
- Breaking changes detected, but current major (2) > base major (1)
- No major bump needed (already bumped)
- Bumped to 2.0.0-beta.2
- Tagged v2.0.0-beta.2
- Created GitHub Pre-Release with release notes from release-root/2.0.0-beta.N to v2.0.0-beta.2
- Published artifacts to fury.io
# 1. Main is at 1.3.0-beta.2
# 2. Create release branch (version auto-determined from main)
Workflow: Create Release Branch
Result:
- Checks for breaking changes since release-root/1.3.0-beta.N
- No breaking changes detected
- Created release/v1.3 at 1.3.0-rc.1
- Tagged v1.3.0-rc.1
- Created GitHub Pre-Release with release notes from release-root/1.3.0-beta.N to v1.3.0-rc.1
- Bumped main to 1.4.0-beta.0 (unreleased)
- Tagged release-root/1.4.0-beta.N → points to commit before RC branch, message "Base: 1.3.0-rc.1"
- GitHub Discussion created
# 3. Vote on RC
- Navigate to Discussion thread
- Test RC artifacts
- Vote with +1, 0, -1
# 4. Approve RC
Workflow: Approve RC
rc_tag: v1.3.0-rc.1
Result:
- release/v1.3 @ 1.3.0 (stable)
- Tagged v1.3.0
- Generated release notes comparing v1.3.0 vs release-root/1.3.0-beta.N
- Created GitHub Release (not pre-release)
- Stable artifacts published
- Release branch auto-bumped to 1.3.1-beta.0
- Main unchanged (already at 1.4.0-beta.0)
# 5. Later: Publish first beta after RC (no breaking changes)
Workflow: Publish Beta
branch: main
Result:
- Looks for release-root/1.4.0-beta.N → found
- Extracts base: 1.3.0-rc.1 (major: 1) from tag message
- No breaking changes detected
- Bumped from 1.4.0-beta.0 to 1.4.0-beta.1
- Tagged v1.4.0-beta.1
- Created GitHub Pre-Release with release notes from release-root/1.4.0-beta.N to v1.4.0-beta.1
- Published artifacts to fury.io
# 6. More changes, publish second beta (breaking changes introduced!)
Workflow: Publish Beta
branch: main
Result:
- Looks for release-root/1.4.0-beta.N → found
- Extracts base: 1.3.0-rc.1 (major: 1) from tag message
- Breaking changes detected, current major (1) == base major (1)
- Bumped from 1.4.0-beta.1 to 2.0.0-beta.1 (beta resets on major bump)
- Created release-root/2.0.0-beta.N → same commit, message "Base: 1.3.0-rc.1"
- Tagged v2.0.0-beta.1
- Created GitHub Pre-Release with release notes from release-root/2.0.0-beta.N to v2.0.0-beta.1
- Published artifacts to fury.io
# 7. More changes, publish third beta (still has breaking changes)
Workflow: Publish Beta
branch: main
Result:
- Looks for release-root/2.0.0-beta.N → found
- Extracts base: 1.3.0-rc.1 (major: 1) from tag message
- Breaking changes detected, but current major (2) > base major (1)
- No major bump needed (already bumped from base)
- Bumped to 2.0.0-beta.2
- Tagged v2.0.0-beta.2
- Published artifacts
# 8. Eventually: Cut RC for v2.0.0
Workflow: Create Release Branch
Result:
- Main at 2.0.0-beta.2
- Checks for breaking changes since release-root/2.0.0-beta.N
- No additional breaking changes (major already bumped)
- Created release/v2.0 at 2.0.0-rc.1
- Tagged v2.0.0-rc.1
- Bumped main to 2.1.0-beta.0
- Tagged release-root/2.1.0-beta.N → points to commit before RC branch, message "Base: 2.0.0-rc.1"
# 1. Start with release/v1.3 @ 1.3.1-beta.0 (auto-bumped after previous stable release)
# 2. Critical bug found in 1.3.0
# 3. Fix committed to main first, then cherry-picked to release/v1.3
# 4. Create patch RC
Workflow: Create RC
release_branch: release/v1.3
Result:
- Branch at 1.3.1-beta.0
- Created 1.3.1-rc.1
- Tagged v1.3.1-rc.1
- Created GitHub Pre-Release with release notes from v1.3.0 to v1.3.1-rc.1
- GitHub Discussion created
# 5. Vote passes
# 6. Approve patch RC
Workflow: Approve RC
rc_tag: v1.3.1-rc.1
Result:
- release/v1.3 @ 1.3.1
- Tagged v1.3.1
- Generated release notes comparing v1.3.1 vs v1.3.0
- Created GitHub Release (not pre-release)
- Stable artifacts published
- Auto-bumped to 1.3.2-beta.0 (ready for next patch)
- Main unchanged
# Scenario: Main is at 2.0.0-beta.N (major version), need to release v1.4.0 with new features
# 1. Main is at 2.0.0-beta.1 (breaking changes introduced)
# 2. release/v1.3 is at 1.3.1-beta.0 (after releasing v1.3.1)
# 3. Cherry-pick desired features from main to release/v1.3
# 4. Create minor release branch from release/v1.3
Workflow: Create Release Branch
source_release_branch: release/v1.3
Result:
- Validates main is at major version (2.0.0-beta.1)
- Source branch at 1.3.1-beta.0
- Created release/v1.4 at 1.4.0-rc.1
- Tagged v1.4.0-rc.1
- Found latest stable: v1.3.1
- Created GitHub Pre-Release with release notes from v1.3.1 to v1.4.0-rc.1
- Main NOT modified (stays at 2.0.0-beta.1)
- GitHub Discussion created
# 5. Vote on RC (3-day voting for minor release)
- Navigate to Discussion thread
- Test RC artifacts
- Vote with +1, 0, -1
# 6. Approve RC
Workflow: Approve RC
rc_tag: v1.4.0-rc.1
Result:
- release/v1.4 @ 1.4.0
- Tagged v1.4.0
- Generated release notes comparing v1.4.0 vs v1.3.1
- Created GitHub Release (not pre-release)
- Stable artifacts published
- Release branch auto-bumped to 1.4.1-beta.0
- Main unchanged (stays at 2.0.0-beta.1)
# 7. Future: Can continue with patches on release/v1.4 or create release/v1.5 from it
# 1. Create initial RC
RC: v1.3.0-rc.1 on release/v1.3
# 2. Issue found during testing
# 3. Fix committed to release/v1.3 branch
# 4. Create new RC
Workflow: Create RC
release_branch: release/v1.3
Result:
- Branch at 1.3.0-rc.1
- Auto-incremented to 1.3.0-rc.2
- Tagged v1.3.0-rc.2
- Created GitHub Pre-Release with release notes from release-root/1.3.0-beta.N to v1.3.0-rc.2 (same comparison as rc.1, showing all changes)
- GitHub Discussion created
# 5. Vote passes
# 6. Approve rc.2
Workflow: Approve RC
rc_tag: v1.3.0-rc.2
Result:
- release/v1.3 @ 1.3.0
- Tagged v1.3.0
- Generated release notes comparing v1.3.0 vs release-root/1.3.0-beta.N (includes fixes from rc.1 and rc.2)
- Created GitHub Release (not pre-release)
- Stable artifacts published
- Release branch auto-bumped to 1.3.1-beta.0
- Main unchanged