Skip to content

Commit 2c3c97d

Browse files
ci: fix backport workflow not cleaning up branch on failure and not able to update existing PRs/branches on re-run (#6620)
Fixes issue in which a failed backport runs would not cleanup the branch (issue 1) and then on the next backport attempt, it would bail out early because it checks if a branch with that name already exists (issue 2). The workflow now treats existing backport branches as reusable unless an open PR already references them (issue 2 solution), force-updates any reused branch with the latest cherry-pick, and records them so a new cleanup step can delete the branch if the run fails (issue 1 solution). That prevents stranded refs from blocking future backport runs while keeping active backport PRs intact. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6620-ci-fix-backport-workflow-not-cleaning-up-branch-on-failure-and-not-able-to-update-existi-2a36d73d365081efbbcbfa75f0c1bbe7) by [Unito](https://www.unito.io)
1 parent f97cf77 commit 2c3c97d

File tree

1 file changed

+66
-7
lines changed

1 file changed

+66
-7
lines changed

.github/workflows/pr-backport.yaml

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ jobs:
164164
165165
PENDING=()
166166
SKIPPED=()
167+
REUSED=()
167168
168169
for target in $REQUESTED_TARGETS; do
169170
SAFE_TARGET=$(echo "$target" | tr '/' '-')
@@ -176,27 +177,43 @@ jobs:
176177
177178
if printf '%s\n' "${EXISTING_BRANCHES[@]:-}" |
178179
grep -Fq "refs/heads/${BACKPORT_BRANCH}"; then
179-
SKIPPED+=("$target")
180-
else
181-
PENDING+=("$target")
180+
OPEN_PR=$(
181+
gh pr list \
182+
--state open \
183+
--head "${BACKPORT_BRANCH}" \
184+
--json number \
185+
--jq 'if length > 0 then .[0].number else "" end'
186+
)
187+
if [ -n "$OPEN_PR" ]; then
188+
SKIPPED+=("${target} (PR #${OPEN_PR})")
189+
continue
190+
fi
191+
192+
REUSED+=("$BACKPORT_BRANCH")
182193
fi
194+
195+
PENDING+=("$target")
183196
done
184197
185198
SKIPPED_JOINED="${SKIPPED[*]:-}"
186199
PENDING_JOINED="${PENDING[*]:-}"
187200
188201
echo "already-exists=${SKIPPED_JOINED}" >> $GITHUB_OUTPUT
189202
echo "pending-targets=${PENDING_JOINED}" >> $GITHUB_OUTPUT
203+
echo "reused-branches=${REUSED[*]:-}" >> $GITHUB_OUTPUT
190204
191205
if [ -z "$PENDING_JOINED" ]; then
192206
echo "skip=true" >> $GITHUB_OUTPUT
193207
if [ -n "$SKIPPED_JOINED" ]; then
194-
echo "::warning::Backport branches already exist for: ${SKIPPED_JOINED}"
208+
echo "::warning::Backport branches exist: ${SKIPPED_JOINED}"
195209
fi
196210
else
197211
echo "skip=false" >> $GITHUB_OUTPUT
198212
if [ -n "$SKIPPED_JOINED" ]; then
199-
echo "::notice::Skipping already backported targets: ${SKIPPED_JOINED}"
213+
echo "::notice::Skipping backport targets: ${SKIPPED_JOINED}"
214+
fi
215+
if [ "${#REUSED[@]}" -gt 0 ]; then
216+
echo "::notice::Reusing backport branches: ${REUSED[*]}"
200217
fi
201218
fi
202219
@@ -208,7 +225,12 @@ jobs:
208225
run: |
209226
FAILED=""
210227
SUCCESS=""
211-
228+
229+
CREATED_BRANCHES_FILE="$(
230+
mktemp "$RUNNER_TEMP/backport-branches-XXXXXX"
231+
)"
232+
echo "CREATED_BRANCHES_FILE=$CREATED_BRANCHES_FILE" >> "$GITHUB_ENV"
233+
212234
# Get PR data for manual triggers
213235
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
214236
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,mergeCommit)
@@ -223,6 +245,12 @@ jobs:
223245
TARGET_BRANCH="${target}"
224246
SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-')
225247
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
248+
REMOTE_BACKPORT_EXISTS=false
249+
250+
if git ls-remote --exit-code origin "${BACKPORT_BRANCH}" >/dev/null 2>&1; then
251+
REMOTE_BACKPORT_EXISTS=true
252+
echo "::notice::Updating existing branch ${BACKPORT_BRANCH}"
253+
fi
226254
227255
echo "::group::Backporting to ${TARGET_BRANCH}"
228256
@@ -247,7 +275,12 @@ jobs:
247275
248276
# Try cherry-pick
249277
if git cherry-pick "${MERGE_COMMIT}"; then
250-
git push origin "${BACKPORT_BRANCH}"
278+
if [ "$REMOTE_BACKPORT_EXISTS" = true ]; then
279+
git push --force-with-lease origin "${BACKPORT_BRANCH}"
280+
else
281+
git push origin "${BACKPORT_BRANCH}"
282+
fi
283+
echo "${BACKPORT_BRANCH}" >> "$CREATED_BRANCHES_FILE"
251284
SUCCESS="${SUCCESS}${TARGET_BRANCH}:${BACKPORT_BRANCH} "
252285
echo "Successfully created backport branch: ${BACKPORT_BRANCH}"
253286
# Return to main (keep the branch, we need it for PR)
@@ -271,6 +304,13 @@ jobs:
271304
echo "success=${SUCCESS}" >> $GITHUB_OUTPUT
272305
echo "failed=${FAILED}" >> $GITHUB_OUTPUT
273306
307+
if [ -s "$CREATED_BRANCHES_FILE" ]; then
308+
CREATED_LIST=$(paste -sd' ' "$CREATED_BRANCHES_FILE")
309+
echo "created-branches=${CREATED_LIST}" >> $GITHUB_OUTPUT
310+
else
311+
echo "created-branches=" >> $GITHUB_OUTPUT
312+
fi
313+
274314
if [ -n "${FAILED}" ]; then
275315
exit 1
276316
fi
@@ -348,6 +388,25 @@ jobs:
348388
fi
349389
done
350390
391+
- name: Cleanup stranded backport branches
392+
if: steps.filter-targets.outputs.skip != 'true' && failure()
393+
run: |
394+
FILE="${CREATED_BRANCHES_FILE:-}"
395+
396+
if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
397+
echo "No backport branches recorded for cleanup"
398+
exit 0
399+
fi
400+
401+
while IFS= read -r branch; do
402+
[ -z "$branch" ] && continue
403+
printf 'Deleting branch %s\n' "${branch}"
404+
if ! git push origin --delete "$branch"; then
405+
echo "::warning::Failed to delete ${branch}"
406+
fi
407+
done < "$FILE"
408+
409+
351410
- name: Remove needs-backport label
352411
if: steps.filter-targets.outputs.skip != 'true' && success()
353412
run: gh pr edit ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} --remove-label "needs-backport"

0 commit comments

Comments
 (0)