Skip to content

Commit e3c2bab

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 e3c2bab

3 files changed

Lines changed: 211 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 173 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ on:
44
push:
55
branches: [main]
66

7-
# contents:write is required for autotag-from-changelog to push tags.
87
permissions:
9-
contents: write
8+
contents: write # Push tags and create branches
9+
pull-requests: write # Retarget PRs to pending deploy branch
1010

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
}
28+
29+
# Write a single-line output: set_output key value
30+
set_output() {
31+
echo "$1=$2" >> "${GITHUB_OUTPUT:-/dev/null}"
32+
}

0 commit comments

Comments
 (0)