From 18386d49aea722cd44f1c9730f42b1fb40cca282 Mon Sep 17 00:00:00 2001 From: pushpit kamboj Date: Tue, 17 Feb 2026 01:28:12 +0530 Subject: [PATCH 01/11] updated trigger --- .github/workflows/bot-workflow.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bot-workflow.yml b/.github/workflows/bot-workflow.yml index 700caf1..5570d6d 100644 --- a/.github/workflows/bot-workflow.yml +++ b/.github/workflows/bot-workflow.yml @@ -1,9 +1,8 @@ name: Changelog Bot on: - # Trigger when a PR review is submitted with approval - pull_request_review: - types: [submitted] + issue_comment: + types: [created] jobs: changelog: From 2ec88133043de707a5198f25188aa3a5e1ece8d4 Mon Sep 17 00:00:00 2001 From: atif09 Date: Tue, 17 Feb 2026 01:57:21 +0530 Subject: [PATCH 02/11] add backport workflow --- .github/workflows/backport.yml | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/backport.yml diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 0000000..a0f90bf --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,37 @@ +name: Backport fixes to stable branch +on: + push: + branches: + - master + - main + issue_comment: + types: [created] +concurrency: + group: backport-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false +permissions: + contents: write + pull-requests: write +jobs: + backport-on-push: + if: github.event_name == 'push' + uses: atif09/openwisp-utils/.github/workflows/reusable-backport.yml@issues/501-automate-backporting-fixes + 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: atif09/openwisp-utils/.github/workflows/reusable-backport.yml@issues/501-automate-backporting-fixes + 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 }} From ec22778e89a7f94b00b82e923c5d96806c35077d Mon Sep 17 00:00:00 2001 From: Atif <39148554+atif09@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:11:53 +0530 Subject: [PATCH 03/11] test: backport workflow (#11) [backport 1.1] [backport 1.2] --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +test From 98d44545d2435269bcab4514154d2617e3b4e641 Mon Sep 17 00:00:00 2001 From: Atif <39148554+atif09@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:20:30 +0530 Subject: [PATCH 04/11] test: single backport tag (#14) [backport 1.1] --- README.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..07d1e31 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +single tag test From 075547365279e85dad457748e3a452a218a25aaf Mon Sep 17 00:00:00 2001 From: Atif <39148554+atif09@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:27:45 +0530 Subject: [PATCH 05/11] test: conflict backport [backport 1.1] --- conflict_test.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 conflict_test.txt diff --git a/conflict_test.txt b/conflict_test.txt new file mode 100644 index 0000000..b5803c9 --- /dev/null +++ b/conflict_test.txt @@ -0,0 +1 @@ +different content on main From 0454708d53cc40c9796976364c58498753f0f675 Mon Sep 17 00:00:00 2001 From: Atif <39148554+atif09@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:36:31 +0530 Subject: [PATCH 06/11] test: conflict backport (#18) From 4174fc615dc79c8e20bf98ef4615b1872ab228b6 Mon Sep 17 00:00:00 2001 From: atif09 Date: Tue, 17 Feb 2026 03:16:04 +0530 Subject: [PATCH 07/11] Allow collaborators to trigger backport --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index a0f90bf..a1f5c4a 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -26,7 +26,7 @@ jobs: github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.issue.state == 'closed' && - contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association) && + contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), github.event.comment.author_association) && startsWith(github.event.comment.body, '/backport') uses: atif09/openwisp-utils/.github/workflows/reusable-backport.yml@issues/501-automate-backporting-fixes with: From 0d498dead3a4cc01bc48c710298d06677bbdd2d8 Mon Sep 17 00:00:00 2001 From: atif09 Date: Tue, 17 Feb 2026 03:27:03 +0530 Subject: [PATCH 08/11] stable-specific change --- conflict-test.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 conflict-test.txt diff --git a/conflict-test.txt b/conflict-test.txt new file mode 100644 index 0000000..fcf084f --- /dev/null +++ b/conflict-test.txt @@ -0,0 +1 @@ +stable version From a506506c744edd5edc835f1595abb7502b1b390c Mon Sep 17 00:00:00 2001 From: atif09 Date: Thu, 19 Feb 2026 00:52:02 +0530 Subject: [PATCH 09/11] fix: test empty cherry-pick scenario --- test-backport.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test-backport.txt diff --git a/test-backport.txt b/test-backport.txt new file mode 100644 index 0000000..009a88e --- /dev/null +++ b/test-backport.txt @@ -0,0 +1 @@ +fix: scenario-1-test-1771442522 From b258dd2064f5456be2563d225e77358107c58610 Mon Sep 17 00:00:00 2001 From: atif09 Date: Thu, 19 Feb 2026 01:08:45 +0530 Subject: [PATCH 10/11] ci: update backport workflows from openwisp-utils --- .github/workflows/backport.yml | 12 +- .github/workflows/reusable-backport.yml | 171 ++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/reusable-backport.yml diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index a1f5c4a..6f21567 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,4 +1,5 @@ name: Backport fixes to stable branch + on: push: branches: @@ -6,29 +7,34 @@ on: - main issue_comment: types: [created] + concurrency: group: backport-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false + permissions: contents: write pull-requests: write + jobs: backport-on-push: if: github.event_name == 'push' - uses: atif09/openwisp-utils/.github/workflows/reusable-backport.yml@issues/501-automate-backporting-fixes + 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.pull_request.merged_at != null && github.event.issue.state == 'closed' && - contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), github.event.comment.author_association) && + contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association) && startsWith(github.event.comment.body, '/backport') - uses: atif09/openwisp-utils/.github/workflows/reusable-backport.yml@issues/501-automate-backporting-fixes + uses: ./.github/workflows/reusable-backport.yml with: pr_number: ${{ github.event.issue.number }} comment_body: ${{ github.event.comment.body }} diff --git a/.github/workflows/reusable-backport.yml b/.github/workflows/reusable-backport.yml new file mode 100644 index 0000000..102fef8 --- /dev/null +++ b/.github/workflows/reusable-backport.yml @@ -0,0 +1,171 @@ +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[:[:space:]]\+\([^]]*\)\].*/\1/p' | tr '\n' ',' | sed 's/,$//') + else + PR_DATA=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json state,mergeCommit) + PR_STATE=$(echo "$PR_DATA" | jq -r '.state') + if [ "$PR_STATE" != "MERGED" ]; then + echo "PR #$PR_NUMBER is not merged, skipping" + exit 0 + fi + SHA=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid') + BRANCHES=$(echo "$COMMENT_BODY" | sed -n 's|.*/backport[[:space:]]\+\([^[:space:]]*\).*|\1|p' | 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@v4 + 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: | + if [ -z "$SHA" ]; then + echo "No SHA to cherry-pick, skipping" + exit 0 + fi + BRANCH=$(echo "$BRANCH" | sed 's/[^a-zA-Z0-9._/-]//g') + if echo "$BRANCH" | grep -q '\.\.'; then + echo "::error::Invalid branch name '$BRANCH'" + exit 1 + fi + 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 + 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 + 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\`." + EXISTING=$(gh pr list --base "$BRANCH" --search "[backport] #${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 + fi + fi + BACKPORT_BRANCH="${BACKPORT_BRANCH}-$(date +%s)" + git checkout -b "$BACKPORT_BRANCH" "origin/$BRANCH" + git cherry-pick -x "$SHA" + CHERRY_PICK_STATUS=$? + if [ $CHERRY_PICK_STATUS -eq 0 ]; then + if ! git push origin "$BACKPORT_BRANCH"; then + echo "::error::Failed to push backport branch '$BACKPORT_BRANCH'" + exit 1 + fi + if ! gh pr create \ + --base "$BRANCH" \ + --head "$BACKPORT_BRANCH" \ + --title "[backport] #${PR_NUMBER}: $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 "- **Source**: #$PR_NUMBER" >> $GITHUB_STEP_SUMMARY + echo "- **Target Branch**: $BRANCH" >> $GITHUB_STEP_SUMMARY + echo "- **Backport Branch**: $BACKPORT_BRANCH" >> $GITHUB_STEP_SUMMARY + elif [ $CHERRY_PICK_STATUS -eq 1 ] && git diff --cached --quiet; then + echo "Commit $SHA appears to already be in $BRANCH (empty cherry-pick)" + git cherry-pick --abort || true + exit 0 + 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 "- **Source**: #$PR_NUMBER" >> $GITHUB_STEP_SUMMARY + echo "- **Target Branch**: $BRANCH" >> $GITHUB_STEP_SUMMARY + echo "- **Error**: Cherry-pick failed due to conflicts" >> $GITHUB_STEP_SUMMARY + exit 1 + fi From 032f07cda9366fc90a7ba17e76ed984c617375c3 Mon Sep 17 00:00:00 2001 From: atif09 Date: Thu, 19 Feb 2026 01:31:48 +0530 Subject: [PATCH 11/11] fix: test cherry-pick exit code capture v2 --- test-fix.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test-fix.txt diff --git a/test-fix.txt b/test-fix.txt new file mode 100644 index 0000000..64ca10e --- /dev/null +++ b/test-fix.txt @@ -0,0 +1 @@ +test-1771444908