Skip to content

Commit 4a3f39f

Browse files
[CNSL-2134] Add pending deploy branch management to release workflow
After a successful release (tag creation), the workflow now automatically manages pending deploy branches: - Finds the most recent merged PR with an automation/pending-deploy-YYYYMMDD-hhmmss head branch to identify what the last pending deploy branch was - Compares its timestamp with any current live pending deploy branch - Creates a new pending deploy branch if a newer one was already merged to main (indicating the current live branch is stale), or if none exists - Retargets all open PRs from old pending deploy branches to the current one. (As of now, workflow does not resolve conflicts caused by this.) This ensures that after each release, there's always a fresh pending deploy branch based on the latest main, and PRs are automatically pointed to the right target. Co-authored-by: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
1 parent 7777b8d commit 4a3f39f

3 files changed

Lines changed: 204 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ on:
55
branches: [main]
66

77
# contents:write is required for autotag-from-changelog to push tags.
8+
# pull-requests:write is required for retargeting PRs to pending deploy branches.
89
permissions:
910
contents: write
11+
pull-requests: write
1012

1113
jobs:
1214
release:
1315
runs-on: ubuntu-latest
16+
outputs:
17+
tag-created: ${{ steps.autotag.outputs.tag-created }}
1418
steps:
1519
- uses: actions/checkout@v6
1620
with:
@@ -28,10 +32,176 @@ jobs:
2832
env:
2933
GH_TOKEN: ${{ secrets.CCLOUD_PRIVATE_DISPATCH_PAT }}
3034
run: |
35+
source scripts/lib/logging.sh
36+
3137
if [ -z "$GH_TOKEN" ]; then
32-
echo "::warning::CCLOUD_PRIVATE_DISPATCH_PAT secret is not set. Skipping dispatch to ccloud-private for ${{ steps.autotag.outputs.tag }}."
38+
log_warning "CCLOUD_PRIVATE_DISPATCH_PAT secret is not set. Skipping dispatch to ccloud-private for ${{ steps.autotag.outputs.tag }}."
3339
exit 0
3440
fi
3541
gh api repos/cockroachdb/ccloud-private/dispatches \
3642
-f event_type=sdk-release \
3743
-f "client_payload[version]=${{ steps.autotag.outputs.tag }}"
44+
45+
# After a successful release, this job ensures there's always a fresh pending deploy
46+
# branch ready for the next release cycle. It:
47+
# 1. Finds the most recently merged pending deploy branch (from PRs to main)
48+
# 2. Finds any existing live pending deploy branches
49+
# 3. Creates a new pending deploy branch if there are no live ones or if the live
50+
# ones are stale (associated with an older date than the last merged one)
51+
# 4. Retargets all open PRs to point to the current pending deploy branch
52+
manage-pending-deploy:
53+
needs: release
54+
if: needs.release.outputs.tag-created == 'true'
55+
runs-on: ubuntu-latest
56+
steps:
57+
- uses: actions/checkout@v6
58+
with:
59+
fetch-depth: 0
60+
61+
# Step 1: Determine the previous pending deploy branch timestamp from main
62+
# Find the most recent merged PR to main where the head branch was automation/pending-deploy-YYYYMMDD-hhmmss
63+
- name: Find previous pending deploy branch from merged PRs
64+
id: previous
65+
env:
66+
GH_TOKEN: ${{ github.token }}
67+
run: |
68+
source scripts/lib/logging.sh
69+
70+
log_info "Searching for previous pending deploy branch from merged PRs..."
71+
72+
# Fetch last 50 merged PRs to find the most recent pending deploy branch.
73+
# 50 should be sufficient given typical API change and SDK release cadence.
74+
previous_branch=$(gh pr list \
75+
--state merged \
76+
--base main \
77+
--limit 50 \
78+
--json headRefName,mergedAt \
79+
--jq '[.[] | select(.headRefName | test("^automation/pending-deploy-[0-9]{8}-[0-9]{6}$"))] | sort_by(.mergedAt) | reverse | .[0].headRefName')
80+
81+
if [ -z "$previous_branch" ] || [ "$previous_branch" = "null" ]; then
82+
log_info "No previous pending deploy branch found from merged PRs"
83+
echo "previous_branch_timestamp=" >> "$GITHUB_OUTPUT"
84+
else
85+
log_info "Found previous pending deploy branch: $previous_branch"
86+
87+
# Extract timestamp from the branch name: automation/pending-deploy-YYYYMMDD-hhmmss
88+
previous_branch_timestamp=$(echo "$previous_branch" | sed -E 's/^automation\/pending-deploy-([0-9]{8}-[0-9]{6})$/\1/' | tr -d '-')
89+
90+
echo "previous_branch_timestamp=$previous_branch_timestamp" >> "$GITHUB_OUTPUT"
91+
92+
log_info " Timestamp: $previous_branch_timestamp"
93+
fi
94+
95+
# Step 2: Determine the current live pending deploy branch in the main repository
96+
# Look for live remote branches matching automation/pending-deploy-YYYYMMDD-hhmmss
97+
# If multiple exist, select the one with the latest timestamp
98+
- name: Find current live pending deploy branch
99+
id: current
100+
run: |
101+
source scripts/lib/logging.sh
102+
103+
log_info "Searching for live pending deploy branches in the main repository..."
104+
105+
# List all remote branches matching the pattern, sorted by timestamp descending
106+
current_branch=$(git ls-remote --heads origin | \
107+
grep -oE 'automation/pending-deploy-[0-9]{8}-[0-9]{6}$' | \
108+
sort -r | \
109+
head -n 1)
110+
111+
if [ -z "$current_branch" ]; then
112+
log_info "No live pending deploy branch found"
113+
echo "current_branch=" >> "$GITHUB_OUTPUT"
114+
echo "current_branch_timestamp=" >> "$GITHUB_OUTPUT"
115+
else
116+
log_info "Found current live pending deploy branch: $current_branch"
117+
118+
# Extract timestamp
119+
current_branch_timestamp=$(echo "$current_branch" | sed -E 's/^automation\/pending-deploy-([0-9]{8}-[0-9]{6})$/\1/' | tr -d '-')
120+
121+
echo "current_branch=$current_branch" >> "$GITHUB_OUTPUT"
122+
echo "current_branch_timestamp=$current_branch_timestamp" >> "$GITHUB_OUTPUT"
123+
124+
log_info " Timestamp: $current_branch_timestamp"
125+
fi
126+
127+
# Step 3: Decide which pending deploy branch should be used now
128+
# If previous timestamp >= current timestamp, create a new branch
129+
- name: Select pending deploy branch
130+
id: selected
131+
env:
132+
PREVIOUS_BRANCH_TIMESTAMP: ${{ steps.previous.outputs.previous_branch_timestamp }}
133+
CURRENT_BRANCH: ${{ steps.current.outputs.current_branch }}
134+
CURRENT_BRANCH_TIMESTAMP: ${{ steps.current.outputs.current_branch_timestamp }}
135+
run: |
136+
source scripts/lib/logging.sh
137+
138+
# Get current UTC timestamp in YYYYMMDD-hhmmss format
139+
current_timestamp=$(date -u +%Y%m%d-%H%M%S)
140+
log_info "Current UTC timestamp: $current_timestamp"
141+
142+
# Decision logic: create new branch if previous >= current, otherwise use current
143+
if [ -z "$CURRENT_BRANCH" ]; then
144+
# No current live branch exists, create new
145+
log_info "No current live branch exists, creating new branch..."
146+
selected_branch="automation/pending-deploy-${current_timestamp}"
147+
needs_creation=true
148+
elif [ -n "$PREVIOUS_BRANCH_TIMESTAMP" ] && [ "$PREVIOUS_BRANCH_TIMESTAMP" -ge "$CURRENT_BRANCH_TIMESTAMP" ]; then
149+
# Previous timestamp >= current timestamp, create new branch
150+
log_info "Previous timestamp ($PREVIOUS_BRANCH_TIMESTAMP) >= current timestamp ($CURRENT_BRANCH_TIMESTAMP), creating new branch..."
151+
selected_branch="automation/pending-deploy-${current_timestamp}"
152+
needs_creation=true
153+
else
154+
# Use current live branch
155+
log_info "Using current live branch"
156+
selected_branch="$CURRENT_BRANCH"
157+
needs_creation=false
158+
fi
159+
160+
log_info "Selected pending deploy branch: $selected_branch"
161+
log_info "Needs creation: $needs_creation"
162+
163+
echo "selected_branch=$selected_branch" >> "$GITHUB_OUTPUT"
164+
echo "needs_creation=$needs_creation" >> "$GITHUB_OUTPUT"
165+
166+
# Step 4: Ensure the selected pending deploy branch exists in the main repository
167+
# Create it from main if it doesn't already exist remotely
168+
- name: Create pending deploy branch if needed
169+
if: steps.selected.outputs.needs_creation == 'true'
170+
env:
171+
SELECTED_BRANCH: ${{ steps.selected.outputs.selected_branch }}
172+
run: |
173+
source scripts/lib/logging.sh
174+
175+
log_info "Creating pending deploy branch: $SELECTED_BRANCH"
176+
177+
# Create the branch from main and push it to the main repository
178+
git checkout -b "$SELECTED_BRANCH" origin/main
179+
git push origin "$SELECTED_BRANCH"
180+
181+
log_info "Branch $SELECTED_BRANCH created and pushed to origin"
182+
183+
# Step 5: Retarget open PRs from any pending deploy branch to the selected branch
184+
- name: Retarget open PRs
185+
env:
186+
GH_TOKEN: ${{ github.token }}
187+
SELECTED_BRANCH: ${{ steps.selected.outputs.selected_branch }}
188+
run: |
189+
source scripts/lib/logging.sh
190+
191+
log_info "Looking for open PRs targeting pending deploy branches..."
192+
193+
# Find all open PRs and filter for those targeting automation/pending-deploy-YYYYMMDD-hhmmss branches
194+
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)"')
195+
196+
if [ -z "$prs_to_retarget" ]; then
197+
log_info "No open PRs found targeting pending deploy branches"
198+
else
199+
log_info "Found PRs to retarget to $SELECTED_BRANCH:"
200+
echo "$prs_to_retarget" | while IFS=: read -r pr_number base_branch; do
201+
log_info " PR #$pr_number (from $base_branch)"
202+
gh pr edit "$pr_number" --base "$SELECTED_BRANCH"
203+
log_info " Retargeted PR #$pr_number to $SELECTED_BRANCH"
204+
done
205+
206+
log_info "All PRs retargeted successfully"
207+
fi

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added pending deploy branch management to release workflow to ensure automated
13+
PRs that remain open after an SDK release are retargeted to the latest pending
14+
deploy branch
15+
1016
## [7.1.0] - 2026-04-14
1117

1218
### Added

scripts/lib/logging.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
# Logging functions for GitHub Actions workflows
3+
4+
# Output an informational message to stdout
5+
log_info() {
6+
local message="$1"
7+
echo "$message"
8+
}
9+
10+
# Output an error message using GitHub Actions workflow command format
11+
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
12+
log_error() {
13+
local message="$1"
14+
echo "::error::$message" >&2
15+
}
16+
17+
# Output a warning message using GitHub Actions workflow command format
18+
log_warning() {
19+
local message="$1"
20+
echo "::warning::$message" >&2
21+
}
22+
23+
# Output a notice message using GitHub Actions workflow command format
24+
log_notice() {
25+
local message="$1"
26+
echo "::notice::$message"
27+
}

0 commit comments

Comments
 (0)