fix(bulk_writer): propagate sync commit failures #527
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Backport | |
| on: | |
| pull_request_target: | |
| types: | |
| - closed | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'PR number' | |
| required: true | |
| type: string | |
| target_branch: | |
| description: 'Target branch' | |
| required: true | |
| type: string | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.pr_number || github.run_id }} | |
| cancel-in-progress: true | |
| jobs: | |
| backport: | |
| timeout-minutes: 30 | |
| if: > | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request_target' && | |
| github.event.pull_request.base.ref == 'master' && | |
| github.event.pull_request.merged == true && | |
| ((github.event.action == 'closed') || | |
| (github.event.action == 'labeled' && startsWith(github.event.label.name, 'backport-to-')))) | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Configure Git | |
| env: | |
| GH_TOKEN: ${{ secrets.PYMILVUS_BOT_TOKEN }} | |
| run: | | |
| git config user.name "pymilvus-bot" | |
| git config user.email "pymilvus-bot@users.noreply.github.com" | |
| git remote add fork "https://x-access-token:${GH_TOKEN}@github.com/pymilvus-bot/pymilvus.git" | |
| - name: Execute Backport | |
| id: execute-backport | |
| env: | |
| GH_TOKEN: ${{ secrets.PYMILVUS_BOT_TOKEN }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_PR_NUMBER: ${{ inputs.pr_number }} | |
| INPUT_TARGET_BRANCH: ${{ inputs.target_branch }} | |
| TRIGGER_USER: ${{ github.actor }} | |
| run: | | |
| # Helper function to write summary | |
| write_summary() { | |
| local status="$1" | |
| local title="$2" | |
| local details="$3" | |
| echo "## $status $title" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "$details" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| } | |
| if [ "$EVENT_NAME" == "workflow_dispatch" ]; then | |
| PR_NUMBER="$INPUT_PR_NUMBER" | |
| IS_MANUAL="true" | |
| if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | |
| echo "::error::Invalid PR number: $PR_NUMBER (must be a positive integer)" | |
| write_summary "❌" "Backport Failed" "**Reason:** Invalid PR number \`$PR_NUMBER\` (must be a positive integer)" | |
| exit 1 | |
| fi | |
| if ! [[ "$INPUT_TARGET_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then | |
| echo "::error::Invalid branch name: $INPUT_TARGET_BRANCH (contains invalid characters)" | |
| write_summary "❌" "Backport Failed" "**Reason:** Invalid branch name \`$INPUT_TARGET_BRANCH\` (contains invalid characters)" | |
| exit 1 | |
| fi | |
| else | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| IS_MANUAL="false" | |
| fi | |
| echo "Starting Backport for PR #$PR_NUMBER (Triggered by $TRIGGER_USER)" | |
| if ! PR_DATA=$(gh pr view "$PR_NUMBER" --json mergeCommit,title,files,author,state,labels,body,assignees --jq . 2>&1); then | |
| echo "::error::Failed to fetch PR data: $PR_DATA" | |
| write_summary "❌" "Backport Failed" "**PR:** #$PR_NUMBER\n\n**Reason:** Failed to fetch PR data\n\n**Error:** \`$PR_DATA\`" | |
| exit 1 | |
| fi | |
| MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid // empty') | |
| PR_TITLE=$(echo "$PR_DATA" | jq -r .title) | |
| PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login // "ghost"') | |
| PR_STATE=$(echo "$PR_DATA" | jq -r .state) | |
| CHANGED_FILES=$(echo "$PR_DATA" | jq -r '.files[].path // empty') | |
| if [ -z "$PR_AUTHOR" ] || [ "$PR_AUTHOR" == "null" ]; then | |
| PR_AUTHOR="ghost" | |
| fi | |
| PR_TITLE_CLEAN=$(echo "$PR_TITLE" | tr -d '\n\r' | sed 's/["`\\]/ /g' | head -c 200) | |
| if [ -z "$PR_TITLE_CLEAN" ]; then | |
| PR_TITLE_CLEAN="Backport PR #$PR_NUMBER" | |
| fi | |
| ASSIGNEE_LOGINS=$(echo "$PR_DATA" | jq -r '.assignees[].login // empty' 2>/dev/null | sed 's/^/@/' | tr '\n' ' ' | xargs) | |
| if [ -n "$ASSIGNEE_LOGINS" ]; then | |
| CC_LINE="(cc $ASSIGNEE_LOGINS)" | |
| else | |
| CC_LINE="(cc @$TRIGGER_USER)" | |
| fi | |
| if [ "$PR_STATE" != "MERGED" ]; then | |
| echo "::error::PR is not merged." | |
| write_summary "❌" "Backport Failed" "**PR:** #$PR_NUMBER\n\n**Reason:** PR is not merged yet" | |
| gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', PR #'"$PR_NUMBER"$' is not merged yet.\n\n'"$CC_LINE"'' | |
| exit 0 | |
| fi | |
| if [ -z "$MERGE_COMMIT" ] || [ "$MERGE_COMMIT" == "null" ]; then | |
| echo "::error::Cannot find merge commit for PR #$PR_NUMBER" | |
| write_summary "❌" "Backport Failed" "**PR:** #$PR_NUMBER\n\n**Reason:** Cannot determine merge commit for this PR" | |
| gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', cannot determine merge commit for this PR.\n\n'"$CC_LINE"'' | |
| exit 0 | |
| fi | |
| TARGET_BRANCHES="" | |
| LABELED_BRANCHES=$(echo "$PR_DATA" | jq -r '.labels[].name' | grep '^backport-to-' | sed 's/^backport-to-//' || true) | |
| if [ "$IS_MANUAL" == "true" ]; then | |
| # Check if the target branch has corresponding label | |
| if echo "$LABELED_BRANCHES" | grep -qx "$INPUT_TARGET_BRANCH"; then | |
| TARGET_BRANCHES="$INPUT_TARGET_BRANCH" | |
| else | |
| echo "::error::Target branch '$INPUT_TARGET_BRANCH' is not labeled with 'backport-to-$INPUT_TARGET_BRANCH'" | |
| write_summary "❌" "Backport Failed" "**PR:** #$PR_NUMBER\n\n**Target Branch:** \`$INPUT_TARGET_BRANCH\`\n\n**Reason:** Missing label \`backport-to-$INPUT_TARGET_BRANCH\`\n\n**Available Labels:** \`$LABELED_BRANCHES\`" | |
| gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', target branch `'"$INPUT_TARGET_BRANCH"$'` is not in the backport labels. Please add label `backport-to-'"$INPUT_TARGET_BRANCH"$'` first.\n\n'"$CC_LINE"'' | |
| exit 0 | |
| fi | |
| else | |
| TARGET_BRANCHES="$LABELED_BRANCHES" | |
| fi | |
| if [ -z "$TARGET_BRANCHES" ]; then | |
| echo "No target branches found." | |
| write_summary "ℹ️" "No Backport Needed" "**PR:** #$PR_NUMBER\n\n**Reason:** No \`backport-to-*\` labels found on this PR" | |
| exit 0 | |
| fi | |
| if [ -n "$CHANGED_FILES" ] && echo "$CHANGED_FILES" | grep -q "^pymilvus/grpc_gen/"; then | |
| echo "::notice::Skipping restricted paths." | |
| write_summary "⚠️" "Backport Skipped" "**PR:** #$PR_NUMBER\n\n**Reason:** This PR modifies \`pymilvus/grpc_gen/\`. Please backport manually." | |
| gh pr comment "$PR_NUMBER" --body $'⚠️ **Backport Skipped**\nHi @'"$PR_AUTHOR"$', this PR modifies `pymilvus/grpc_gen/`. Please backport manually.\n\n'"$CC_LINE"'' | |
| exit 0 | |
| fi | |
| BACKPORT_SUCCESS="false" | |
| BACKPORT_COUNT=0 | |
| SUMMARY_RESULTS="" | |
| while IFS= read -r TARGET_BRANCH; do | |
| [ -z "$TARGET_BRANCH" ] && continue | |
| TARGET_BRANCH=$(echo "$TARGET_BRANCH" | xargs) | |
| echo "::group::Processing backport to $TARGET_BRANCH" | |
| if ! git ls-remote --exit-code --heads origin "$TARGET_BRANCH" > /dev/null 2>&1; then | |
| echo "::error::Target branch '$TARGET_BRANCH' does not exist." | |
| SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Branch does not exist |" | |
| gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', target branch `'"$TARGET_BRANCH"$'` does not exist.\n\n'"$CC_LINE"'' | |
| echo "::endgroup::" | |
| continue | |
| fi | |
| echo "Checking for existing backport PR..." | |
| EXISTING_PR=$(gh pr list \ | |
| --base "$TARGET_BRANCH" \ | |
| --state all \ | |
| --search "in:title [Backport $TARGET_BRANCH]" \ | |
| --json number,title,state,mergedAt \ | |
| --jq ".[] | select(.title | contains(\"#$PR_NUMBER\")) | select(.state == \"OPEN\" or .mergedAt != null) | .number" \ | |
| | head -n 1) | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "::notice::Backport PR already exists: #$EXISTING_PR" | |
| SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ℹ️ Skipped | Already exists: #$EXISTING_PR |" | |
| gh pr comment "$PR_NUMBER" --body $'ℹ️ **Backport Already Exists**\nHi @'"$PR_AUTHOR"$', a backport PR for `'"$TARGET_BRANCH"$'` already exists: #'"$EXISTING_PR"$'\n\n'"$CC_LINE"'' | |
| echo "::endgroup::" | |
| continue | |
| fi | |
| git reset --hard HEAD | |
| git clean -fdx | |
| TARGET_BRANCH_SHORT="${TARGET_BRANCH:0:100}" | |
| BRANCH_SUFFIX="$(date +%s)-$RANDOM" | |
| BACKPORT_BRANCH="backport-$PR_NUMBER-to-$TARGET_BRANCH_SHORT-$BRANCH_SUFFIX" | |
| BACKPORT_BRANCH="${BACKPORT_BRANCH:0:250}" | |
| if ! git fetch origin "$TARGET_BRANCH"; then | |
| echo "::error::Failed to fetch $TARGET_BRANCH" | |
| SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Failed to fetch branch |" | |
| gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', failed to fetch target branch `'"$TARGET_BRANCH"$'`.\n\n'"$CC_LINE"'' | |
| echo "::endgroup::" | |
| continue | |
| fi | |
| git checkout -b "$BACKPORT_BRANCH" "origin/$TARGET_BRANCH" | |
| echo "Attempting cherry-pick of $MERGE_COMMIT..." | |
| if git cherry-pick -x -s "$MERGE_COMMIT" 2>&1; then | |
| echo "Cherry-pick successful." | |
| else | |
| echo "Standard cherry-pick failed, trying with -m 1..." | |
| git cherry-pick --abort 2>/dev/null || true | |
| if git cherry-pick -x -s -m 1 "$MERGE_COMMIT" 2>&1; then | |
| echo "Cherry-pick with -m 1 successful." | |
| else | |
| echo "::error::Cherry-pick failed due to conflicts." | |
| git cherry-pick --abort 2>/dev/null || true | |
| SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Merge conflicts - manual backport required |" | |
| gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', I could not cherry-pick this to `'"$TARGET_BRANCH"$'` due to **merge conflicts**. Please backport manually.\n\n'"$CC_LINE"'' | |
| echo "::endgroup::" | |
| continue | |
| fi | |
| fi | |
| if ! git push fork "$BACKPORT_BRANCH"; then | |
| echo "::error::Failed to push branch $BACKPORT_BRANCH" | |
| SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Failed to push branch |" | |
| gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', failed to push backport branch for `'"$TARGET_BRANCH"$'`.\n\n'"$CC_LINE"'' | |
| echo "::endgroup::" | |
| continue | |
| fi | |
| PR_BODY="Backport of #$PR_NUMBER to \`$TARGET_BRANCH\`." | |
| ASSIGNEE_ARG="" | |
| if [ "$IS_MANUAL" == "true" ]; then | |
| PR_BODY="Manual backport of #$PR_NUMBER to \`$TARGET_BRANCH\`." | |
| ASSIGNEE_ARG="--assignee $TRIGGER_USER" | |
| fi | |
| if NEW_PR_URL=$(gh pr create \ | |
| --repo "${{ github.repository }}" \ | |
| --base "$TARGET_BRANCH" \ | |
| --head "pymilvus-bot:$BACKPORT_BRANCH" \ | |
| --title "[Backport $TARGET_BRANCH] $PR_TITLE_CLEAN (#$PR_NUMBER)" \ | |
| --body "$PR_BODY" \ | |
| --label "backport" \ | |
| $ASSIGNEE_ARG 2>&1); then | |
| gh pr comment "$PR_NUMBER" --body $'✅ **Backport Created**\nHi @'"$PR_AUTHOR"$', Backport PR for `'"$TARGET_BRANCH"$'` has been created: '"$NEW_PR_URL"$'\n\n'"$CC_LINE"'' | |
| echo "Backport PR created: $NEW_PR_URL" | |
| SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ✅ Success | $NEW_PR_URL |" | |
| BACKPORT_SUCCESS="true" | |
| BACKPORT_COUNT=$((BACKPORT_COUNT + 1)) | |
| else | |
| echo "::error::Failed to create backport PR." | |
| SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Failed to create PR |" | |
| git push fork --delete "$BACKPORT_BRANCH" 2>/dev/null || true | |
| gh pr comment "$PR_NUMBER" --body $'❌ **Backport PR Creation Failed**\nHi @'"$PR_AUTHOR"$', Failed to create backport PR for `'"$TARGET_BRANCH"$'`.\n\nError: '"$NEW_PR_URL"$'\n\n'"$CC_LINE"'' | |
| fi | |
| echo "::endgroup::" | |
| done < <(echo "$TARGET_BRANCHES") | |
| # Write final summary | |
| if [ "$BACKPORT_SUCCESS" == "true" ]; then | |
| echo "## ✅ Backport Completed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "## ❌ Backport Failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**PR:** #$PR_NUMBER" >> $GITHUB_STEP_SUMMARY | |
| echo "**Title:** $PR_TITLE_CLEAN" >> $GITHUB_STEP_SUMMARY | |
| echo "**Triggered by:** @$TRIGGER_USER" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Branch | Status | Details |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|--------|---------|" >> $GITHUB_STEP_SUMMARY | |
| echo -e "$SUMMARY_RESULTS" >> $GITHUB_STEP_SUMMARY | |
| echo "success=$BACKPORT_SUCCESS" >> $GITHUB_OUTPUT | |
| echo "count=$BACKPORT_COUNT" >> $GITHUB_OUTPUT | |
| echo "Backport process completed. Success: $BACKPORT_SUCCESS, Count: $BACKPORT_COUNT" | |
| - name: Label Original PR | |
| if: steps.execute-backport.outputs.success == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.PYMILVUS_BOT_TOKEN }} | |
| INPUT_PR_NUMBER: ${{ inputs.pr_number }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| run: | | |
| if [ "$EVENT_NAME" == "workflow_dispatch" ]; then | |
| PR_NUMBER="$INPUT_PR_NUMBER" | |
| else | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| fi | |
| gh pr edit "$PR_NUMBER" --add-label "backported" || echo "::warning::Failed to add 'backported' label" |