diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88d3d43..56df3ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,12 +5,16 @@ on: branches: [main] # contents:write is required for autotag-from-changelog to push tags. +# pull-requests:write is required for retargeting PRs to pending deploy branches. permissions: contents: write + pull-requests: write jobs: release: runs-on: ubuntu-latest + outputs: + tag-created: ${{ steps.autotag.outputs.tag-created }} steps: - uses: actions/checkout@v6 with: @@ -28,10 +32,176 @@ jobs: env: GH_TOKEN: ${{ secrets.CCLOUD_PRIVATE_DISPATCH_PAT }} run: | + source scripts/lib/logging.sh + if [ -z "$GH_TOKEN" ]; then - echo "::warning::CCLOUD_PRIVATE_DISPATCH_PAT secret is not set. Skipping dispatch to ccloud-private for ${{ steps.autotag.outputs.tag }}." + log_warning "CCLOUD_PRIVATE_DISPATCH_PAT secret is not set. Skipping dispatch to ccloud-private for ${{ steps.autotag.outputs.tag }}." exit 0 fi gh api repos/cockroachdb/ccloud-private/dispatches \ -f event_type=sdk-release \ -f "client_payload[version]=${{ steps.autotag.outputs.tag }}" + + # After a successful release, this job ensures there's always a fresh pending deploy + # branch ready for the next release cycle. It: + # 1. Finds the most recently merged pending deploy branch (from PRs to main) + # 2. Finds any existing live pending deploy branches + # 3. Creates a new pending deploy branch if there are no live ones or if the live + # ones are stale (associated with an older date than the last merged one) + # 4. Retargets all open PRs to point to the current pending deploy branch + manage-pending-deploy: + needs: release + if: needs.release.outputs.tag-created == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + # Step 1: Determine the previous pending deploy branch timestamp from main + # Find the most recent merged PR to main where the head branch was automation/pending-deploy-YYYYMMDD-hhmmss + - name: Find previous pending deploy branch from merged PRs + id: previous + env: + GH_TOKEN: ${{ github.token }} + run: | + source scripts/lib/logging.sh + + log_info "Searching for previous pending deploy branch from merged PRs..." + + # Fetch last 50 merged PRs to find the most recent pending deploy branch. + # 50 should be sufficient given typical API change and SDK release cadence. + previous_branch=$(gh pr list \ + --state merged \ + --base main \ + --limit 50 \ + --json headRefName,mergedAt \ + --jq '[.[] | select(.headRefName | test("^automation/pending-deploy-[0-9]{8}-[0-9]{6}$"))] | sort_by(.mergedAt) | reverse | .[0].headRefName') + + if [ -z "$previous_branch" ] || [ "$previous_branch" = "null" ]; then + log_info "No previous pending deploy branch found from merged PRs" + echo "previous_branch_timestamp=" >> "$GITHUB_OUTPUT" + else + log_info "Found previous pending deploy branch: $previous_branch" + + # Extract timestamp from the branch name: automation/pending-deploy-YYYYMMDD-hhmmss + previous_branch_timestamp=$(echo "$previous_branch" | sed -E 's/^automation\/pending-deploy-([0-9]{8}-[0-9]{6})$/\1/' | tr -d '-') + + echo "previous_branch_timestamp=$previous_branch_timestamp" >> "$GITHUB_OUTPUT" + + log_info " Timestamp: $previous_branch_timestamp" + fi + + # Step 2: Determine the current live pending deploy branch in the main repository + # Look for live remote branches matching automation/pending-deploy-YYYYMMDD-hhmmss + # If multiple exist, select the one with the latest timestamp + - name: Find current live pending deploy branch + id: current + run: | + source scripts/lib/logging.sh + + log_info "Searching for live pending deploy branches in the main repository..." + + # List all remote branches matching the pattern, sorted by timestamp descending + current_branch=$(git ls-remote --heads origin | \ + grep -oE 'automation/pending-deploy-[0-9]{8}-[0-9]{6}$' | \ + sort -r | \ + head -n 1) + + if [ -z "$current_branch" ]; then + log_info "No live pending deploy branch found" + echo "current_branch=" >> "$GITHUB_OUTPUT" + echo "current_branch_timestamp=" >> "$GITHUB_OUTPUT" + else + log_info "Found current live pending deploy branch: $current_branch" + + # Extract timestamp + current_branch_timestamp=$(echo "$current_branch" | sed -E 's/^automation\/pending-deploy-([0-9]{8}-[0-9]{6})$/\1/' | tr -d '-') + + echo "current_branch=$current_branch" >> "$GITHUB_OUTPUT" + echo "current_branch_timestamp=$current_branch_timestamp" >> "$GITHUB_OUTPUT" + + log_info " Timestamp: $current_branch_timestamp" + fi + + # Step 3: Decide which pending deploy branch should be used now + # If previous timestamp >= current timestamp, create a new branch + - name: Select pending deploy branch + id: selected + env: + PREVIOUS_BRANCH_TIMESTAMP: ${{ steps.previous.outputs.previous_branch_timestamp }} + CURRENT_BRANCH: ${{ steps.current.outputs.current_branch }} + CURRENT_BRANCH_TIMESTAMP: ${{ steps.current.outputs.current_branch_timestamp }} + run: | + source scripts/lib/logging.sh + + # Get current UTC timestamp in YYYYMMDD-hhmmss format + current_timestamp=$(date -u +%Y%m%d-%H%M%S) + log_info "Current UTC timestamp: $current_timestamp" + + # Decision logic: create new branch if previous >= current, otherwise use current + if [ -z "$CURRENT_BRANCH" ]; then + # No current live branch exists, create new + log_info "No current live branch exists, creating new branch..." + selected_branch="automation/pending-deploy-${current_timestamp}" + needs_creation=true + elif [ -n "$PREVIOUS_BRANCH_TIMESTAMP" ] && [ "$PREVIOUS_BRANCH_TIMESTAMP" -ge "$CURRENT_BRANCH_TIMESTAMP" ]; then + # Previous timestamp >= current timestamp, create new branch + log_info "Previous timestamp ($PREVIOUS_BRANCH_TIMESTAMP) >= current timestamp ($CURRENT_BRANCH_TIMESTAMP), creating new branch..." + selected_branch="automation/pending-deploy-${current_timestamp}" + needs_creation=true + else + # Use current live branch + log_info "Using current live branch" + selected_branch="$CURRENT_BRANCH" + needs_creation=false + fi + + log_info "Selected pending deploy branch: $selected_branch" + log_info "Needs creation: $needs_creation" + + echo "selected_branch=$selected_branch" >> "$GITHUB_OUTPUT" + echo "needs_creation=$needs_creation" >> "$GITHUB_OUTPUT" + + # Step 4: Ensure the selected pending deploy branch exists in the main repository + # Create it from main if it doesn't already exist remotely + - name: Create pending deploy branch if needed + if: steps.selected.outputs.needs_creation == 'true' + env: + SELECTED_BRANCH: ${{ steps.selected.outputs.selected_branch }} + run: | + source scripts/lib/logging.sh + + log_info "Creating pending deploy branch: $SELECTED_BRANCH" + + # Create the branch from main and push it to the main repository + git checkout -b "$SELECTED_BRANCH" origin/main + git push origin "$SELECTED_BRANCH" + + log_info "Branch $SELECTED_BRANCH created and pushed to origin" + + # Step 5: Retarget open PRs from any pending deploy branch to the selected branch + - name: Retarget open PRs + env: + GH_TOKEN: ${{ github.token }} + SELECTED_BRANCH: ${{ steps.selected.outputs.selected_branch }} + run: | + source scripts/lib/logging.sh + + log_info "Looking for open PRs targeting pending deploy branches..." + + # Find all open PRs and filter for those targeting automation/pending-deploy-YYYYMMDD-hhmmss branches + prs_to_retarget=$(gh pr list --state open --json number,baseRefName --jq '.[] | select(.baseRefName | test("^automation/pending-deploy-[0-9]{8}-[0-9]{6}$")) | select(.baseRefName != "'$SELECTED_BRANCH'") | "\(.number):\(.baseRefName)"') + + if [ -z "$prs_to_retarget" ]; then + log_info "No open PRs found targeting pending deploy branches" + else + log_info "Found PRs to retarget to $SELECTED_BRANCH:" + echo "$prs_to_retarget" | while IFS=: read -r pr_number base_branch; do + log_info " PR #$pr_number (from $base_branch)" + gh pr edit "$pr_number" --base "$SELECTED_BRANCH" + log_info " Retargeted PR #$pr_number to $SELECTED_BRANCH" + done + + log_info "All PRs retargeted successfully" + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 83d84e8..a9a3286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added pending deploy branch management to release workflow to ensure automated + PRs that remain open after an SDK release are retargeted to the latest pending + deploy branch + ## [7.1.0] - 2026-04-14 ### Added diff --git a/scripts/lib/logging.sh b/scripts/lib/logging.sh new file mode 100644 index 0000000..be47671 --- /dev/null +++ b/scripts/lib/logging.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Logging functions for GitHub Actions workflows + +# Output an informational message to stdout +log_info() { + local message="$1" + echo "$message" +} + +# Output an error message using GitHub Actions workflow command format +# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message +log_error() { + local message="$1" + echo "::error::$message" >&2 +} + +# Output a warning message using GitHub Actions workflow command format +log_warning() { + local message="$1" + echo "::warning::$message" >&2 +} + +# Output a notice message using GitHub Actions workflow command format +log_notice() { + local message="$1" + echo "::notice::$message" +} \ No newline at end of file