Skip to content

Commit 51f08fa

Browse files
committed
INFRA-3188:Create workflow to sync release, stable branches
1 parent c350664 commit 51f08fa

2 files changed

Lines changed: 330 additions & 0 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Release Branch Sync
2+
description: 'Syncs the stable branch into all open release branches after a release is merged.'
3+
4+
inputs:
5+
merged-release-branch:
6+
required: true
7+
description: 'The release branch that was just merged into stable (e.g., release/7.35.0)'
8+
repo-type:
9+
required: false
10+
description: 'Type of repository (mobile or extension)'
11+
default: 'mobile'
12+
github-token:
13+
description: 'GitHub token used for authentication and PR creation.'
14+
required: true
15+
github-tools-repository:
16+
description: 'The GitHub repository containing the GitHub tools.'
17+
required: false
18+
default: ${{ github.action_repository }}
19+
github-tools-ref:
20+
description: 'The ref of the GitHub tools repository to use.'
21+
required: false
22+
default: ${{ github.action_ref }}
23+
24+
runs:
25+
using: composite
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@v6
29+
with:
30+
fetch-depth: 0
31+
token: ${{ inputs.github-token }}
32+
33+
- name: Checkout GitHub tools repository
34+
uses: actions/checkout@v6
35+
with:
36+
repository: ${{ inputs.github-tools-repository }}
37+
ref: ${{ inputs.github-tools-ref }}
38+
path: ./github-tools
39+
40+
- name: Set Git user and email
41+
shell: bash
42+
run: |
43+
git config --global user.name "github-actions[bot]"
44+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
45+
46+
- name: Run release branch sync script
47+
env:
48+
MERGED_RELEASE_BRANCH: ${{ inputs.merged-release-branch }}
49+
REPO_TYPE: ${{ inputs.repo-type }}
50+
GITHUB_TOKEN: ${{ inputs.github-token }}
51+
shell: bash
52+
run: bash ./github-tools/.github/scripts/release-branch-sync.sh
53+
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
#!/bin/bash
2+
# =============================================================================
3+
# Release Branch Sync Script
4+
# =============================================================================
5+
# Purpose: After a release branch is merged into stable, create PRs to sync
6+
# stable into all other open release branches.
7+
#
8+
# Flow:
9+
# 1. Find all open release branches (release/X.Y.Z)
10+
# 2. For each one, create a branch from stable (stable-sync-release-X.Y.Z)
11+
# 3. Create a PR from that branch into the release branch
12+
# 4. Conflicts are left for manual resolution by developers
13+
#
14+
# Environment variables:
15+
# MERGED_RELEASE_BRANCH - The release branch that was just merged (e.g., release/7.35.0)
16+
# REPO_TYPE - Repository type: 'mobile' or 'extension'
17+
# GITHUB_TOKEN - GitHub token for authentication and PR creation
18+
# =============================================================================
19+
20+
set -e
21+
22+
# Regex pattern for valid release branch names (release/X.Y.Z)
23+
RELEASE_BRANCH_PATTERN='^release/[0-9]+\.[0-9]+\.[0-9]+$'
24+
25+
# -----------------------------------------------------------------------------
26+
# Helper Functions
27+
# -----------------------------------------------------------------------------
28+
29+
log_info() {
30+
echo "INFO: $1"
31+
}
32+
33+
log_success() {
34+
echo "SUCCESS: $1"
35+
}
36+
37+
log_warning() {
38+
echo "WARNING: $1"
39+
}
40+
41+
log_error() {
42+
echo "ERROR: $1"
43+
}
44+
45+
log_section() {
46+
echo ""
47+
echo "============================================================"
48+
echo "$1"
49+
echo "============================================================"
50+
}
51+
52+
# Validate that a branch name matches the release/X.Y.Z format
53+
is_valid_release_branch() {
54+
local branch=$1
55+
[[ "$branch" =~ $RELEASE_BRANCH_PATTERN ]]
56+
}
57+
58+
# Check if a sync PR already exists for a release branch
59+
pr_exists() {
60+
local release_branch=$1
61+
local sync_branch=$2
62+
63+
local existing_pr
64+
existing_pr=$(gh pr list --base "$release_branch" --head "$sync_branch" --state open --json number --jq 'length')
65+
66+
[[ "$existing_pr" -gt 0 ]]
67+
}
68+
69+
# Parse version from release branch name (release/X.Y.Z -> X.Y.Z)
70+
parse_version() {
71+
local branch=$1
72+
echo "$branch" | sed 's|release/||'
73+
}
74+
75+
# Compare two semantic versions
76+
# Returns: 0 if v1 < v2, 1 if v1 >= v2
77+
is_version_older() {
78+
local v1=$1
79+
local v2=$2
80+
81+
local oldest
82+
oldest=$(printf '%s\n%s\n' "$v1" "$v2" | sort -V | head -n1)
83+
84+
[[ "$v1" == "$oldest" && "$v1" != "$v2" ]]
85+
}
86+
87+
# Check if stable has commits that the release branch doesn't have
88+
stable_has_new_commits() {
89+
local release_branch=$1
90+
91+
# Count commits in stable that are not in the release branch
92+
local ahead_count
93+
ahead_count=$(git rev-list --count "origin/${release_branch}..origin/stable" 2>/dev/null || echo "0")
94+
95+
[[ "$ahead_count" -gt 0 ]]
96+
}
97+
98+
# Create a sync PR for a release branch
99+
create_sync_pr() {
100+
local release_branch=$1
101+
local sync_branch=$2
102+
103+
local body="## Summary
104+
105+
This PR syncs the latest changes from \`stable\` into \`${release_branch}\`.
106+
107+
## Why is this needed?
108+
109+
A release branch (\`${MERGED_RELEASE_BRANCH}\`) was merged into \`stable\`. This PR brings those changes (hotfixes, etc.) into \`${release_branch}\`.
110+
111+
## Action Required
112+
113+
**Please review and resolve any merge conflicts manually.**
114+
115+
If there are conflicts, they will appear in this PR. Resolve them to ensure the release branch has all the latest fixes from stable."
116+
117+
gh pr create \
118+
--base "$release_branch" \
119+
--head "$sync_branch" \
120+
--title "chore: sync stable into ${release_branch}" \
121+
--body "$body"
122+
}
123+
124+
# Process a single release branch
125+
# Returns: 0 = PR created, 1 = failed, 2 = skipped
126+
process_release_branch() {
127+
local release_branch=$1
128+
local merged_version=$2
129+
local release_version
130+
release_version=$(parse_version "$release_branch")
131+
132+
log_section "Processing ${release_branch}"
133+
134+
# Skip branches that don't match the release/X.Y.Z format
135+
if ! is_valid_release_branch "$release_branch"; then
136+
log_info "Skipping ${release_branch} (does not match release/X.Y.Z format)"
137+
return 2
138+
fi
139+
140+
# Skip the branch that was just merged
141+
if [[ "$release_branch" == "$MERGED_RELEASE_BRANCH" ]]; then
142+
log_info "Skipping ${release_branch} (just merged into stable)"
143+
return 2
144+
fi
145+
146+
# Skip branches older than the merged release
147+
if is_version_older "$release_version" "$merged_version"; then
148+
log_info "Skipping ${release_branch} (older than merged release ${MERGED_RELEASE_BRANCH})"
149+
return 2
150+
fi
151+
152+
# Create sync branch name (replace / with -)
153+
local sync_branch="stable-sync-${release_branch//\//-}"
154+
155+
# Check if a sync PR already exists
156+
if pr_exists "$release_branch" "$sync_branch"; then
157+
log_warning "Sync PR already exists for ${release_branch}, skipping"
158+
return 2
159+
fi
160+
161+
# Check if stable has any new commits compared to the release branch
162+
if ! stable_has_new_commits "$release_branch"; then
163+
log_success "${release_branch} is already up-to-date with stable, no sync needed"
164+
return 2
165+
fi
166+
167+
log_info "Creating sync branch: ${sync_branch} (from stable)"
168+
169+
# Ensure we're on a clean state
170+
git checkout -f origin/stable 2>/dev/null || true
171+
git clean -fd
172+
173+
# Delete local sync branch if it exists
174+
git branch -D "$sync_branch" 2>/dev/null || true
175+
176+
# Create sync branch from stable
177+
git checkout -b "$sync_branch" origin/stable
178+
179+
# Push the sync branch (force in case it exists remotely)
180+
log_info "Pushing ${sync_branch}..."
181+
if git push -u origin "$sync_branch" --force; then
182+
log_success "Pushed ${sync_branch}"
183+
else
184+
log_error "Failed to push ${sync_branch}"
185+
return 1
186+
fi
187+
188+
# Create the PR (stable-sync branch → release branch)
189+
log_info "Creating PR: ${sync_branch}${release_branch}"
190+
if create_sync_pr "$release_branch" "$sync_branch"; then
191+
log_success "Created PR for ${release_branch}"
192+
else
193+
log_error "Failed to create PR for ${release_branch}"
194+
return 1
195+
fi
196+
197+
return 0
198+
}
199+
200+
# -----------------------------------------------------------------------------
201+
# Main Script
202+
# -----------------------------------------------------------------------------
203+
204+
main() {
205+
log_section "Release Branch Sync"
206+
207+
# Validate environment
208+
if [[ -z "$MERGED_RELEASE_BRANCH" ]]; then
209+
log_error "MERGED_RELEASE_BRANCH environment variable is required"
210+
exit 1
211+
fi
212+
213+
if [[ -z "$GITHUB_TOKEN" ]]; then
214+
log_error "GITHUB_TOKEN environment variable is required"
215+
exit 1
216+
fi
217+
218+
log_info "Merged release branch: ${MERGED_RELEASE_BRANCH}"
219+
log_info "Repository type: ${REPO_TYPE:-not set}"
220+
221+
# Get version of the merged release
222+
local merged_version
223+
merged_version=$(parse_version "$MERGED_RELEASE_BRANCH")
224+
log_info "Merged version: ${merged_version}"
225+
226+
# Fetch all branches
227+
log_info "Fetching all branches..."
228+
git fetch --all --prune
229+
230+
# Find all release branches
231+
log_info "Finding open release branches..."
232+
local release_branches
233+
release_branches=$(git branch -r --list 'origin/release/*' | sed 's|origin/||' | tr -d ' ' | sort -t'/' -k2 -V)
234+
235+
if [[ -z "$release_branches" ]]; then
236+
log_warning "No release branches found"
237+
exit 0
238+
fi
239+
240+
log_info "Found release branches:"
241+
echo "$release_branches" | while read -r branch; do
242+
echo " - $branch"
243+
done
244+
245+
# Process each release branch
246+
local processed=0
247+
local skipped=0
248+
local failed=0
249+
250+
while IFS= read -r branch; do
251+
if [[ -z "$branch" ]]; then
252+
continue
253+
fi
254+
255+
local result
256+
process_release_branch "$branch" "$merged_version" && result=$? || result=$?
257+
258+
case $result in
259+
0) ((processed++)) || true ;; # PR created
260+
1) ((failed++)) || true ;; # Failed
261+
2) ((skipped++)) || true ;; # Skipped
262+
esac
263+
done <<< "$release_branches"
264+
265+
# Summary
266+
log_section "Summary"
267+
log_info "PRs created: ${processed}"
268+
log_info "Skipped: ${skipped}"
269+
if [[ "$failed" -gt 0 ]]; then
270+
log_error "Failed: ${failed}"
271+
exit 1
272+
fi
273+
274+
log_success "Release branch sync completed!"
275+
}
276+
277+
main "$@"

0 commit comments

Comments
 (0)