-
-
Notifications
You must be signed in to change notification settings - Fork 92
[feature] Add reusable backport workflow #592
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[feature] Add reusable backport workflow #592
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughTwo new GitHub Actions workflows are added to automate backporting fixes to stable release branches. The main workflow (backport.yml) triggers on pushes to the master branch or on issue comments containing "/backport" directives. It calls a reusable workflow (reusable-backport.yml) which parses commit messages or comment directives to identify target branches, attempts cherry-picking the specified commit to each target branch, and either creates a pull request on success or posts manual backport instructions if conflicts occur. Sequence Diagram(s)sequenceDiagram
participant GHEvent as GitHub Event
participant Parse as Parse Job
participant Backport as Backport Job
participant Git as Git Operations
participant PR as PR/Comment System
GHEvent->>Parse: Trigger (push/comment)
Parse->>Parse: Extract commit SHA or<br/>parse comment body
Parse->>Parse: Identify target branches<br/>from commit/comment
Parse-->>Backport: Output branches + SHA
rect rgba(76, 175, 80, 0.5)
Note over Backport,PR: Success Path
Backport->>Git: Cherry-pick SHA to<br/>backport branch
Git-->>Backport: Success
Backport->>PR: Create PR to target branch
end
rect rgba(244, 67, 54, 0.5)
Note over Backport,PR: Failure Path
Backport->>Git: Cherry-pick SHA
Git-->>Backport: Conflict detected
Backport->>Backport: Abort cherry-pick
Backport->>PR: Post comment with<br/>manual instructions
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @.github/workflows/backport.yml:
- Around line 19-25: Tighten the issue_comment matcher to only trigger for the
exact /backport command (replace startsWith(github.event.comment.body,
'/backport') with a regex match like matches(github.event.comment.body,
'^/backport(\\s|$)') so variants like /backporting won't match) and in the
reusable workflow add a step that checks steps.check.outputs.merged and, when it
!= 'true', posts a comment back to the PR (using the existing check step name
"check" and a comment action or gh script) to notify the user the PR was closed
but not merged and the backport was skipped.
In @.github/workflows/reusable-backport.yml:
- Around line 27-31: Update the "Create backport pull requests" step that uses
korthout/backport-action@v4 to include an explicit experimental conflict
resolution setting so conflicts create draft backport commits and post
resolution instructions instead of failing; specifically, add the experimental
input with conflict_resolution set to "draft_commit_conflicts" alongside the
existing source_pr_number input in the step that uses
korthout/backport-action@v4 to enable PR commenting on cherry-pick conflicts.
🧹 Nitpick comments (1)
.github/workflows/backport.yml (1)
26-26: Consider using a local path for the reusable workflow within this repo.Since
backport.ymlandreusable-backport.ymllive in the same repository (openwisp-utils), referencing via the full path@mastermeans any changes to the reusable workflow cannot be tested through a PR — it will always pull the version frommaster. Using a relative path enables PR-based testing of workflow changes.- uses: openwisp/openwisp-utils/.github/workflows/reusable-backport.yml@master + uses: ./.github/workflows/reusable-backport.ymlOther OpenWISP repos that consume this reusable workflow would still reference the full
openwisp/openwisp-utils/...@masterpath.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/backport.yml.github/workflows/reusable-backport.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: Python==3.12 | django~=5.2.0
- GitHub Check: Python==3.12 | django~=4.2.0
- GitHub Check: Python==3.10 | django~=4.2.0
- GitHub Check: Python==3.13 | django~=5.1.0
- GitHub Check: Python==3.10 | django~=5.0.0
- GitHub Check: Python==3.13 | django~=5.2.0
- GitHub Check: Python==3.11 | django~=5.0.0
- GitHub Check: Python==3.10 | django~=5.2.0
- GitHub Check: Python==3.12 | django~=5.0.0
🔇 Additional comments (2)
.github/workflows/backport.yml (1)
3-7: Workflow triggers are appropriate.Using
pull_request_target(fork-safe) andissue_commentis the standard pattern for this kind of automation. Note thatissue_commentfires for every comment on every issue/PR, but the job-leveliffilters it correctly, so the job won't actually run for irrelevant comments..github/workflows/reusable-backport.yml (1)
24-25: No changes needed. Both concerns in the original review are incorrect.
actions/checkout@v6is published and current (v6.0.2 as of Feb 2026).korthout/backport-actionexplicitly supports shallow clones by design—it performs its owngit fetchfor refs/SHAs it needs. The defaultfetch-depth: 1is fine;fetch-depth: 0is only necessary if you encounter specific cherry-pick errors related to missing history.Likely an incorrect or invalid review comment.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
1227109 to
5aa195a
Compare
|
addressed all the comments made by code rabbit (along with the nitpicks) |
nemesifier
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to see this @atif09!
I have a couple of questions:
- Can you explain why we need two YAML files, what does each do?
- Can this work by recognizing a flag in the commit message?
From what I see it would work with either a comment or an issue label.
Request: please add a new docs section for this here:
https://github.com/openwisp/openwisp-utils/blob/master/docs/developer/reusable-github-utils.rst to describe why this was added and basic usage instructions.
|
1)the the this is similar to 2)no, this approach doesnt recognize flags like [fix] or something of that sort in the commit message, it works by a comment trigger or an issue label trigger. And the label is needed to recognize which branch to cherry pick to, and the keyword in the commit message wouldnt tell it the target branch 3)noted, will add the docs explaining this section's basic usage and other related info
|
af63e32 to
fa23440
Compare
fa23440 to
7e77420
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @.github/workflows/reusable-backport.yml:
- Line 96: The reusable workflow currently hardcodes "master" in the manual
backport echo command (the line that echoes "git fetch origin $BRANCH master");
add a workflow_call input named default_branch (string, optional, default
"master"), wire that input into the job environment as DEFAULT_BRANCH, and
update the echo/command to use $DEFAULT_BRANCH instead of the literal "master"
(e.g., "git fetch origin $BRANCH $DEFAULT_BRANCH") so calling repos can override
the default branch safely; ensure DEFAULT_BRANCH is passed via env to avoid
injection.
- Around line 71-79: The run block currently interpolates untrusted values
directly (SHA="${{ needs.parse.outputs.sha }}" and BRANCH="${{ matrix.branch
}}"), allowing script injection; instead move those interpolations into the
job's env section (set env: SHA: ${{ needs.parse.outputs.sha }}, BRANCH: ${{
matrix.branch }}) and reference $SHA and $BRANCH inside run, then explicitly
sanitize them at the start of the run step (e.g., strip newlines/CRs and reject
or escape shell metacharacters) before using them to build PR_NUMBER,
BACKPORT_BRANCH, PR_TITLE; fail early if BRANCH contains unsafe characters so
gh/other commands never receive unsanitized input.
- Around line 33-42: Avoid directly interpolating GitHub Action inputs into the
run script; instead export them as environment variables and reference those
vars inside the shell to prevent pre-execution interpolation and command
injection. Update the run block so that inputs.commit_sha, inputs.pr_number, and
inputs.comment_body are passed via env (e.g., export SHA_INPUT, PR_INPUT,
COMMENT_INPUT) and then use SHA_INPUT/PR_INPUT/COMMENT_INPUT inside the shell to
populate variables like SHA, COMMIT_MSG, COMMENT and derive BRANCHES, keeping
any gh api calls using the env var values rather than the ${{ ... }}
interpolation.
🧹 Nitpick comments (1)
.github/workflows/reusable-backport.yml (1)
77-79: Redundant API call: PR number and title fetched in two separate requests to the same endpoint.Lines 77 and 79 both call
repos/{repo}/commits/{sha}/pulls. Combine into a single call to reduce API usage and latency.Proposed fix
- PR_NUMBER=$(gh api "repos/$REPO/commits/$SHA/pulls" --jq '.[0].number') + PR_INFO=$(gh api "repos/$REPO/commits/$SHA/pulls" --jq '.[0] | {number, title}') + PR_NUMBER=$(echo "$PR_INFO" | jq -r '.number') + PR_TITLE=$(echo "$PR_INFO" | jq -r '.title') BACKPORT_BRANCH="backport/${PR_NUMBER}-to-${BRANCH}" - PR_TITLE=$(gh api "repos/$REPO/commits/$SHA/pulls" --jq '.[0].title')
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.github/workflows/backport.yml.github/workflows/reusable-backport.ymlbackport-test-file.txt
✅ Files skipped from review due to trivial changes (1)
- backport-test-file.txt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-02-10T20:38:21.134Z
Learnt from: nemesifier
Repo: openwisp/openwisp-utils PR: 593
File: openwisp_utils/releaser/commitizen.py:5-9
Timestamp: 2026-02-10T20:38:21.134Z
Learning: In openwisp-utils commitizen implementation, the `_TITLE_ISSUE_EXTRACT_RE` pattern intentionally matches any space-preceded `#<number>` in commit titles without anchoring to the end. This is acceptable because 99.9% of such patterns are actual issue references, and rare edge cases (like version numbers) will be handled manually if they occur.
Applied to files:
.github/workflows/backport.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: Python==3.11 | django~=5.1.0
- GitHub Check: Python==3.11 | django~=4.2.0
- GitHub Check: Python==3.13 | django~=5.1.0
- GitHub Check: Python==3.12 | django~=5.2.0
- GitHub Check: Python==3.10 | django~=4.2.0
- GitHub Check: Python==3.11 | django~=5.2.0
- GitHub Check: Python==3.13 | django~=5.2.0
- GitHub Check: Python==3.12 | django~=5.0.0
- GitHub Check: Python==3.10 | django~=5.2.0
- GitHub Check: Python==3.12 | django~=5.1.0
- GitHub Check: Python==3.10 | django~=5.0.0
- GitHub Check: Python==3.10 | django~=5.1.0
- GitHub Check: Python==3.12 | django~=4.2.0
- GitHub Check: Python==3.11 | django~=5.0.0
🔇 Additional comments (2)
.github/workflows/backport.yml (1)
1-12: Triggers and permissions look appropriate.Push-to-master captures merge events, and
issue_commentenables the manual/backportfallback.contents: writeandpull-requests: writeare the minimum required for pushing branches and creating PRs..github/workflows/reusable-backport.yml (1)
62-62:actions/checkout@v6is available. Released on Nov 20, 2025 (latest patch v6.0.2 as of Jan 9, 2026). Note that v6 requires Actions Runner v2.329.0+ due to credential-handling changes.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
2102181 to
3ede984
Compare
Automates cherry-picking fixes to stable branches via [backport X.Y] in commit messages or /backport X.Y comments, with conflict notification. Closes openwisp#501
3ede984 to
c3122f5
Compare
Moved direct interpolations to env variables in run blocks as recommended by CodeRabbit to prevent shell injection. Fixes openwisp#501
|
i have tested these new changes on test PRs again on my fork, and they are working |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs missing. PS: can you show this action in action somewhere before we merge it?
Make sure to have different branches (eg: 1.2, 1.3, 1.4) and backport only to specific branches indicated in the commit message.
Would a comment post merge also work?
sorry i didnt understand, do you mean in a repo which i made (besides the fork)?
noted, was waiting on your feedback before adding docs, ill add them
i have also updated the PR description with these new test PRs |
Added instructions and explanation related to the usage of this new custom workflow Related to openwisp#501
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work @atif09! It looks like is doing just what we need!
I reviewed the code manually and also with some automated tools, I have found a few things that need to be improved before we can merge this and use it in all our repos.
Critical Issues (Must Fix)
1. Race Condition on Branch Creation 🚨
Problem: The branch naming strategy backport/${PR_NUMBER}-to-${BRANCH} can collide on workflow re-runs or if multiple commits from the same PR are backported.
Current Code:
BACKPORT_BRANCH="backport/${PR_NUMBER}-to-${BRANCH}"
git checkout -b "$BACKPORT_BRANCH" "origin/$BRANCH"Impact: Subsequent runs will fail with "fatal: A branch named '...' already exists", or worse, may push to an existing branch with unintended commits.
Fix:
# Option 1: Timestamp-based
BACKPORT_BRANCH="backport/${PR_NUMBER}-to-${BRANCH}-$(date +%s)"
# Option 2: Check-and-skip (preferred for idempotency)
EXISTING_PR=$(gh pr list --base "$BRANCH" --head "$BACKPORT_BRANCH" --state open --json number --jq '.[0].number')
if [ -n "$EXISTING_PR" ]; then
echo "Backport PR #$EXISTING_PR already exists, skipping"
exit 0
fi
git checkout -B "$BACKPORT_BRANCH" "origin/$BRANCH" # -B forces reset if exists2. Missing PR Merge State Validation 🚨
Problem: The comment trigger only checks issue.state == 'closed' but not whether the PR was actually merged (could be closed without merging).
Current Code:
if: >
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.issue.state == 'closed' &&
...Impact: Users could trigger backports on unmerged PRs, leading to cherry-picking commits that shouldn't be backported.
Fix: Add validation in the parse job:
if [ -n "$PR_NUMBER" ]; then
MERGED=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json merged --jq '.merged')
if [ "$MERGED" != "true" ]; then
echo "PR #$PR_NUMBER is not merged, exiting"
exit 0
fi
fi3. No Target Branch Existence Check 🚨
Problem: The workflow doesn't validate that the target branch (e.g., 1.1) exists before attempting to cherry-pick.
Impact: Confusing failures late in the workflow when git checkout fails on non-existent branches.
Fix:
if ! git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
echo "::error::Target branch '$BRANCH' does not exist"
exit 1
fi4. Fragile PR Association on Direct Commits 🚨
Problem: When commits are pushed directly to master (not via PR), gh api "repos/$REPO/commits/$SHA/pulls" returns an empty array, causing jq to return null.
Current Code:
PR_DATA=$(gh api "repos/$REPO/commits/$SHA/pulls" --jq '.[0] | {number, title}')
PR_NUMBER=$(echo "$PR_DATA" | jq -r '.number')Impact: Script fails with null values or creates PRs with "null" in the title.
Fix:
PR_DATA=$(gh api "repos/$REPO/commits/$SHA/pulls" --jq '.[0] | {number, title}')
PR_NUMBER=$(echo "$PR_DATA" | jq -r '.number // empty')
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // "Fix from master"')
if [ -z "$PR_NUMBER" ]; then
# Handle direct commit to master
BACKPORT_BRANCH="backport/commit-${SHA:0:7}-to-${BRANCH}"
PR_TITLE="Backport commit ${SHA:0:7} to $BRANCH"
BODY="Backport of commit $SHA to \`$BRANCH\`."
else
BACKPORT_BRANCH="backport/${PR_NUMBER}-to-${BRANCH}"
BODY="Backport of #$PR_NUMBER to \`$BRANCH\`."
fi5. Missing Support for main Branch 🚨
Problem: The trigger workflow only listens to master, but many repositories use main.
Current Code:
on:
push:
branches:
- masterFix:
on:
push:
branches:
- master
- mainSecurity Issues
6. Branch Name Injection Risk ⚠️
Problem: The branch name extracted from user comments (/backport X.Y) is used directly in shell commands without sanitization.
Current Code:
BRANCHES=$(echo "$COMMENT_BODY" | grep -oP '/backport\s+\K\S+' | tr '\n' ',' | sed 's/,$//')
# ... later used in:
git checkout -b "$BACKPORT_BRANCH" "origin/$BRANCH"Risk: While GitHub Actions env var quoting provides some protection, malicious branch names with special characters could cause unexpected behavior.
Fix:
# Sanitize branch names - only allow alphanumeric, dots, dashes, and slashes
BRANCH=$(echo "$BRANCH" | sed 's/[^a-zA-Z0-9._/-]//g')7. Workflow Token Permissions ⚠️
Observation: The workflow correctly uses minimal permissions (contents: write, pull-requests: write). However, using GITHUB_TOKEN for pushing and creating PRs means subsequent workflows (like CI) won't be triggered automatically due to GitHub's token restrictions.
This is a problem because we need CI to run on these PRs. I looked for possible solutions and it seems the only secure way to do this is to create a github app, this is an investment of time I am willing to make as we can reuse the same app for other tasks which deal with onboarding and guiding contributors. Here's the details:
Option 1: GitHub App Token (Recommended) ⭐
Best balance of security and functionality.
I already created the Github App and added the organization secrets.
- Update Trigger Workflow (
.github/workflows/backport.yml):
name: Backport fixes to stable branch
on:
push:
branches:
- master
- main
issue_comment:
types: [created]
permissions:
contents: write
pull-requests: write
jobs:
backport-on-push:
if: github.event_name == 'push'
uses: ./.github/workflows/reusable-backport.yml
with:
commit_sha: ${{ github.sha }}
secrets:
app_id: ${{ secrets.OPENWISP_BOT_APP_ID }}
private_key: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }}
backport-on-comment:
if: >
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.issue.state == 'closed' &&
contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association) &&
startsWith(github.event.comment.body, '/backport')
uses: ./.github/workflows/reusable-backport.yml
with:
pr_number: ${{ github.event.issue.number }}
comment_body: ${{ github.event.comment.body }}
secrets:
app_id: ${{ secrets.OPENWISP_BOT_APP_ID }}
private_key: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }}- Update Reusable Workflow (
.github/workflows/reusable-backport.yml):
name: Backport fixes to stable branch
on:
workflow_call:
inputs:
commit_sha:
required: false
type: string
default: ""
pr_number:
required: false
type: number
default: 0
comment_body:
required: false
type: string
default: ""
secrets:
app_id:
required: true
private_key:
required: true
jobs:
parse:
runs-on: ubuntu-latest
outputs:
branches: ${{ steps.extract.outputs.branches }}
sha: ${{ steps.extract.outputs.sha }}
steps:
- name: Extract backport targets
id: extract
env:
GH_TOKEN: ${{ github.token }}
COMMIT_SHA: ${{ inputs.commit_sha }}
PR_NUMBER: ${{ inputs.pr_number }}
COMMENT_BODY: ${{ inputs.comment_body }}
REPO: ${{ github.repository }}
run: |
if [ -n "$COMMIT_SHA" ]; then
SHA="$COMMIT_SHA"
COMMIT_MSG=$(gh api "repos/$REPO/git/commits/$SHA" --jq '.message')
BRANCHES=$(echo "$COMMIT_MSG" | sed -n 's/.*\[backport[:\s]\+\([^]]*\)\].*/\1/p' | tr '\n' ',' | sed 's/,$//')
else
SHA=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json mergeCommit --jq '.mergeCommit.oid')
BRANCHES=$(echo "$COMMENT_BODY" | grep -oP '/backport\s+\K\S+' | tr '\n' ',' | sed 's/,$//')
fi
if [ -z "$BRANCHES" ]; then
echo "branches=[]" >> $GITHUB_OUTPUT
else
echo "branches=$(echo "$BRANCHES" | tr ',' '\n' | jq -R . | jq -sc .)" >> $GITHUB_OUTPUT
fi
echo "sha=$SHA" >> $GITHUB_OUTPUT
backport:
needs: parse
if: needs.parse.outputs.branches != '[]'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branch: ${{ fromJSON(needs.parse.outputs.branches) }}
steps:
- name: Generate GitHub App Token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.app_id }}
private-key: ${{ secrets.private_key }}
- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.generate-token.outputs.token }}
- name: Configure Git
run: |
git config user.name 'OpenWISP Automation Bot'
git config user.email 'support@openwisp.io'
- name: Cherry-pick and create PR
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
SHA: ${{ needs.parse.outputs.sha }}
BRANCH: ${{ matrix.branch }}
REPO: ${{ github.repository }}
run: |
# Validate target branch exists
if ! git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
echo "::error::Target branch '$BRANCH' does not exist"
exit 1
fi
# Get PR data (handle direct commits)
PR_DATA=$(gh api "repos/$REPO/commits/$SHA/pulls" --jq '.[0] | {number, title}' || echo "null")
PR_NUMBER=$(echo "$PR_DATA" | jq -r '.number // empty')
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // "Fix from master"')
if [ -z "$PR_NUMBER" ]; then
# Handle direct commit to master
BACKPORT_BRANCH="backport/commit-${SHA:0:7}-to-${BRANCH}"
BODY="Backport of commit $SHA to \`$BRANCH\`."
PR_TITLE="Backport commit ${SHA:0:7} to $BRANCH"
else
BACKPORT_BRANCH="backport/${PR_NUMBER}-to-${BRANCH}"
BODY="Backport of #$PR_NUMBER to \`$BRANCH\`."
# Check for existing backport PR
EXISTING=$(gh pr list --base "$BRANCH" --head "$BACKPORT_BRANCH" --state open --json number --jq '.[0].number' || echo "")
if [ -n "$EXISTING" ]; then
echo "Backport PR #$EXISTING already exists, skipping"
exit 0
fi
fi
# Create unique branch name to avoid collisions
BACKPORT_BRANCH="${BACKPORT_BRANCH}-$(date +%s)"
git checkout -b "$BACKPORT_BRANCH" "origin/$BRANCH"
if git cherry-pick -x "$SHA"; then
git push origin "$BACKPORT_BRANCH"
if ! gh pr create \
--base "$BRANCH" \
--head "$BACKPORT_BRANCH" \
--title "[backport] $PR_TITLE (to $BRANCH)" \
--body "$BODY"; then
echo "::error::Failed to create PR"
git push origin --delete "$BACKPORT_BRANCH" || true
exit 1
fi
echo "## ✅ Backport Successful" >> $GITHUB_STEP_SUMMARY
echo "- **Target Branch**: $BRANCH" >> $GITHUB_STEP_SUMMARY
else
git cherry-pick --abort || true
{
echo "❌ Cherry-pick to \`$BRANCH\` failed due to conflicts. Please backport manually:"
echo ""
echo "\`\`\`bash"
echo "git fetch origin $BRANCH"
echo "git checkout -b $BACKPORT_BRANCH origin/$BRANCH"
echo "git cherry-pick -x $SHA"
echo "# resolve conflicts"
echo "git cherry-pick --continue"
echo "git push origin $BACKPORT_BRANCH"
echo "\`\`\`"
} > /tmp/backport-comment.md
[ -n "$PR_NUMBER" ] && gh pr comment "$PR_NUMBER" --body-file /tmp/backport-comment.md
echo "## ❌ Backport Failed" >> $GITHUB_STEP_SUMMARY
echo "- **Target Branch**: $BRANCH" >> $GITHUB_STEP_SUMMARY
echo "- **Error**: Cherry-pick conflicts" >> $GITHUB_STEP_SUMMARY
exit 1
fiPros:
- ✅ CI runs automatically on backport PRs
- ✅ Fine-grained permissions control
- ✅ Scoped to specific repositories
- ✅ No personal user tokens required
- ✅ Can be centrally managed at org level
- ✅ Reusable for other automation workflows
Cons:
⚠️ Requires one-time setup of GitHub App⚠️ App needs to be installed on each repo
Moderate Issues (Should Fix)
8. Empty Cherry-Pick Not Handled
Problem: If a commit was already cherry-picked manually, git returns "The previous cherry-pick is now empty" which is treated as a failure.
Fix:
if git cherry-pick -x "$SHA"; then
# Success - create PR
...
elif [ $? -eq 1 ] && git diff --cached --quiet; then
# Empty commit - already backported
echo "Commit $SHA appears to already be in $BRANCH (empty cherry-pick)"
git cherry-pick --abort || true
exit 0
else
# Real conflict - handle failure
...
fi9. No Duplicate PR Prevention
Problem: The workflow doesn't check if a backport PR already exists before creating a new one.
Fix: Add this check before creating PR:
EXISTING=$(gh pr list --base "$BRANCH" --search "Backport of #$PR_NUMBER" --state open --json number --jq '.[0].number')
if [ -n "$EXISTING" ]; then
echo "Backport PR #$EXISTING already exists"
gh pr comment "$PR_NUMBER" --body "Backport to \`$BRANCH\` already exists: #$EXISTING"
exit 0
fi10. Insufficient Error Handling for gh pr create
Problem: If PR creation fails (e.g., network issue, validation error), the workflow fails silently after the push.
Fix:
if ! gh pr create \\
--base "$BRANCH" \\
--head "$BACKPORT_BRANCH" \\
--title "[backport] $PR_TITLE (to $BRANCH)" \\
--body "$BODY"; then
echo "::error::Failed to create PR"
# Optionally cleanup branch
git push origin --delete "$BACKPORT_BRANCH" || true
exit 1
fi11. No Workflow Summary
Recommendation: Add a step summary for better visibility in the GitHub UI:
if git cherry-pick -x "$SHA"; then
echo "## ✅ Backport Successful" >> $GITHUB_STEP_SUMMARY
echo "- **Source**: #$PR_NUMBER" >> $GITHUB_STEP_SUMMARY
echo "- **Target Branch**: $BRANCH" >> $GITHUB_STEP_SUMMARY
echo "- **Backport Branch**: $BACKPORT_BRANCH" >> $GITHUB_STEP_SUMMARY
else
echo "## ❌ Backport Failed" >> $GITHUB_STEP_SUMMARY
echo "- **Source**: #$PR_NUMBER" >> $GITHUB_STEP_SUMMARY
echo "- **Target Branch**: $BRANCH" >> $GITHUB_STEP_SUMMARY
echo "- **Error**: Cherry-pick failed due to conflicts" >> $GITHUB_STEP_SUMMARY
fiMinor Issues & Improvements
12. PCRE Dependency
It seems that grep -P (Perl regex) is not POSIX portable. While Ubuntu supports it, using sed is more portable:
# Instead of:
BRANCHES=$(echo "$COMMIT_MSG" | grep -oP '\[backport\s+\K[^\]]+' | tr '\n' ',' | sed 's/,$//')
# Use:
BRANCHES=$(echo "$COMMIT_MSG" | sed -n 's/.*\[backport[[:space:]]\+\([^]]*\)\].*/\1/p' | tr '\n' ',' | sed 's/,$//')13. Missing Concurrency Control
Add concurrency to prevent race conditions on rapid pushes:
concurrency:
group: backport-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false14. Regex Could Support More Formats
Let's support both [backport 1.1] and [backport: 1.1] for better UX:
BRANCHES=$(echo "$COMMIT_MSG" | sed -n 's/.*\[backport[:\s]\+\([^]]*\)\].*/\1/p')Documentation Review
The documentation is generally well-written and clear. Suggested improvements:
- Clarify that target branches must exist before triggering backport
- Document the limitation that
GITHUB_TOKENwon't trigger CI on the backport PR - Add examples of both commit message and comment triggers
- Mention the branch naming convention for generated backport branches
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created the Github App and installed in all OpenWISP repos.
I also added the organizations secrets needed for the github action:
The secrets are:
OPENWISP_BOT_APP_IDOPENWISP_BOT_PRIVATE_KEY
I updated the previous review text to match these.
Checklist
Reference to Existing Issue
Closes #501 .
Description of Changes
[backport X.Y]tags from squash merge commit messages/backport X.Ycomment as a manual fallback on merged PRs/backportcommand to organization membersScreenshots
N/A
Test PRs
[backport 1.1]in body, also see the workflow not being triggered by a non-member comment (PR)/backport 1.1on merged PR (PR)[backport 1.1]+[backport 1.2](PR)/backport 1.1on unmerged PR (PR)/backport 1.1on the PR after it got merged (PR)