This is an automated email from the ASF dual-hosted git repository.

Yicong-Huang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new ddb5c401a1 fix(ci): resolve merged PR robustly in Direct Backport Push 
(#4620)
ddb5c401a1 is described below

commit ddb5c401a18abb76b0c36adaf4cbbbc8db051549
Author: Yicong Huang <[email protected]>
AuthorDate: Fri May 1 14:52:01 2026 -0700

    fix(ci): resolve merged PR robustly in Direct Backport Push (#4620)
    
    ### What changes were proposed in this PR?
    
    The `Discover direct backport targets` step in
    `direct-backport-push.yml` previously relied solely on `GET
    /commits/{sha}/pulls`. That endpoint is backed by an async association
    index that lags for tens of seconds after a squash merge — see [run
    25233602734 attempt
    1](https://github.com/apache/texera/actions/runs/25233602734) for #4600,
    which logged `No merged pull request is associated with ...` and skipped
    `push-backports` even though the PR carried a `release/*` label.
    
    Resolve the PR in two stages, in order:
    
    1. **Parse the squash-merge commit message.** `.asf.yaml` forces squash
    merges with `PR_TITLE_AND_DESC`, so the first line of every merge commit
    on `main` ends with `(#NNNN)`. Regex-extracting that number and fetching
    the PR directly via `/pulls/{N}` bypasses the association index
    entirely. We also verify `pr.merged === true` before trusting the
    result.
    2. **Fall back to `/commits/{sha}/pulls` with retries.** If the message
    does not match (manual or unconventional commits), retry the original
    API call up to 5 times with exponential backoff (`0s, 2s, 4s, 8s, 16s` —
    ~30s worst case).
    
    ### Any related issues, documentation, discussions?
    
    Closes #4617
    
    ### How was this PR tested?
    
    YAML parses locally (`python3 -c "import yaml; yaml.safe_load(...)"`).
    The fix runs the same final code path as before — only the resolution
    method changes — so existing `push-backports` behavior is unchanged once
    a PR is found. Will be exercised the next time a PR with `release/*`
    labels merges to `main`.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.7)
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
 .github/workflows/direct-backport-push.yml | 65 ++++++++++++++++++++++++++----
 1 file changed, 57 insertions(+), 8 deletions(-)

diff --git a/.github/workflows/direct-backport-push.yml 
b/.github/workflows/direct-backport-push.yml
index 2bee5b53b6..37fbe04ce4 100644
--- a/.github/workflows/direct-backport-push.yml
+++ b/.github/workflows/direct-backport-push.yml
@@ -43,16 +43,65 @@ jobs:
             const sha = context.sha;
             const { owner, repo } = context.repo;
 
-            const response = await github.request(
-              "GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls",
-              {
-                owner,
-                repo,
-                commit_sha: sha,
+            // Strategy 1 (preferred): parse the squash-merge commit message.
+            // ASF .asf.yaml forces squash merges with PR_TITLE_AND_DESC, so 
the
+            // first line ends with "(#NNNN)". This is deterministic and avoids
+            // the commit↔PR association index, which can lag for tens of 
seconds
+            // after a merge.
+            async function resolvePrFromMessage() {
+              const message = context.payload?.head_commit?.message ?? "";
+              const firstLine = message.split("\n", 1)[0];
+              const match = firstLine.match(/\(#(\d+)\)\s*$/);
+              if (!match) {
+                core.info('Commit message does not end with "(#N)"; falling 
back to API.');
+                return null;
               }
-            );
+              const prNumber = Number(match[1]);
+              try {
+                const { data: pr } = await github.rest.pulls.get({
+                  owner,
+                  repo,
+                  pull_number: prNumber,
+                });
+                if (!pr.merged) {
+                  core.warning(`PR #${prNumber} extracted from commit message 
is not merged; falling back to API.`);
+                  return null;
+                }
+                core.info(`Resolved PR #${prNumber} from commit message.`);
+                return pr;
+              } catch (e) {
+                core.warning(`Failed to fetch PR #${prNumber}: ${e.message}. 
Falling back to API.`);
+                return null;
+              }
+            }
+
+            // Strategy 2 (fallback): GET /commits/{sha}/pulls with exponential
+            // backoff. 5 attempts at 0/2/4/8/16s — total worst case ~30s.
+            async function resolvePrFromApi() {
+              const backoffsMs = [0, 2000, 4000, 8000, 16000];
+              for (let i = 0; i < backoffsMs.length; i++) {
+                if (backoffsMs[i] > 0) {
+                  core.info(`Retrying commit→PR lookup in ${backoffsMs[i] / 
1000}s (attempt ${i + 1}/${backoffsMs.length}).`);
+                  await new Promise((resolve) => setTimeout(resolve, 
backoffsMs[i]));
+                }
+                const response = await github.request(
+                  "GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls",
+                  {
+                    owner,
+                    repo,
+                    commit_sha: sha,
+                  }
+                );
+                const pr = response.data.find((p) => p.merge_commit_sha === 
sha) ?? response.data[0];
+                if (pr) {
+                  core.info(`Resolved PR #${pr.number} from 
commits/${sha}/pulls on attempt ${i + 1}.`);
+                  return pr;
+                }
+              }
+              return null;
+            }
 
-            const pullRequest = response.data.find((pr) => pr.merge_commit_sha 
=== sha) ?? response.data[0];
+            const pullRequest = (await resolvePrFromMessage()) ?? (await 
resolvePrFromApi());
             if (!pullRequest) {
               core.info(`No merged pull request is associated with ${sha}.`);
               core.setOutput("pr_number", "");

Reply via email to