vigyasharma commented on code in PR #14738: URL: https://github.com/apache/lucene/pull/14738#discussion_r2118736897
########## .github/workflows/backport.yml: ########## @@ -0,0 +1,305 @@ +name: Backport PR + +on: + pull_request: + types: [closed] + +jobs: + backport: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + statuses: read + checks: read + actions: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Backport + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + MERGE_COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + set -e + + # Cache PR data early + echo "đ Fetching PR data..." + PR_DATA=$(gh pr view "$PR_NUMBER" --json title,body,labels,statusCheckRollup,commits) + PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') + PR_BODY=$(echo "$PR_DATA" | jq -r '.body // "No description provided."') + PR_LABELS=$(echo "$PR_DATA" | jq -r '.labels[].name | select(test("^[Bb]ackport/"; "i"))') + + # Function to create GitHub comment + create_comment() { + gh pr comment "$PR_NUMBER" --body "$1" + } + + # Function to add label safely + add_label() { + gh pr edit "$PR_NUMBER" --add-label "$1" 2>/dev/null || true + } + + # Check status checks once + echo "đ Checking PR status checks..." + FAILED_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[]? | select(.isRequired == true and (.state == "FAILURE" or .state == "ERROR")) | .state' 2>/dev/null || echo "") + + if [ -n "$FAILED_CHECKS" ]; then + echo "â Required status checks failed" + create_comment "â ī¸ **Automatic backports skipped** + + Some required status checks failed on this PR. Backports will not be created automatically. + + **Action required:** + 1. Fix failing tests or checks + 2. Manually create backport PRs after verification + 3. Or re-run this workflow after fixes are merged" + add_label "backport-failed" + exit 0 + fi + + # Early exit if no backport labels + if [ -z "$PR_LABELS" ]; then + echo "âšī¸ No backport labels found. Nothing to do." + exit 0 + fi + + echo "đ Found backport labels: $(echo "$PR_LABELS" | tr '\n' ' ')" + + # Batch create labels + echo "đˇī¸ Ensuring backport labels exist..." + gh label create "backport" --description "Automated backport workflow" --color "0366d6" 2>/dev/null || true & + gh label create "backport-conflict" --description "Backport had conflicts" --color "d73a49" 2>/dev/null || true & + gh label create "backport-failed" --description "Backport failed" --color "d73a49" 2>/dev/null || true & + wait # Wait for background label creation + + # Cache all remote branches once + echo "đŋ Caching branch information..." + ALL_BRANCHES=$(git branch -r | grep -v HEAD | sed 's|.*origin/||' | sort) + + # Define templates in order of preference + declare -a target_branch_templates=( + "branch_{{version_us}}" Review Comment: I like Stefan's suggestion of using the milestone label to find backport branch. We already have a bot to put that label based on `CHANGES` entry! In Lucene, a change meant for `10.3.0` will be back-ported into `branch_10x`. Generally, our back-port branches follow the naming scheme `branch_{MAJOR_VERSION}x`. Would that match any of the branch templates defined here? ########## .github/workflows/backport.yml: ########## @@ -0,0 +1,305 @@ +name: Backport PR + +on: + pull_request: + types: [closed] + +jobs: + backport: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + statuses: read + checks: read + actions: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Backport + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + MERGE_COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + set -e + + # Cache PR data early + echo "đ Fetching PR data..." + PR_DATA=$(gh pr view "$PR_NUMBER" --json title,body,labels,statusCheckRollup,commits) + PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') + PR_BODY=$(echo "$PR_DATA" | jq -r '.body // "No description provided."') + PR_LABELS=$(echo "$PR_DATA" | jq -r '.labels[].name | select(test("^[Bb]ackport/"; "i"))') + + # Function to create GitHub comment + create_comment() { + gh pr comment "$PR_NUMBER" --body "$1" + } + + # Function to add label safely + add_label() { + gh pr edit "$PR_NUMBER" --add-label "$1" 2>/dev/null || true + } + + # Check status checks once + echo "đ Checking PR status checks..." + FAILED_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[]? | select(.isRequired == true and (.state == "FAILURE" or .state == "ERROR")) | .state' 2>/dev/null || echo "") + + if [ -n "$FAILED_CHECKS" ]; then + echo "â Required status checks failed" + create_comment "â ī¸ **Automatic backports skipped** + + Some required status checks failed on this PR. Backports will not be created automatically. + + **Action required:** + 1. Fix failing tests or checks + 2. Manually create backport PRs after verification + 3. Or re-run this workflow after fixes are merged" + add_label "backport-failed" + exit 0 + fi + + # Early exit if no backport labels + if [ -z "$PR_LABELS" ]; then + echo "âšī¸ No backport labels found. Nothing to do." + exit 0 + fi + + echo "đ Found backport labels: $(echo "$PR_LABELS" | tr '\n' ' ')" + + # Batch create labels + echo "đˇī¸ Ensuring backport labels exist..." + gh label create "backport" --description "Automated backport workflow" --color "0366d6" 2>/dev/null || true & + gh label create "backport-conflict" --description "Backport had conflicts" --color "d73a49" 2>/dev/null || true & + gh label create "backport-failed" --description "Backport failed" --color "d73a49" 2>/dev/null || true & + wait # Wait for background label creation + + # Cache all remote branches once + echo "đŋ Caching branch information..." + ALL_BRANCHES=$(git branch -r | grep -v HEAD | sed 's|.*origin/||' | sort) + + # Define templates in order of preference + declare -a target_branch_templates=( + "branch_{{version_us}}" + "release/{{version}}" + "v{{version}}" + "{{version}}" + "{{version}}-stable" + ) + + # Optimized branch finder with caching + find_target_branch() { + local version="$1" + local version_us="${version//\./_}" + + for template in "${target_branch_templates[@]}"; do + local pattern="${template//\{\{version\}\}/$version}" + pattern="${pattern//\{\{version_us\}\}/$version_us}" + + # Use cached branches instead of repeated git calls + local target=$(echo "$ALL_BRANCHES" | grep -E "^${pattern}$" | head -1) + if [ -n "$target" ]; then + echo "$target" + return 0 + fi + done + + # Fallback with cached data + echo "$ALL_BRANCHES" | grep -E "${version}([^0-9]|$)" | head -1 || echo "" + } + + # Pre-check all target branches exist before processing + declare -a valid_backports=() + declare -a failed_versions=() + + echo "đ Pre-validating target branches..." + while IFS= read -r label; do + [ -z "$label" ] && continue + version=${label#[Bb]ackport/} + target=$(find_target_branch "$version") + + if [ -n "$target" ]; then + valid_backports+=("$version:$target") + echo "â $version -> $target" + else + failed_versions+=("$version") + echo "â $version -> NOT FOUND" + fi + done <<< "$PR_LABELS" + + # Report all missing branches at once + if [ ${#failed_versions[@]} -gt 0 ]; then + available_sample=$(echo "$ALL_BRANCHES" | head -5 | tr '\n' ', ' | sed 's/,$//') + failed_list="" + for version in "${failed_versions[@]}"; do + failed_list="${failed_list}- \`${version}\`"$'\n' + done + + create_comment "â **Backport failed for some versions - Missing target branches** + + Could not find target branches for: + $failed_list + + **Sample available branches:** $available_sample + + **Expected patterns:** + - \`branch_{version_with_underscores}\` (Lucene style) + - \`release/{version}\` + - \`v{version}\` + + Please create the missing branches or update the backport labels. + + **Note:** Valid backports will still be processed." + + add_label "backport-failed" + fi + + # Only exit if NO valid backports exist + if [ ${#valid_backports[@]} -eq 0 ]; then + echo "â No valid backports found. Exiting." + exit 1 + fi + + echo "â Processing ${#valid_backports[@]} valid backport(s)..." + + # Process valid backports + success_count=0 + failure_count=${#failed_versions[@]} + + for backport in "${valid_backports[@]}"; do + IFS=':' read -r version target <<< "$backport" + echo "đ Processing backport: $version -> $target" + + branch="backport-${PR_NUMBER}-to-${target}" + + # Clean checkout with error handling + git checkout "$DEFAULT_BRANCH" >/dev/null 2>&1 + git branch -D "$branch" 2>/dev/null || true + + if ! git checkout -b "$branch" "origin/$target" >/dev/null 2>&1; then + echo "â Failed to checkout origin/$target" + create_comment "â **Backport to \`$target\` failed** + + Could not create branch from \`origin/$target\`. Branch may have been deleted or is unreachable." + add_label "backport-failed" + failure_count=$((failure_count + 1)) + continue + fi + + # Check if PR already exists (early check) + existing_pr=$(gh pr list --head "$branch" --base "$target" --json number -q '.[0].number' 2>/dev/null || echo "") + if [ -n "$existing_pr" ]; then + echo "âšī¸ Backport PR already exists: #$existing_pr" + success_count=$((success_count + 1)) + continue + fi + + # Get individual commits from the PR instead of merge commit + echo "đ Getting PR commits..." + PR_COMMITS=$(echo "$PR_DATA" | jq -r '.commits[].oid' | tr '\n' ' ') + echo "đ PR commits: $PR_COMMITS" + + if [ -z "$PR_COMMITS" ]; then + echo "â No commits found in PR" + create_comment "â **Backport to \`$target\` failed** + + Could not find individual commits in PR #${PR_NUMBER}." + add_label "backport-failed" + failure_count=$((failure_count + 1)) + continue + fi + + # Cherry-pick each commit individually + cherry_pick_success=true + for commit in $PR_COMMITS; do + echo "đ Cherry-picking commit: $commit" + if ! git cherry-pick -x "$commit" 2>&1; then + echo "â Failed to cherry-pick $commit" + cherry_pick_success=false + break + fi + done Review Comment: Any reason to cherry-pick each commit individually instead of the merged commit. How PRs are merged in Lucene depends on what committers think make sense. For some of them, the full git history is retained, for others changes are squash merged. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org For additional commands, e-mail: issues-h...@lucene.apache.org