From d5cbaa9fa8e7a8895cc57990a3a44cfe058fa52c Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Thu, 12 Feb 2026 18:53:50 -0800 Subject: [PATCH 1/3] ci: improve release workflows --- ci/create_rc.sh | 15 ++++++++++++++- ci/publish_beta.sh | 31 +++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/ci/create_rc.sh b/ci/create_rc.sh index 6dcc53ae3cf..f6f8b0039a6 100644 --- a/ci/create_rc.sh +++ b/ci/create_rc.sh @@ -78,8 +78,21 @@ else echo "Warning: Previous tag not found" fi +# Determine release type based on version components +# - major: X.0.0 releases +# - minor: X.Y.0 releases where Y > 0 +# - patch: X.Y.Z releases where Z > 0 +if [ "${PATCH}" -gt 0 ]; then + RELEASE_TYPE="patch" +elif [ "${MINOR}" -eq 0 ]; then + RELEASE_TYPE="major" +else + RELEASE_TYPE="minor" +fi +echo "Release type: ${RELEASE_TYPE}" + echo "Successfully created RC tag: ${RC_TAG}" echo "RC_TAG=${RC_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true echo "RC_VERSION=${RC_VERSION}" >> $GITHUB_OUTPUT 2>/dev/null || true echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true -echo "RELEASE_TYPE=patch" >> $GITHUB_OUTPUT 2>/dev/null || true +echo "RELEASE_TYPE=${RELEASE_TYPE}" >> $GITHUB_OUTPUT 2>/dev/null || true diff --git a/ci/publish_beta.sh b/ci/publish_beta.sh index 43747f32cd8..f50798a52e0 100644 --- a/ci/publish_beta.sh +++ b/ci/publish_beta.sh @@ -169,17 +169,32 @@ fi BETA_MAJOR=$(echo "${NEW_VERSION}" | cut -d. -f1) BETA_MINOR=$(echo "${NEW_VERSION}" | cut -d. -f2) BETA_PATCH=$(echo "${NEW_VERSION}" | cut -d. -f3 | cut -d- -f1) +BETA_NUM=$(echo "${NEW_VERSION}" | sed 's/.*-beta\.//') if [[ "${BRANCH}" == "main" ]]; then - # For main branch: compare against release-root tag - BETA_RELEASE_ROOT_TAG="release-root/${BETA_MAJOR}.${BETA_MINOR}.${BETA_PATCH}-beta.N" - - if git rev-parse "${BETA_RELEASE_ROOT_TAG}" >/dev/null 2>&1; then - echo "Release notes will compare from ${BETA_RELEASE_ROOT_TAG} to ${BETA_TAG}" - RELEASE_NOTES_FROM="${BETA_RELEASE_ROOT_TAG}" + # For main branch: + # - First beta (beta.1): compare against release-root tag (all changes since last RC) + # - Subsequent betas (beta.2+): compare against previous beta tag (incremental changes) + if [ "${BETA_NUM}" -eq 1 ]; then + BETA_RELEASE_ROOT_TAG="release-root/${BETA_MAJOR}.${BETA_MINOR}.${BETA_PATCH}-beta.N" + if git rev-parse "${BETA_RELEASE_ROOT_TAG}" >/dev/null 2>&1; then + echo "First beta: release notes will compare from ${BETA_RELEASE_ROOT_TAG} to ${BETA_TAG}" + RELEASE_NOTES_FROM="${BETA_RELEASE_ROOT_TAG}" + else + echo "Warning: Release root tag ${BETA_RELEASE_ROOT_TAG} not found" + RELEASE_NOTES_FROM="" + fi else - echo "Warning: Release root tag ${BETA_RELEASE_ROOT_TAG} not found" - RELEASE_NOTES_FROM="" + # For beta.2+, compare against previous beta + PREV_BETA_NUM=$((BETA_NUM - 1)) + PREV_BETA_TAG="${TAG_PREFIX}${BETA_MAJOR}.${BETA_MINOR}.${BETA_PATCH}-beta.${PREV_BETA_NUM}" + if git rev-parse "${PREV_BETA_TAG}" >/dev/null 2>&1; then + echo "Subsequent beta: release notes will compare from ${PREV_BETA_TAG} to ${BETA_TAG}" + RELEASE_NOTES_FROM="${PREV_BETA_TAG}" + else + echo "Warning: Previous beta tag ${PREV_BETA_TAG} not found" + RELEASE_NOTES_FROM="" + fi fi elif [[ "${BRANCH}" =~ ^release/ ]]; then # For release branch: compare against last stable tag From 7ac2e33fbe0fdf79798be65ef4011c6b61c9b1af Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Thu, 12 Feb 2026 20:19:56 -0800 Subject: [PATCH 2/3] support new release mechanism --- .github/workflows/create-release-branch.yml | 45 ++- ci/create_release_branch.sh | 391 +++++++++++++------- ci/release_common.sh | 20 +- release_process.md | 237 ++++++++++-- 4 files changed, 526 insertions(+), 167 deletions(-) diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml index 5b7ccf5454c..dcaca062bf1 100644 --- a/.github/workflows/create-release-branch.yml +++ b/.github/workflows/create-release-branch.yml @@ -3,6 +3,11 @@ name: Create Release Branch on: workflow_dispatch: inputs: + source_release_branch: + description: 'Source release branch (optional, e.g., release/v1.3). Leave empty to create from main.' + required: false + type: string + default: '' dry_run: description: 'Dry run (simulate without pushing)' required: true @@ -25,7 +30,7 @@ jobs: - name: Check out repository uses: actions/checkout@v4 with: - ref: main + ref: ${{ inputs.source_release_branch || 'main' }} token: ${{ secrets.LANCE_RELEASE_TOKEN }} fetch-depth: 0 lfs: true @@ -39,16 +44,25 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} run: | - bash ci/create_release_branch.sh + bash ci/create_release_branch.sh "${{ inputs.source_release_branch }}" - name: Push changes (if not dry run) if: ${{ !inputs.dry_run }} run: | git push origin "${{ steps.create_branch.outputs.RELEASE_BRANCH }}" - git push origin main git push origin "${{ steps.create_branch.outputs.RC_TAG }}" - # Push release root tag (may already exist remotely if created during beta publish) - git push origin "${{ steps.create_branch.outputs.RELEASE_ROOT_TAG }}" || echo "Release root tag already exists remotely" + # When creating from main: push main and release root tag + # When creating from release branch: push minor release base tag + if [ -z "${{ inputs.source_release_branch }}" ]; then + git push origin main + # Push release root tag (may already exist remotely if created during beta publish) + git push origin "${{ steps.create_branch.outputs.RELEASE_ROOT_TAG }}" || echo "Release root tag already exists remotely" + else + # Push minor release base tag if it was created + if [ -n "${{ steps.create_branch.outputs.MINOR_RELEASE_BASE_TAG }}" ]; then + git push origin "${{ steps.create_branch.outputs.MINOR_RELEASE_BASE_TAG }}" + fi + fi - name: Generate Release Notes (if not dry run) if: ${{ !inputs.dry_run }} @@ -106,9 +120,18 @@ jobs: echo "- **Target Version:** ${{ steps.create_branch.outputs.RC_VERSION }}" >> $GITHUB_STEP_SUMMARY echo "- **RC Tag:** ${{ steps.create_branch.outputs.RC_TAG }}" >> $GITHUB_STEP_SUMMARY echo "- **Release Branch:** ${{ steps.create_branch.outputs.RELEASE_BRANCH }}" >> $GITHUB_STEP_SUMMARY - echo "- **Release Root Tag:** ${{ steps.create_branch.outputs.RELEASE_ROOT_TAG }}" >> $GITHUB_STEP_SUMMARY - echo "- **Main Version:** ${{ steps.create_branch.outputs.MAIN_VERSION }}" >> $GITHUB_STEP_SUMMARY - echo "- **Source Branch:** main (HEAD)" >> $GITHUB_STEP_SUMMARY + + if [ -z "${{ inputs.source_release_branch }}" ]; then + echo "- **Release Root Tag:** ${{ steps.create_branch.outputs.RELEASE_ROOT_TAG }}" >> $GITHUB_STEP_SUMMARY + echo "- **Main Version:** ${{ steps.create_branch.outputs.MAIN_VERSION }}" >> $GITHUB_STEP_SUMMARY + echo "- **Source Branch:** main (HEAD)" >> $GITHUB_STEP_SUMMARY + else + echo "- **Source Branch:** ${{ inputs.source_release_branch }}" >> $GITHUB_STEP_SUMMARY + echo "- **Release Notes Base:** ${{ steps.create_branch.outputs.PREVIOUS_TAG }}" >> $GITHUB_STEP_SUMMARY + if [ -n "${{ steps.create_branch.outputs.MINOR_RELEASE_BASE_TAG }}" ]; then + echo "- **Minor Release Base Tag:** ${{ steps.create_branch.outputs.MINOR_RELEASE_BASE_TAG }}" >> $GITHUB_STEP_SUMMARY + fi + fi echo "- **Dry Run:** ${{ inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY if [ "${{ inputs.dry_run }}" == "true" ]; then @@ -122,7 +145,11 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "**What happened:**" >> $GITHUB_STEP_SUMMARY echo "1. Created release branch ${{ steps.create_branch.outputs.RELEASE_BRANCH }} at ${{ steps.create_branch.outputs.RC_TAG }}" >> $GITHUB_STEP_SUMMARY - echo "2. Bumped main to ${{ steps.create_branch.outputs.MAIN_VERSION }} (unreleased)" >> $GITHUB_STEP_SUMMARY + if [ -z "${{ inputs.source_release_branch }}" ]; then + echo "2. Bumped main to ${{ steps.create_branch.outputs.MAIN_VERSION }} (unreleased)" >> $GITHUB_STEP_SUMMARY + else + echo "2. Created from ${{ inputs.source_release_branch }} (main unchanged)" >> $GITHUB_STEP_SUMMARY + fi echo "" >> $GITHUB_STEP_SUMMARY echo "**Next steps:**" >> $GITHUB_STEP_SUMMARY echo "1. Review and vote in the discussion thread" >> $GITHUB_STEP_SUMMARY diff --git a/ci/create_release_branch.sh b/ci/create_release_branch.sh index 594709bcb15..0f213127a93 100755 --- a/ci/create_release_branch.sh +++ b/ci/create_release_branch.sh @@ -2,180 +2,307 @@ set -e # Script to create a release branch with initial RC for major/minor release -# Always creates RC from the tip of main branch -# Checks for breaking changes and bumps major version if needed -# The version is automatically determined from main branch HEAD -# Usage: create_release_branch.sh -# Example: create_release_branch.sh - -TAG_PREFIX=${1:-"v"} +# Can create from main branch or from an existing release branch +# +# Usage: create_release_branch.sh [source_release_branch] [tag_prefix] +# +# Examples: +# create_release_branch.sh # Create from main branch +# create_release_branch.sh release/v1.3 # Create minor release from release/v1.3 +# create_release_branch.sh "" v # Create from main with custom prefix + +SOURCE_RELEASE_BRANCH=${1:-""} +TAG_PREFIX=${2:-"v"} readonly SELF_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -git checkout main -MAIN_VERSION=$(grep '^version = ' Cargo.toml | head -n1 | cut -d'"' -f2) -echo "Main branch current version: ${MAIN_VERSION}" +# Source common release functions +source "${SELF_DIR}/release_common.sh" + +# Determine if we're creating from main or from a release branch +if [ -n "${SOURCE_RELEASE_BRANCH}" ]; then + echo "Creating minor release from release branch: ${SOURCE_RELEASE_BRANCH}" + CREATE_FROM_RELEASE_BRANCH="true" +else + echo "Creating release from main branch" + CREATE_FROM_RELEASE_BRANCH="false" +fi + +# Always check main version first (for validation when creating from release branch) +git fetch origin main +MAIN_VERSION=$(git show origin/main:Cargo.toml | grep '^version = ' | head -n1 | cut -d'"' -f2) +echo "Main branch version: ${MAIN_VERSION}" -# Extract the base version from main (remove beta suffix if present) +# Parse main version if [[ "${MAIN_VERSION}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-beta\.([0-9]+))?$ ]]; then - CURR_MAJOR="${BASH_REMATCH[1]}" - CURR_MINOR="${BASH_REMATCH[2]}" - CURR_PATCH="${BASH_REMATCH[3]}" - BASE_VERSION="${CURR_MAJOR}.${CURR_MINOR}.${CURR_PATCH}" + MAIN_MAJOR="${BASH_REMATCH[1]}" + MAIN_MINOR="${BASH_REMATCH[2]}" + MAIN_PATCH="${BASH_REMATCH[3]}" else echo "ERROR: Cannot parse version from main branch: ${MAIN_VERSION}" exit 1 fi -echo "Current base version on main: ${BASE_VERSION}" +if [ "${CREATE_FROM_RELEASE_BRANCH}" = "true" ]; then + # + # ============= CREATE FROM RELEASE BRANCH ============= + # + # Validate main is at a major version (X.0.0-beta.N) + if [ "${MAIN_MINOR}" != "0" ] || [ "${MAIN_PATCH}" != "0" ]; then + echo "ERROR: Cannot create minor release from release branch when main is not at a major version" + echo "Main is at ${MAIN_VERSION}, expected X.0.0-beta.N format" + echo "Minor releases from release branches are only allowed when main is targeting a major release" + exit 1 + fi -# Check for existing release-root tag to find comparison base -CURR_RELEASE_ROOT_TAG="release-root/${BASE_VERSION}-beta.N" + echo "Main is at major version ${MAIN_MAJOR}.0.0 - OK to create minor release from release branch" -if git rev-parse "${CURR_RELEASE_ROOT_TAG}" >/dev/null 2>&1; then - echo "Found release root tag: ${CURR_RELEASE_ROOT_TAG}" - COMPARE_TAG="${CURR_RELEASE_ROOT_TAG}" - COMPARE_COMMIT=$(git rev-parse "${CURR_RELEASE_ROOT_TAG}") - echo "Will compare against: ${COMPARE_TAG} (commit: ${COMPARE_COMMIT})" -else - echo "No release root tag found for current version series" - COMPARE_TAG="" -fi + # Checkout the source release branch + git checkout "${SOURCE_RELEASE_BRANCH}" + SOURCE_VERSION=$(get_version_from_cargo) + echo "Source release branch version: ${SOURCE_VERSION}" -# Check for breaking changes -BREAKING_CHANGES="false" -if [ -n "${COMPARE_TAG}" ]; then - if python3 "${SELF_DIR}/check_breaking_changes.py" --detect-only "${COMPARE_TAG}" "HEAD"; then - echo "No breaking changes detected" - BREAKING_CHANGES="false" + # Parse source version + if [[ "${SOURCE_VERSION}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-beta\.([0-9]+))?$ ]]; then + SOURCE_MAJOR="${BASH_REMATCH[1]}" + SOURCE_MINOR="${BASH_REMATCH[2]}" + SOURCE_PATCH="${BASH_REMATCH[3]}" else - echo "Breaking changes detected" - BREAKING_CHANGES="true" + echo "ERROR: Cannot parse version from source branch: ${SOURCE_VERSION}" + exit 1 fi -fi -# Determine RC version based on breaking changes -if [ "${BREAKING_CHANGES}" = "true" ]; then - # Extract base RC version from release-root tag message - TAG_MESSAGE=$(git tag -l --format='%(contents)' "${CURR_RELEASE_ROOT_TAG}") - BASE_RC_VERSION=$(echo "${TAG_MESSAGE}" | head -n1 | sed 's/Base: //') - BASE_RC_MAJOR=$(echo "${BASE_RC_VERSION}" | cut -d. -f1 | sed 's/^v//') + # Validate source branch is in the same major version series (or one less than main) + if [ "${SOURCE_MAJOR}" -ge "${MAIN_MAJOR}" ]; then + echo "ERROR: Source branch major version (${SOURCE_MAJOR}) must be less than main major version (${MAIN_MAJOR})" + exit 1 + fi - echo "Base RC version: ${BASE_RC_VERSION} (major: ${BASE_RC_MAJOR})" + # Determine next minor version + RC_MAJOR="${SOURCE_MAJOR}" + RC_MINOR=$((SOURCE_MINOR + 1)) + RC_VERSION="${RC_MAJOR}.${RC_MINOR}.0-rc.1" - if [ "${CURR_MAJOR}" -gt "${BASE_RC_MAJOR}" ]; then - echo "Major version already bumped from ${BASE_RC_MAJOR} to ${CURR_MAJOR}" - RC_VERSION="${BASE_VERSION}-rc.1" + echo "Creating RC version: ${RC_VERSION}" + + # Release type is always minor when creating from release branch + RELEASE_TYPE="minor" + echo "Release type: ${RELEASE_TYPE}" + + # Create new release branch from source branch + RELEASE_BRANCH="release/v${RC_MAJOR}.${RC_MINOR}" + echo "Creating release branch ${RELEASE_BRANCH} from ${SOURCE_RELEASE_BRANCH}" + git checkout -b "${RELEASE_BRANCH}" + + # Set version to RC version + echo "Setting version to ${RC_VERSION}" + bump_and_commit_version "${RC_VERSION}" "chore: release candidate ${RC_VERSION} + +Created from ${SOURCE_RELEASE_BRANCH}" + + # Create the RC tag + RC_TAG="${TAG_PREFIX}${RC_VERSION}" + echo "Creating tag ${RC_TAG}" + git tag -a "${RC_TAG}" -m "Release candidate ${RC_VERSION} + +Created from ${SOURCE_RELEASE_BRANCH}" + + echo "Successfully created RC tag: ${RC_TAG} on branch ${RELEASE_BRANCH}" + + # Find latest stable tag on source branch for release notes comparison + # Look for tags matching vX.Y.* where X.Y matches source branch + LATEST_STABLE_TAG=$(git tag -l "${TAG_PREFIX}${SOURCE_MAJOR}.${SOURCE_MINOR}.*" | grep -v -E '(beta|rc)' | sort -V | tail -n1) + + if [ -n "${LATEST_STABLE_TAG}" ]; then + PREVIOUS_TAG="${LATEST_STABLE_TAG}" + echo "Release notes will compare against latest stable: ${PREVIOUS_TAG}" + + # Create minor-release-base tag to mark this as a minor release from a release branch + # This tag stores the source stable tag for use by determine_previous_tag + MINOR_RELEASE_BASE_TAG="minor-release-base/${RC_MAJOR}.${RC_MINOR}.0" + echo "Creating minor release base tag: ${MINOR_RELEASE_BASE_TAG}" + git tag -a "${MINOR_RELEASE_BASE_TAG}" -m "${PREVIOUS_TAG}" else - echo "Breaking changes require major version bump" - RC_MAJOR=$((CURR_MAJOR + 1)) - RC_VERSION="${RC_MAJOR}.0.0-rc.1" + echo "Warning: No stable tag found for ${SOURCE_MAJOR}.${SOURCE_MINOR}.* series" + PREVIOUS_TAG="" fi -else - # No breaking changes, use current base version - RC_VERSION="${BASE_VERSION}-rc.1" -fi -echo "Creating RC version: ${RC_VERSION}" + # Output for GitHub Actions (no main version or release root tag when creating from release branch) + echo "RC_TAG=${RC_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "RC_VERSION=${RC_VERSION}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "RELEASE_BRANCH=${RELEASE_BRANCH}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "RELEASE_TYPE=${RELEASE_TYPE}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "SOURCE_RELEASE_BRANCH=${SOURCE_RELEASE_BRANCH}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "MINOR_RELEASE_BASE_TAG=${MINOR_RELEASE_BASE_TAG:-}" >> $GITHUB_OUTPUT 2>/dev/null || true + + echo "Successfully created minor RC from release branch!" + echo " RC Tag: ${RC_TAG}" + echo " Release Branch: ${RELEASE_BRANCH}" + echo " Source Branch: ${SOURCE_RELEASE_BRANCH}" + echo " Release Notes Base: ${PREVIOUS_TAG}" + echo " Minor Release Base Tag: ${MINOR_RELEASE_BASE_TAG:-none}" -# Determine release type (major if X.0.0, otherwise minor) -RC_MINOR=$(echo "${RC_VERSION}" | cut -d. -f2 | cut -d- -f1) -if [ "${RC_MINOR}" = "0" ]; then - RELEASE_TYPE="major" else - RELEASE_TYPE="minor" -fi -echo "Release type: ${RELEASE_TYPE}" + # + # ============= CREATE FROM MAIN BRANCH ============= + # + git checkout main + BASE_VERSION="${MAIN_MAJOR}.${MAIN_MINOR}.${MAIN_PATCH}" + CURR_MAJOR="${MAIN_MAJOR}" + CURR_MINOR="${MAIN_MINOR}" + CURR_PATCH="${MAIN_PATCH}" + + echo "Current base version on main: ${BASE_VERSION}" + + # Check for existing release-root tag to find comparison base + CURR_RELEASE_ROOT_TAG="release-root/${BASE_VERSION}-beta.N" + + if git rev-parse "${CURR_RELEASE_ROOT_TAG}" >/dev/null 2>&1; then + echo "Found release root tag: ${CURR_RELEASE_ROOT_TAG}" + COMPARE_TAG="${CURR_RELEASE_ROOT_TAG}" + COMPARE_COMMIT=$(git rev-parse "${CURR_RELEASE_ROOT_TAG}") + echo "Will compare against: ${COMPARE_TAG} (commit: ${COMPARE_COMMIT})" + else + echo "No release root tag found for current version series" + COMPARE_TAG="" + fi -# Parse RC version for release branch -RC_MAJOR=$(echo "${RC_VERSION}" | cut -d. -f1) -RC_MINOR=$(echo "${RC_VERSION}" | cut -d. -f2) -RELEASE_BRANCH="release/v${RC_MAJOR}.${RC_MINOR}" + # Check for breaking changes + BREAKING_CHANGES="false" + if [ -n "${COMPARE_TAG}" ]; then + if python3 "${SELF_DIR}/check_breaking_changes.py" --detect-only "${COMPARE_TAG}" "HEAD"; then + echo "No breaking changes detected" + BREAKING_CHANGES="false" + else + echo "Breaking changes detected" + BREAKING_CHANGES="true" + fi + fi -echo "Will create release branch: ${RELEASE_BRANCH}" + # Determine RC version based on breaking changes + if [ "${BREAKING_CHANGES}" = "true" ]; then + # Extract base RC version from release-root tag message + TAG_MESSAGE=$(git tag -l --format='%(contents)' "${CURR_RELEASE_ROOT_TAG}") + BASE_RC_VERSION=$(echo "${TAG_MESSAGE}" | head -n1 | sed 's/Base: //') + BASE_RC_MAJOR=$(echo "${BASE_RC_VERSION}" | cut -d. -f1 | sed 's/^v//') + + echo "Base RC version: ${BASE_RC_VERSION} (major: ${BASE_RC_MAJOR})" + + if [ "${CURR_MAJOR}" -gt "${BASE_RC_MAJOR}" ]; then + echo "Major version already bumped from ${BASE_RC_MAJOR} to ${CURR_MAJOR}" + RC_VERSION="${BASE_VERSION}-rc.1" + else + echo "Breaking changes require major version bump" + RC_MAJOR=$((CURR_MAJOR + 1)) + RC_VERSION="${RC_MAJOR}.0.0-rc.1" + fi + else + # No breaking changes, use current base version + RC_VERSION="${BASE_VERSION}-rc.1" + fi -# Create release branch from main HEAD -echo "Creating release branch ${RELEASE_BRANCH} from main HEAD" -git checkout -b "${RELEASE_BRANCH}" + echo "Creating RC version: ${RC_VERSION}" -# Set version to RC version -echo "Setting version to ${RC_VERSION}" -bump-my-version bump -vv --new-version "${RC_VERSION}" --no-tag patch + # Determine release type (major if X.0.0, otherwise minor) + RC_MINOR=$(echo "${RC_VERSION}" | cut -d. -f2 | cut -d- -f1) + if [ "${RC_MINOR}" = "0" ]; then + RELEASE_TYPE="major" + else + RELEASE_TYPE="minor" + fi + echo "Release type: ${RELEASE_TYPE}" + + # Parse RC version for release branch + RC_MAJOR=$(echo "${RC_VERSION}" | cut -d. -f1) + RC_MINOR=$(echo "${RC_VERSION}" | cut -d. -f2) + RELEASE_BRANCH="release/v${RC_MAJOR}.${RC_MINOR}" + + echo "Will create release branch: ${RELEASE_BRANCH}" + + # Create release branch from main HEAD + echo "Creating release branch ${RELEASE_BRANCH} from main HEAD" + git checkout -b "${RELEASE_BRANCH}" + + # Set version to RC version + echo "Setting version to ${RC_VERSION}" + bump-my-version bump -vv --new-version "${RC_VERSION}" --no-tag patch -# Update Cargo.lock files after version bump -cargo update -(cd python && cargo update) -(cd java/lance-jni && cargo update) + # Update Cargo.lock files after version bump + cargo update + (cd python && cargo update) + (cd java/lance-jni && cargo update) -# Commit the RC version -git add -A -git commit -m "chore: release candidate ${RC_VERSION}" + # Commit the RC version + git add -A + git commit -m "chore: release candidate ${RC_VERSION}" -# Create the RC tag -RC_TAG="${TAG_PREFIX}${RC_VERSION}" -echo "Creating tag ${RC_TAG}" -git tag -a "${RC_TAG}" -m "Release candidate ${RC_VERSION}" + # Create the RC tag + RC_TAG="${TAG_PREFIX}${RC_VERSION}" + echo "Creating tag ${RC_TAG}" + git tag -a "${RC_TAG}" -m "Release candidate ${RC_VERSION}" -echo "Successfully created RC tag: ${RC_TAG} on branch ${RELEASE_BRANCH}" + echo "Successfully created RC tag: ${RC_TAG} on branch ${RELEASE_BRANCH}" -# Now bump main to next unreleased version (beta.0) -echo "Bumping main to next version beta.0" -git checkout main + # Now bump main to next unreleased version (beta.0) + echo "Bumping main to next version beta.0" + git checkout main -# Determine next version for main based on RC version -# Always bump minor from the RC version -NEXT_MAJOR="${RC_MAJOR}" -NEXT_MINOR=$((RC_MINOR + 1)) -NEXT_VERSION="${NEXT_MAJOR}.${NEXT_MINOR}.0-beta.0" + # Determine next version for main based on RC version + # Always bump minor from the RC version + NEXT_MAJOR="${RC_MAJOR}" + NEXT_MINOR=$((RC_MINOR + 1)) + NEXT_VERSION="${NEXT_MAJOR}.${NEXT_MINOR}.0-beta.0" -echo "Bumping main to ${NEXT_VERSION} (unreleased)" + echo "Bumping main to ${NEXT_VERSION} (unreleased)" -bump-my-version bump -vv --new-version "${NEXT_VERSION}" --no-tag patch + bump-my-version bump -vv --new-version "${NEXT_VERSION}" --no-tag patch -# Update Cargo.lock files after version bump -cargo update -(cd python && cargo update) -(cd java/lance-jni && cargo update) + # Update Cargo.lock files after version bump + cargo update + (cd python && cargo update) + (cd java/lance-jni && cargo update) -git add -A -git commit -m "chore: bump main to ${NEXT_VERSION} + git add -A + git commit -m "chore: bump main to ${NEXT_VERSION} Unreleased version after creating ${RC_TAG}" -echo "Main branch bumped to ${NEXT_VERSION}" + echo "Main branch bumped to ${NEXT_VERSION}" -# Create release-root tag for the new beta series on main (points to commit before RC branch) -# Strip the prerelease suffix from NEXT_VERSION for the tag name -NEXT_BASE_VERSION="${NEXT_MAJOR}.${NEXT_MINOR}.0" -RELEASE_ROOT_TAG="release-root/${NEXT_BASE_VERSION}-beta.N" -echo "Creating release root tag ${RELEASE_ROOT_TAG} pointing to RC ${RC_VERSION}" -git tag -a "${RELEASE_ROOT_TAG}" "${RC_TAG}^" -m "Base: ${RC_VERSION} + # Create release-root tag for the new beta series on main (points to commit before RC branch) + # Strip the prerelease suffix from NEXT_VERSION for the tag name + NEXT_BASE_VERSION="${NEXT_MAJOR}.${NEXT_MINOR}.0" + RELEASE_ROOT_TAG="release-root/${NEXT_BASE_VERSION}-beta.N" + echo "Creating release root tag ${RELEASE_ROOT_TAG} pointing to RC ${RC_VERSION}" + git tag -a "${RELEASE_ROOT_TAG}" "${RC_TAG}^" -m "Base: ${RC_VERSION} Release root for ${NEXT_BASE_VERSION}-beta.N series" -# Determine comparison base for RC release notes -# For major/minor RC, we want to compare against the OLD release-root tag (the one for the main version before bump) -# which points to the previous RC base -OLD_RELEASE_ROOT_TAG="release-root/${BASE_VERSION}-beta.N" + # Determine comparison base for RC release notes + # For major/minor RC, we want to compare against the OLD release-root tag (the one for the main version before bump) + # which points to the previous RC base + OLD_RELEASE_ROOT_TAG="release-root/${BASE_VERSION}-beta.N" -if git rev-parse "${OLD_RELEASE_ROOT_TAG}" >/dev/null 2>&1; then - PREVIOUS_TAG="${OLD_RELEASE_ROOT_TAG}" - echo "Release notes will compare against previous release-root: ${PREVIOUS_TAG}" -else - echo "Warning: Release root tag ${OLD_RELEASE_ROOT_TAG} not found" - PREVIOUS_TAG="" -fi + if git rev-parse "${OLD_RELEASE_ROOT_TAG}" >/dev/null 2>&1; then + PREVIOUS_TAG="${OLD_RELEASE_ROOT_TAG}" + echo "Release notes will compare against previous release-root: ${PREVIOUS_TAG}" + else + echo "Warning: Release root tag ${OLD_RELEASE_ROOT_TAG} not found" + PREVIOUS_TAG="" + fi -# Output for GitHub Actions -echo "RC_TAG=${RC_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true -echo "RC_VERSION=${RC_VERSION}" >> $GITHUB_OUTPUT 2>/dev/null || true -echo "RELEASE_BRANCH=${RELEASE_BRANCH}" >> $GITHUB_OUTPUT 2>/dev/null || true -echo "MAIN_VERSION=${NEXT_VERSION}" >> $GITHUB_OUTPUT 2>/dev/null || true -echo "RELEASE_ROOT_TAG=${RELEASE_ROOT_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true -echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true -echo "RELEASE_TYPE=${RELEASE_TYPE}" >> $GITHUB_OUTPUT 2>/dev/null || true - -echo "Successfully created major/minor RC!" -echo " RC Tag: ${RC_TAG}" -echo " Release Branch: ${RELEASE_BRANCH}" -echo " Main Version: ${NEXT_VERSION}" -echo " Release Root Tag: ${RELEASE_ROOT_TAG}" + # Output for GitHub Actions + echo "RC_TAG=${RC_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "RC_VERSION=${RC_VERSION}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "RELEASE_BRANCH=${RELEASE_BRANCH}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "MAIN_VERSION=${NEXT_VERSION}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "RELEASE_ROOT_TAG=${RELEASE_ROOT_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "RELEASE_TYPE=${RELEASE_TYPE}" >> $GITHUB_OUTPUT 2>/dev/null || true + + echo "Successfully created major/minor RC!" + echo " RC Tag: ${RC_TAG}" + echo " Release Branch: ${RELEASE_BRANCH}" + echo " Main Version: ${NEXT_VERSION}" + echo " Release Root Tag: ${RELEASE_ROOT_TAG}" +fi diff --git a/ci/release_common.sh b/ci/release_common.sh index bea245919fa..4b316adf200 100644 --- a/ci/release_common.sh +++ b/ci/release_common.sh @@ -40,6 +40,12 @@ bump_and_commit_version() { # Determines the previous tag for release notes comparison # Args: MAJOR MINOR PATCH [TAG_PREFIX] # Returns: previous tag name or empty string +# +# For major/minor releases (PATCH=0): +# - Checks for minor-release-base tag (minor release from release branch) +# - Otherwise uses release-root tag (standard flow from main) +# For patch releases (PATCH>0): +# - Compares against previous patch stable tag determine_previous_tag() { local MAJOR=$1 local MINOR=$2 @@ -47,7 +53,19 @@ determine_previous_tag() { local TAG_PREFIX=${4:-"v"} if [ "${PATCH}" = "0" ]; then - # Major/Minor release: compare against release-root tag + # Major/Minor release: check for minor-release-base tag first + # This tag is created when a minor release is cut from a release branch + local MINOR_RELEASE_BASE_TAG="minor-release-base/${MAJOR}.${MINOR}.0" + if git rev-parse "${MINOR_RELEASE_BASE_TAG}" >/dev/null 2>&1; then + # Read the source tag from the tag message + local SOURCE_TAG=$(git tag -l --format='%(contents:subject)' "${MINOR_RELEASE_BASE_TAG}") + if [ -n "${SOURCE_TAG}" ]; then + echo "${SOURCE_TAG}" + return + fi + fi + + # Standard flow: use release-root tag local RELEASE_ROOT_TAG="release-root/${MAJOR}.${MINOR}.${PATCH}-beta.N" if git rev-parse "${RELEASE_ROOT_TAG}" >/dev/null 2>&1; then echo "${RELEASE_ROOT_TAG}" diff --git a/release_process.md b/release_process.md index 0a97e3afaf5..0f25ce0e0d5 100644 --- a/release_process.md +++ b/release_process.md @@ -1,11 +1,14 @@ # Release Process -Lance uses [semantic versioning](https://semver.org/) and maintains a linear commit history. -* All pull requests are merged into the `main` branch. +Lance maintains a linear commit history with a controlled release process. + +* All pull requests are merged into the `main` branch first. * Beta releases (or preview releases) are created on-demand from the `main` branch. * Stable releases (non-prereleases) are created only after a voting process and come from a release branch `release/vX.Y`. These are typically created once every two weeks. * Release Candidates (RC) are created from release branches prior to voting. +* Minor releases can be cut from either main branch or an existing release branch (when main is targeting a major release). * Patch releases are created by committing fixes directly to the release branch, voting on a new RC, and releasing. +* All changes (features, fixes) must be committed to main first, then cherry-picked to release branches as needed. ```mermaid gitGraph @@ -31,15 +34,24 @@ gitGraph commit tag: "1.4.1-rc.1" commit tag: "1.4.1" checkout main - commit tag: "1.5.0-beta.1" + 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" ``` +**Diagram explanation**: When main has breaking changes and moves to `2.0.0-beta.1`, a new minor release `v1.5.0` can be cut from `release/v1.4` (after cherry-picking features from main) instead of from main. + ## Version Semantics +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. + ### Version Format -Lance uses semantic versioning with prerelease identifiers: +Lance uses version numbers with prerelease identifiers: - **Stable**: `X.Y.Z` (e.g., `1.3.0`) - **Beta**: `X.Y.Z-beta.N` (e.g., `1.3.0-beta.1`, `1.3.0-beta.2`) - **RC**: `X.Y.Z-rc.N` (e.g., `1.3.0-rc.1`, `1.3.0-rc.2`) @@ -63,32 +75,51 @@ Lance uses semantic versioning with prerelease identifiers: ### GitHub Releases and Release Notes -| Release Type | GitHub Release Type | Start Commit (exclusive) | End Commit (inclusive) | Explanation | -|---------------------------|---------------------|-----------------------------|------------------------|----------------------------------------------------------------------| -| **Stable (Major/Minor)** | Release | `release-root/X.Y.0-beta.N` | `vX.Y.0` | All changes from main + RC fixes | -| **Stable (Patch)** | Release | `vX.Y.(Z-1)` | `vX.Y.Z` | Only changes in this patch release | -| **RC (Major/Minor)** | Pre-Release | `release-root/X.Y.0-beta.N` | `vX.Y.0-rc.N` | All changes for the release | -| **RC (Patch)** | Pre-Release | `vX.Y.(Z-1)` | `vX.Y.Z-rc.N` | Only changes in this patch release | -| **RC (Iterations)** | Pre-Release | `vX.Y.(Z-1)` | `vX.Y.Z-rc.N` | Only changes in this patch release (not changes 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 | +| 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 | ## Branching Strategy ### Main Branch + - Always contains the latest development work - Version format: `X.Y.Z-beta.N` - After RC creation, bumped to next minor version with `-beta.0` (unreleased) - Beta previews published by bumping to `-beta.1+` ### Release Branches + - Format: `release/v{major}.{minor}` (e.g., `release/v1.3`) - Created when cutting initial RC for major/minor release +- Can be created from: + - **Main branch**: Standard flow for major/minor releases + - **Existing release branch**: For minor releases when main is targeting a major release - Maintained for patch releases - Version progression: `rc.1` → `rc.2` → stable → `beta.0` → `rc.1` (for patches) +### Commit Flow + +All changes must be committed to the main branch first: + +1. **Features and fixes**: Merge PR to main +2. **Release branch needs**: Cherry-pick from main to release branch +3. **Never commit directly to release branch** without the change existing in main first + +This ensures main always has the complete history and release branches only contain subsets of main's changes. + ## Version Flow +### Standard Flow (Major/Minor from Main) + ```mermaid %%{init: {'theme':'base', 'themeVariables': { 'fontSize':'14px'}}}%% flowchart LR @@ -112,6 +143,7 @@ flowchart LR ``` **Flow explanation:** + - **Main branch**: Commit M0 at `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 branch** `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.0` - **Tags**: 🏷️ = version tag (points to tagged commit), 📍 = release-root tag (points to commit before RC was created, used for breaking change detection) @@ -119,12 +151,47 @@ flowchart LR **Note**: All commits are linear on their respective branches. `beta.0` = unreleased, `beta.1+` = published previews. +### Minor Release from Release Branch (When Main is Major) + +```mermaid +%%{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:** + +- **Main branch** is at `2.0.0-beta.N` (major version) +- **release/v1.3** has released `1.3.1` and needs a new minor release with features +- **release/v1.4** is created from `release/v1.3` (not from main) because main is at a different major version +- Release notes for `v1.4.0` compare against `v1.3.1` (latest stable on source branch) +- Main branch is NOT modified (already at 2.x) + ## Workflows ### User-Facing Workflows 1. **publish-beta.yml** - Publish beta preview releases from any branch 2. **create-release-branch.yml** - Create release branch with initial RC for new major/minor version + - From main: Standard flow for major/minor releases + - From release branch: For minor releases when main is targeting a major release 3. **create-rc.yml** - Create RC on existing release branch (for new patch release RC or iterations of an existing RC) 4. **approve-rc.yml** - Approve any RC to stable (works for all release types) @@ -140,6 +207,8 @@ flowchart LR **Result**: 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. +
How beta versioning works @@ -187,11 +256,27 @@ Release root tags mark the base commits for breaking change detection. The tag n - Example: When bumping 1.4.0-beta.5 → 2.0.0-beta.1, create `release-root/2.0.0-beta.N` pointing to the SAME commit with the SAME base RC version **Key properties**: + - **Multiple tags, same commit**: `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) - **Major version bumped once**: Both tags store same base RC version (1.3.0-rc.1), so we know 2.x is already a major bump from 1.3.0 - **No additional bumps**: When at 2.0.0-beta.1, we detect breaking changes but see major already bumped (2 > 1), so just increment beta - **Beta reset on major bump**: When bumping major version, beta number resets to 1 (e.g., 1.4.0-beta.5 → 2.0.0-beta.1) +### Minor Release Base Tag + +When a minor release is created from an existing release branch (not from main), a `minor-release-base` tag is created to track the comparison base for release notes. + +**Tag Format**: `minor-release-base/{major}.{minor}.0` + +- Created when using `create-release-branch` workflow with `source_release_branch` parameter +- Tag message contains the source stable tag (e.g., `v1.3.1`) +- Used by `determine_previous_tag` to find the correct comparison base + +**Example**: When creating `release/v1.4` from `release/v1.3` (where latest stable is v1.3.1): + +- Creates `minor-release-base/1.4.0` with message `v1.3.1` +- Release notes for v1.4.0-rc.N and v1.4.0 will compare against v1.3.1 + ### Detection Process Breaking change detection happens **on every beta publish from main branch**: @@ -219,11 +304,12 @@ Starting from v1.3.0-rc.1 cut, main at 1.4.0-beta.0 with `release-root/1.4.0-bet **Key insight**: Multiple beta version series can share the same release-root commit, with major version bumped only once when first detected.
-## Create a Major / Minor Release +## Create a Major / Minor Release (from Main) **Purpose**: Create a new major or minor release from the main branch. **Steps**: + 1. Ensure CI on main is green 2. Trigger **"Create Release Branch"** workflow with **dry_run**: `true` 3. Review the RC version and changes @@ -234,6 +320,7 @@ Starting from v1.3.0-rc.1 cut, main at 1.4.0-beta.0 with `release-root/1.4.0-bet 8. **If approved**: Trigger **"Approve RC"** workflow with **rc_tag** (e.g., `v1.3.0-rc.2`) **Result**: + - Creates release branch (e.g., `release/v1.3`) with RC tag (e.g., `v1.3.0-rc.1`) - Bumps main to next minor (e.g., `1.4.0-beta.0`) - After approval: Creates stable tag (e.g., `v1.3.0`) and publishes to PyPI, crates.io, Maven Central @@ -242,6 +329,7 @@ Starting from v1.3.0-rc.1 cut, main at 1.4.0-beta.0 with `release-root/1.4.0-bet What happens under the hood **Create Release Branch workflow**: + - Reads current version from main (e.g., `1.3.0-beta.2`) - Checks for breaking changes since release-root tag - If breaking changes: Creates RC with bumped major (e.g., `2.0.0-rc.1`), bumps main to `2.1.0-beta.0` @@ -250,6 +338,7 @@ Starting from v1.3.0-rc.1 cut, main at 1.4.0-beta.0 with `release-root/1.4.0-bet - Creates GitHub Discussion for voting **Approve RC workflow**: + - Bumps version from `rc.N` to stable - Generates release notes comparing against `release-root/{version}-beta.N` tag - Creates GitHub Release and publishes stable artifacts @@ -257,22 +346,77 @@ Starting from v1.3.0-rc.1 cut, main at 1.4.0-beta.0 with `release-root/1.4.0-bet - Main branch is NOT affected (already bumped in step 1) +## Create a Minor Release (from Release Branch) + +**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**: + +- Main branch must be at a major version (e.g., `2.0.0-beta.N` where patch = 0 and minor = 0) +- Source release branch must exist (e.g., `release/v1.3`) +- Features to release must already be committed to main, then cherry-picked to the source release branch + +**Steps**: + +1. Cherry-pick desired features from main to the source release branch +2. Trigger **"Create Release Branch"** workflow with: + - **source_release_branch**: e.g., `release/v1.3` + - **dry_run**: `true` +3. Review the minor RC version (e.g., `1.4.0-rc.1`) +4. Run with **dry_run**: `false` to create new release branch and RC +5. Test RC artifacts and vote in the GitHub Discussion +6. **If issues found**: Fix and run **"Create RC"** workflow +7. **If approved**: Trigger **"Approve RC"** workflow + +**Result**: + +- Creates new release branch (e.g., `release/v1.4`) from source branch +- Creates RC tag (e.g., `v1.4.0-rc.1`) +- Main branch is NOT modified (already at major version) +- Release notes compare against latest stable on source branch (e.g., `v1.3.1`) +- After approval: Creates stable tag and publishes artifacts + +
+What happens under the hood + +**Create Release Branch workflow (with source_release_branch)**: + +- Validates main is at major version (`X.0.0-beta.N` where patch = 0) +- Checks out source release branch (e.g., `release/v1.3`) +- Reads current version from source branch +- Increments minor version (e.g., `1.3.1-beta.0` → `1.4.0-rc.1`) +- Creates new release branch (e.g., `release/v1.4`) from source branch HEAD +- Finds latest stable tag on source branch for release notes comparison +- Does NOT modify main branch +- Creates GitHub Discussion for voting + +**Key differences from main branch flow**: + +- No breaking change detection (assumes features are already validated) +- No release-root tag created (not needed for this flow) +- Main branch version unchanged +- Release notes compare against source branch's latest stable release +
+ ## Create a Patch / Bugfix Release **Purpose**: Release critical bug fixes for an existing release. **Steps**: -1. Checkout the release branch (e.g., `release/v1.3`) -2. Create and test your fix (ensure no breaking changes) -3. Create a PR to merge into the release branch -4. Trigger **"Create RC"** workflow with **release_branch** (e.g., `release/v1.3`) and **dry_run**: `true` -5. Review the patch RC version -6. Run with **dry_run**: `false` to create the patch RC -7. Test RC artifacts and vote in the GitHub Discussion -8. **If issues found**: Fix and run **"Create RC"** again to create `rc.2`, `rc.3`, etc. -9. **If approved**: Trigger **"Approve RC"** workflow with **rc_tag** (e.g., `v1.3.1-rc.1`) + +1. Commit the fix to main branch first (all changes must go to main first) +2. Cherry-pick the fix to the release branch (e.g., `release/v1.3`) +3. Trigger **"Create RC"** workflow with **release_branch** (e.g., `release/v1.3`) and **dry_run**: `true` +4. Review the patch RC version +5. Run with **dry_run**: `false` to create the patch RC +6. Test RC artifacts and vote in the GitHub Discussion +7. **If issues found**: Fix and run **"Create RC"** again to create `rc.2`, `rc.3`, etc. +8. **If approved**: Trigger **"Approve RC"** workflow with **rc_tag** (e.g., `v1.3.1-rc.1`) **Result**: + - Creates patch RC tag (e.g., `v1.3.1-rc.1`) on release branch - After approval: Creates stable tag (e.g., `v1.3.1`) and publishes to PyPI, crates.io, Maven Central - Auto-bumps release branch to next patch `beta.0` (e.g., `1.3.2-beta.0`) @@ -281,7 +425,8 @@ Starting from v1.3.0-rc.1 cut, main at 1.4.0-beta.0 with `release-root/1.4.0-bet
Important notes -- **Breaking changes not allowed**: Release branches are for patch releases only +- **Commit to main first**: All fixes must be committed to main before cherry-picking to release branches +- **Breaking changes not allowed**: Patch releases should not introduce breaking changes - **Beta versions**: Release branches stay at `X.Y.Z-beta.N` between releases (auto-bumped after stable) - **Release notes**: Compares against previous stable tag (e.g., `v1.3.0`) - **Allowed changes**: Correctness bugs, security fixes, major performance regressions, unintentional breaking change reverts @@ -419,7 +564,7 @@ Workflow: Create Release Branch ```bash # 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 release/v1.3 +# 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 @@ -444,6 +589,48 @@ Workflow: Approve RC - Main unchanged ``` +### Minor Release from Release Branch + +```bash +# 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 +``` + ### RC Iteration Due to Issues ```bash From 7bd5c9c59e910ae0455da7cd487520e09af72b4d Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Thu, 12 Feb 2026 22:43:17 -0800 Subject: [PATCH 3/3] correct name --- .github/workflows/create-release-branch.yml | 12 ++++++------ ci/create_release_branch.sh | 12 ++++++------ ci/release_common.sh | 10 +++++----- release_process.md | 10 ++++------ 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml index dcaca062bf1..a338977270f 100644 --- a/.github/workflows/create-release-branch.yml +++ b/.github/workflows/create-release-branch.yml @@ -52,15 +52,15 @@ jobs: git push origin "${{ steps.create_branch.outputs.RELEASE_BRANCH }}" git push origin "${{ steps.create_branch.outputs.RC_TAG }}" # When creating from main: push main and release root tag - # When creating from release branch: push minor release base tag + # When creating from release branch: push minor release root tag if [ -z "${{ inputs.source_release_branch }}" ]; then git push origin main # Push release root tag (may already exist remotely if created during beta publish) git push origin "${{ steps.create_branch.outputs.RELEASE_ROOT_TAG }}" || echo "Release root tag already exists remotely" else - # Push minor release base tag if it was created - if [ -n "${{ steps.create_branch.outputs.MINOR_RELEASE_BASE_TAG }}" ]; then - git push origin "${{ steps.create_branch.outputs.MINOR_RELEASE_BASE_TAG }}" + # Push minor release root tag if it was created + if [ -n "${{ steps.create_branch.outputs.MINOR_RELEASE_ROOT_TAG }}" ]; then + git push origin "${{ steps.create_branch.outputs.MINOR_RELEASE_ROOT_TAG }}" fi fi @@ -128,8 +128,8 @@ jobs: else echo "- **Source Branch:** ${{ inputs.source_release_branch }}" >> $GITHUB_STEP_SUMMARY echo "- **Release Notes Base:** ${{ steps.create_branch.outputs.PREVIOUS_TAG }}" >> $GITHUB_STEP_SUMMARY - if [ -n "${{ steps.create_branch.outputs.MINOR_RELEASE_BASE_TAG }}" ]; then - echo "- **Minor Release Base Tag:** ${{ steps.create_branch.outputs.MINOR_RELEASE_BASE_TAG }}" >> $GITHUB_STEP_SUMMARY + if [ -n "${{ steps.create_branch.outputs.MINOR_RELEASE_ROOT_TAG }}" ]; then + echo "- **Minor Release Root Tag:** ${{ steps.create_branch.outputs.MINOR_RELEASE_ROOT_TAG }}" >> $GITHUB_STEP_SUMMARY fi fi echo "- **Dry Run:** ${{ inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY diff --git a/ci/create_release_branch.sh b/ci/create_release_branch.sh index 0f213127a93..9c7d9d3e58a 100755 --- a/ci/create_release_branch.sh +++ b/ci/create_release_branch.sh @@ -117,11 +117,11 @@ Created from ${SOURCE_RELEASE_BRANCH}" PREVIOUS_TAG="${LATEST_STABLE_TAG}" echo "Release notes will compare against latest stable: ${PREVIOUS_TAG}" - # Create minor-release-base tag to mark this as a minor release from a release branch + # Create minor-release-root tag to mark this as a minor release from a release branch # This tag stores the source stable tag for use by determine_previous_tag - MINOR_RELEASE_BASE_TAG="minor-release-base/${RC_MAJOR}.${RC_MINOR}.0" - echo "Creating minor release base tag: ${MINOR_RELEASE_BASE_TAG}" - git tag -a "${MINOR_RELEASE_BASE_TAG}" -m "${PREVIOUS_TAG}" + MINOR_RELEASE_ROOT_TAG="minor-release-root/${RC_MAJOR}.${RC_MINOR}.0" + echo "Creating minor release root tag: ${MINOR_RELEASE_ROOT_TAG}" + git tag -a "${MINOR_RELEASE_ROOT_TAG}" -m "${PREVIOUS_TAG}" else echo "Warning: No stable tag found for ${SOURCE_MAJOR}.${SOURCE_MINOR}.* series" PREVIOUS_TAG="" @@ -134,14 +134,14 @@ Created from ${SOURCE_RELEASE_BRANCH}" echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_OUTPUT 2>/dev/null || true echo "RELEASE_TYPE=${RELEASE_TYPE}" >> $GITHUB_OUTPUT 2>/dev/null || true echo "SOURCE_RELEASE_BRANCH=${SOURCE_RELEASE_BRANCH}" >> $GITHUB_OUTPUT 2>/dev/null || true - echo "MINOR_RELEASE_BASE_TAG=${MINOR_RELEASE_BASE_TAG:-}" >> $GITHUB_OUTPUT 2>/dev/null || true + echo "MINOR_RELEASE_ROOT_TAG=${MINOR_RELEASE_ROOT_TAG:-}" >> $GITHUB_OUTPUT 2>/dev/null || true echo "Successfully created minor RC from release branch!" echo " RC Tag: ${RC_TAG}" echo " Release Branch: ${RELEASE_BRANCH}" echo " Source Branch: ${SOURCE_RELEASE_BRANCH}" echo " Release Notes Base: ${PREVIOUS_TAG}" - echo " Minor Release Base Tag: ${MINOR_RELEASE_BASE_TAG:-none}" + echo " Minor Release Root Tag: ${MINOR_RELEASE_ROOT_TAG:-none}" else # diff --git a/ci/release_common.sh b/ci/release_common.sh index 4b316adf200..cd653212aae 100644 --- a/ci/release_common.sh +++ b/ci/release_common.sh @@ -42,7 +42,7 @@ bump_and_commit_version() { # Returns: previous tag name or empty string # # For major/minor releases (PATCH=0): -# - Checks for minor-release-base tag (minor release from release branch) +# - Checks for minor-release-root tag (minor release from release branch) # - Otherwise uses release-root tag (standard flow from main) # For patch releases (PATCH>0): # - Compares against previous patch stable tag @@ -53,12 +53,12 @@ determine_previous_tag() { local TAG_PREFIX=${4:-"v"} if [ "${PATCH}" = "0" ]; then - # Major/Minor release: check for minor-release-base tag first + # Major/Minor release: check for minor-release-root tag first # This tag is created when a minor release is cut from a release branch - local MINOR_RELEASE_BASE_TAG="minor-release-base/${MAJOR}.${MINOR}.0" - if git rev-parse "${MINOR_RELEASE_BASE_TAG}" >/dev/null 2>&1; then + local MINOR_RELEASE_ROOT_TAG="minor-release-root/${MAJOR}.${MINOR}.0" + if git rev-parse "${MINOR_RELEASE_ROOT_TAG}" >/dev/null 2>&1; then # Read the source tag from the tag message - local SOURCE_TAG=$(git tag -l --format='%(contents:subject)' "${MINOR_RELEASE_BASE_TAG}") + local SOURCE_TAG=$(git tag -l --format='%(contents:subject)' "${MINOR_RELEASE_ROOT_TAG}") if [ -n "${SOURCE_TAG}" ]; then echo "${SOURCE_TAG}" return diff --git a/release_process.md b/release_process.md index 0f25ce0e0d5..c0403407c41 100644 --- a/release_process.md +++ b/release_process.md @@ -43,8 +43,6 @@ gitGraph ``` -**Diagram explanation**: When main has breaking changes and moves to `2.0.0-beta.1`, a new minor release `v1.5.0` can be cut from `release/v1.4` (after cherry-picking features from main) instead of from main. - ## Version Semantics 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. @@ -262,11 +260,11 @@ Release root tags mark the base commits for breaking change detection. The tag n - **No additional bumps**: When at 2.0.0-beta.1, we detect breaking changes but see major already bumped (2 > 1), so just increment beta - **Beta reset on major bump**: When bumping major version, beta number resets to 1 (e.g., 1.4.0-beta.5 → 2.0.0-beta.1) -### Minor Release Base Tag +### Minor Release Root Tag -When a minor release is created from an existing release branch (not from main), a `minor-release-base` tag is created to track the comparison base for release notes. +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-base/{major}.{minor}.0` +**Tag Format**: `minor-release-root/{major}.{minor}.0` - Created when using `create-release-branch` workflow with `source_release_branch` parameter - Tag message contains the source stable tag (e.g., `v1.3.1`) @@ -274,7 +272,7 @@ When a minor release is created from an existing release branch (not from main), **Example**: When creating `release/v1.4` from `release/v1.3` (where latest stable is v1.3.1): -- Creates `minor-release-base/1.4.0` with message `v1.3.1` +- Creates `minor-release-root/1.4.0` with message `v1.3.1` - Release notes for v1.4.0-rc.N and v1.4.0 will compare against v1.3.1 ### Detection Process