Skip to content

Bump the all group with 9 updates #159

Bump the all group with 9 updates

Bump the all group with 9 updates #159

Workflow file for this run

name: Backport and Forwardport PRs
permissions: read-all
on:
pull_request_target:
types: [closed]
branches:
- main
workflow_dispatch:
inputs:
pr_number:
description: 'Pull Request number to backport/forwardport'
required: true
type: number
dry_run:
description: 'Dry run mode - show what would happen without making changes'
required: false
type: boolean
default: true
jobs:
backport:
name: Backport/Forwardport PR
permissions:
pull-requests: write
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
with:
egress-policy: audit
- uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
- name: Configure Git
run: |
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Perform backport/forwardport
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
# Determine PR number and dry-run mode based on trigger type
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
PR_NUMBER="${{ inputs.pr_number }}"
DRY_RUN="${{ inputs.dry_run }}"
else
PR_NUMBER="${{ github.event.pull_request.number }}"
DRY_RUN="false"
fi
if [ "$DRY_RUN" = "true" ]; then
echo "🔍 DRY RUN MODE ENABLED - No changes will be made"
echo "=================================================="
fi
# Fetch PR details from API
PR_DATA=$(gh pr view "$PR_NUMBER" --json number,title,author,mergeCommit,state)
# Extract PR details
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
MERGE_COMMIT_SHA=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid // empty')
PR_STATE=$(echo "$PR_DATA" | jq -r '.state')
# Validate PR is merged
if [ "$PR_STATE" != "MERGED" ]; then
echo "Error: PR #$PR_NUMBER is not merged (state: $PR_STATE). Cannot backport unmerged PRs."
exit 1
fi
echo "Processing PR #$PR_NUMBER: $PR_TITLE"
echo "Author: $PR_AUTHOR"
echo "Merge commit: $MERGE_COMMIT_SHA"
# Get all labels from the PR
LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name')
# Extract backport branches
BACKPORT_BRANCHES=$(echo "$LABELS" | grep "^Backport to: " | sed 's/^Backport to: //' || true)
FORWARDPORT_BRANCHES=$(echo "$LABELS" | grep "^Forwardport to: " | sed 's/^Forwardport to: //' || true)
# Extract other labels (excluding backport/forwardport labels)
OTHER_LABELS=$(echo "$LABELS" | grep -v "^Backport to: " | grep -v "^Forwardport to: " | jq -R -s -c 'split("\n") | map(select(length > 0))' || echo '[]')
if [ -n "$BACKPORT_BRANCHES" ]; then
echo "Will backport PR #$PR_NUMBER to branches: $BACKPORT_BRANCHES"
fi
if [ -n "$FORWARDPORT_BRANCHES" ]; then
echo "Will forwardport PR #$PR_NUMBER to branches: $FORWARDPORT_BRANCHES"
fi
# Backport processing
if [ "$DRY_RUN" = "true" ]; then
echo "🔍 DRY RUN: Backport Processing"
echo "================================"
fi
# Process each backport branch
while IFS= read -r BRANCH; do
[ -z "$BRANCH" ] && continue
echo "Processing backport to branch: $BRANCH"
PORT_TYPE="backport"
NEW_BRANCH="${PORT_TYPE}-${PR_NUMBER}-to-${BRANCH}"
# Fetch the target branch
git fetch origin "$BRANCH:$BRANCH" || {
echo "Error: Failed to fetch branch $BRANCH"
continue
}
# Create and checkout new branch from target branch
git checkout -b "$NEW_BRANCH" "$BRANCH" || {
echo "Error: Failed to create branch $NEW_BRANCH"
continue
}
# Attempt cherry-pick
CONFLICT=false
if ! git cherry-pick -m 1 "$MERGE_COMMIT_SHA" 2>&1; then
# Check if there are conflicts
if git status | grep -q "Unmerged paths\|both modified"; then
echo "Conflicts detected during cherry-pick"
CONFLICT=true
# Stage all changes
git add .
# Commit with conflict message, setting bot as author
git commit --author='${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' -m "Cherry-pick $MERGE_COMMIT_SHA with conflicts" || {
echo "Error: Failed to commit conflicts"
git checkout main
git branch -D "$NEW_BRANCH" 2>/dev/null || true
continue
}
else
echo "Error: Cherry-pick failed with non-conflict error"
git cherry-pick --abort 2>/dev/null || true
git checkout main
git branch -D "$NEW_BRANCH" 2>/dev/null || true
continue
fi
else
# Cherry-pick succeeded, amend to update author
git commit --amend --no-edit --reset-author || {
echo "Error: Failed to amend commit"
git checkout main
git branch -D "$NEW_BRANCH" 2>/dev/null || true
continue
}
fi
# Push the new branch
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY RUN] Would push branch: $NEW_BRANCH"
echo " Command: git push -f origin $NEW_BRANCH"
NEW_PR_NUMBER="<dry-run>"
else
git push -f origin "$NEW_BRANCH" || {
echo "Error: Failed to push branch $NEW_BRANCH"
git checkout main
git branch -D "$NEW_BRANCH" 2>/dev/null || true
continue
}
# Create PR body
PR_BODY="## Description"$'\n'"This is a $PORT_TYPE of #${PR_NUMBER}"
# Determine if PR should be draft
DRAFT_FLAG=""
if [ "$CONFLICT" = true ]; then
DRAFT_FLAG="--draft"
fi
# Build labels JSON array from OTHER_LABELS and add port type specific labels
if [ "$CONFLICT" = true ]; then
LABELS_JSON=$(echo "$OTHER_LABELS" | jq -c '. + ["Merge Conflict", "Skip CI", "Backport"]')
else
LABELS_JSON=$(echo "$OTHER_LABELS" | jq -c '. + ["Backport"]')
fi
# Convert JSON array to comma-separated list for gh pr create --label
LABELS_LIST=$(echo "$LABELS_JSON" | jq -r 'join(",")')
echo " Creating pull request..."
# Create the pull request with labels (returns URL like https://github.com/owner/repo/pull/123)
PR_URL=$(gh pr create \
--title "[$BRANCH] $PR_TITLE (#$PR_NUMBER)" \
--body "$PR_BODY" \
--base "$BRANCH" \
--head "$NEW_BRANCH" \
--label "$LABELS_LIST" \
$DRAFT_FLAG 2>&1)
if [ $? -ne 0 ] || [ -z "$PR_URL" ]; then
echo "Error: Failed to create PR for branch $NEW_BRANCH"
echo "$PR_URL"
git checkout main
continue
fi
# Extract PR number from URL using gh pr view
NEW_PR_NUMBER=$(gh pr view "$PR_URL" --json number --jq '.number')
echo "Created backport PR #$NEW_PR_NUMBER ($PR_URL)"
fi
# Build labels JSON array for dry-run display
if [ "$CONFLICT" = true ]; then
LABELS_JSON=$(echo "$OTHER_LABELS" | jq -c '. + ["Merge Conflict", "Skip CI", "Backport"]')
else
LABELS_JSON=$(echo "$OTHER_LABELS" | jq -c '. + ["Backport"]')
fi
# Display PR information (for both dry-run and real mode)
if [ "$DRY_RUN" = "true" ]; then
echo ""
echo " [DRY RUN] Would create PR with:"
echo " --------------------------------"
echo " Title: [$BRANCH] $PR_TITLE (#$PR_NUMBER)"
echo " Body: ## Description"
echo " This is a $PORT_TYPE of #${PR_NUMBER}"
echo " Base: $BRANCH"
echo " Head: $NEW_BRANCH"
if [ "$CONFLICT" = true ]; then
echo " Draft: true (due to conflicts)"
else
echo " Draft: false"
fi
echo " Repository: ${{ github.repository }}"
echo ""
echo " [DRY RUN] Would add labels:"
echo "$LABELS_JSON" | jq -r '.[]' | while read -r label; do
echo " - $label"
done
fi
# Add conflict comment if there were conflicts
if [ "$CONFLICT" = true ]; then
CONFLICT_COMMENT="Hello @${PR_AUTHOR}, there are conflicts in this ${PORT_TYPE}."$'\n\n'
CONFLICT_COMMENT+="Please address them in order to merge this Pull Request. You can execute the snippet below to reset your branch and resolve the conflict manually."$'\n\n'
CONFLICT_COMMENT+="Make sure you replace \`origin\` by the name of the ${{ github.repository_owner }}/${{ github.event.repository.name }} remote"$'\n'
CONFLICT_COMMENT+="\`\`\`"$'\n'
CONFLICT_COMMENT+="git fetch --all"$'\n'
CONFLICT_COMMENT+="gh pr checkout ${NEW_PR_NUMBER}"$'\n'
CONFLICT_COMMENT+="git reset --hard origin/${BRANCH}"$'\n'
CONFLICT_COMMENT+="git cherry-pick -m 1 ${MERGE_COMMIT_SHA}"$'\n'
CONFLICT_COMMENT+="\`\`\`"
if [ "$DRY_RUN" = "true" ]; then
echo ""
echo " [DRY RUN] Would add conflict resolution comment:"
echo " ------------------------------------------------"
echo "$CONFLICT_COMMENT" | sed 's/^/ /'
else
if ! OUTPUT=$(gh pr comment "$NEW_PR_NUMBER" --body "$CONFLICT_COMMENT" 2>&1); then
echo "Warning: Could not add conflict resolution comment"
echo " Error: $OUTPUT"
fi
fi
fi
# Get reviewers from original PR and build JSON array
REVIEWERS_JSON=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/requested_reviewers" \
--jq '[.users[].login, .teams[].slug] + ["'"$PR_AUTHOR"'"]' 2>/dev/null || echo '["'"$PR_AUTHOR"'"]')
if [ "$DRY_RUN" = "true" ]; then
echo ""
echo " [DRY RUN] Would request reviews from:"
echo "$REVIEWERS_JSON" | jq -r '.[]' | while read -r reviewer; do
echo " - $reviewer"
done
else
# Convert JSON array to CSV and request reviewers
REVIEWERS_CSV=$(echo "$REVIEWERS_JSON" | jq -r '@csv')
if ! OUTPUT=$(gh pr edit "$NEW_PR_NUMBER" --add-reviewer "$REVIEWERS_CSV" 2>&1); then
echo "Note: Could not add some reviewers (may include PR author or have insufficient permissions)"
echo " Error: $OUTPUT"
fi
fi
# Return to main branch for next iteration
git checkout main
done <<< "$BACKPORT_BRANCHES"
# Forwardport processing
if [ "$DRY_RUN" = "true" ]; then
echo "🔍 DRY RUN: Forwardport Processing"
echo "==================================="
fi
# Process each forwardport branch
while IFS= read -r BRANCH; do
[ -z "$BRANCH" ] && continue
echo "Processing forwardport to branch: $BRANCH"
PORT_TYPE="forwardport"
NEW_BRANCH="${PORT_TYPE}-${PR_NUMBER}-to-${BRANCH}"
# Fetch the target branch
git fetch origin "$BRANCH:$BRANCH" || {
echo "Error: Failed to fetch branch $BRANCH"
continue
}
# Create and checkout new branch from target branch
git checkout -b "$NEW_BRANCH" "$BRANCH" || {
echo "Error: Failed to create branch $NEW_BRANCH"
continue
}
# Attempt cherry-pick
CONFLICT=false
if ! git cherry-pick -m 1 "$MERGE_COMMIT_SHA" 2>&1; then
# Check if there are conflicts
if git status | grep -q "Unmerged paths\|both modified"; then
echo "Conflicts detected during cherry-pick"
CONFLICT=true
# Stage all changes
git add .
# Commit with conflict message, setting bot as author
git commit --author='${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' -m "Cherry-pick $MERGE_COMMIT_SHA with conflicts" || {
echo "Error: Failed to commit conflicts"
git checkout main
git branch -D "$NEW_BRANCH" 2>/dev/null || true
continue
}
else
echo "Error: Cherry-pick failed with non-conflict error"
git cherry-pick --abort 2>/dev/null || true
git checkout main
git branch -D "$NEW_BRANCH" 2>/dev/null || true
continue
fi
else
# Cherry-pick succeeded, amend to update author
git commit --amend --no-edit --reset-author || {
echo "Error: Failed to amend commit"
git checkout main
git branch -D "$NEW_BRANCH" 2>/dev/null || true
continue
}
fi
# Push the new branch
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY RUN] Would push branch: $NEW_BRANCH"
echo " Command: git push -f origin $NEW_BRANCH"
NEW_PR_NUMBER="<dry-run>"
else
git push -f origin "$NEW_BRANCH" || {
echo "Error: Failed to push branch $NEW_BRANCH"
git checkout main
git branch -D "$NEW_BRANCH" 2>/dev/null || true
continue
}
# Create PR body
PR_BODY="## Description"$'\n'"This is a $PORT_TYPE of #${PR_NUMBER}"
# Determine if PR should be draft
DRAFT_FLAG=""
if [ "$CONFLICT" = true ]; then
DRAFT_FLAG="--draft"
fi
# Build labels JSON array from OTHER_LABELS and add port type specific labels
if [ "$CONFLICT" = true ]; then
LABELS_JSON=$(echo "$OTHER_LABELS" | jq -c '. + ["Merge Conflict", "Skip CI", "Forwardport"]')
else
LABELS_JSON=$(echo "$OTHER_LABELS" | jq -c '. + ["Forwardport"]')
fi
# Convert JSON array to comma-separated list for gh pr create --label
LABELS_LIST=$(echo "$LABELS_JSON" | jq -r 'join(",")')
echo " Creating pull request..."
# Create the pull request with labels (returns URL like https://github.com/owner/repo/pull/123)
PR_URL=$(gh pr create \
--title "[$BRANCH] $PR_TITLE (#$PR_NUMBER)" \
--body "$PR_BODY" \
--base "$BRANCH" \
--head "$NEW_BRANCH" \
--label "$LABELS_LIST" \
$DRAFT_FLAG 2>&1)
if [ $? -ne 0 ] || [ -z "$PR_URL" ]; then
echo "Error: Failed to create PR for branch $NEW_BRANCH"
echo "$PR_URL"
git checkout main
continue
fi
# Extract PR number from URL using gh pr view
NEW_PR_NUMBER=$(gh pr view "$PR_URL" --json number --jq '.number')
echo "Created forwardport PR #$NEW_PR_NUMBER ($PR_URL)"
fi
# Build labels JSON array for dry-run display
if [ "$CONFLICT" = true ]; then
LABELS_JSON=$(echo "$OTHER_LABELS" | jq -c '. + ["Merge Conflict", "Skip CI", "Forwardport"]')
else
LABELS_JSON=$(echo "$OTHER_LABELS" | jq -c '. + ["Forwardport"]')
fi
# Display PR information (for both dry-run and real mode)
if [ "$DRY_RUN" = "true" ]; then
echo ""
echo " [DRY RUN] Would create PR with:"
echo " --------------------------------"
echo " Title: [$BRANCH] $PR_TITLE (#$PR_NUMBER)"
echo " Body: ## Description"
echo " This is a $PORT_TYPE of #${PR_NUMBER}"
echo " Base: $BRANCH"
echo " Head: $NEW_BRANCH"
if [ "$CONFLICT" = true ]; then
echo " Draft: true (due to conflicts)"
else
echo " Draft: false"
fi
echo " Repository: ${{ github.repository }}"
echo ""
echo " [DRY RUN] Would add labels:"
echo "$LABELS_JSON" | jq -r '.[]' | while read -r label; do
echo " - $label"
done
fi
# Add conflict comment if there were conflicts
if [ "$CONFLICT" = true ]; then
CONFLICT_COMMENT="Hello @${PR_AUTHOR}, there are conflicts in this ${PORT_TYPE}."$'\n\n'
CONFLICT_COMMENT+="Please address them in order to merge this Pull Request. You can execute the snippet below to reset your branch and resolve the conflict manually."$'\n\n'
CONFLICT_COMMENT+="Make sure you replace \`origin\` by the name of the ${{ github.repository_owner }}/${{ github.event.repository.name }} remote"$'\n'
CONFLICT_COMMENT+="\`\`\`"$'\n'
CONFLICT_COMMENT+="git fetch --all"$'\n'
CONFLICT_COMMENT+="gh pr checkout ${NEW_PR_NUMBER} -R ${{ github.repository }}"$'\n'
CONFLICT_COMMENT+="git reset --hard origin/${BRANCH}"$'\n'
CONFLICT_COMMENT+="git cherry-pick -m 1 ${MERGE_COMMIT_SHA}"$'\n'
CONFLICT_COMMENT+="\`\`\`"
if [ "$DRY_RUN" = "true" ]; then
echo ""
echo " [DRY RUN] Would add conflict resolution comment:"
echo " ------------------------------------------------"
echo "$CONFLICT_COMMENT" | sed 's/^/ /'
else
if ! OUTPUT=$(gh pr comment "$NEW_PR_NUMBER" --body "$CONFLICT_COMMENT" 2>&1); then
echo "Warning: Could not add conflict resolution comment"
echo " Error: $OUTPUT"
fi
fi
fi
# Get reviewers from original PR and build JSON array
REVIEWERS_JSON=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/requested_reviewers" \
--jq '[.users[].login, .teams[].slug] + ["'"$PR_AUTHOR"'"]' 2>/dev/null || echo '["'"$PR_AUTHOR"'"]')
if [ "$DRY_RUN" = "true" ]; then
echo ""
echo " [DRY RUN] Would request reviews from:"
echo "$REVIEWERS_JSON" | jq -r '.[]' | while read -r reviewer; do
echo " - $reviewer"
done
else
# Convert JSON array to CSV and request reviewers
REVIEWERS_CSV=$(echo "$REVIEWERS_JSON" | jq -r '@csv')
if ! OUTPUT=$(gh pr edit "$NEW_PR_NUMBER" --add-reviewer "$REVIEWERS_CSV" 2>&1); then
echo "Note: Could not add some reviewers (may include PR author or have insufficient permissions)"
echo " Error: $OUTPUT"
fi
fi
# Return to main branch for next iteration
git checkout main
done <<< "$FORWARDPORT_BRANCHES"