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

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 4381d999f042ba22bf95a06126b06858e21a5c86
Author: Marco Casaroli <[email protected]>
AuthorDate: Mon Oct 19 10:21:41 2026 +0200

    .github/workflows: Skip depends-on PRs that are already merged.
    
    When a depends-on PR is merged, its commits are in master. Build and
    the memory report then cherry-pick the PR head again, the cherry-pick
    is empty or conflicts, and Fetch-Source fails. The author must remove
    the depends-on line and start CI again.
    
    Read the state of each dependency with the GitHub API. If the PR is
    merged and its merge commit is in the checkout, skip the PR. Apply
    open PRs, PRs merged into a different branch, and PRs whose merge
    commit is not in the checkout as before. If the API call fails, apply
    the dependency as before.
    
    A PR that is closed without merge is now an error: CI does not apply
    it, and the step fails with "closed without merge; remove it from
    depends-on or reopen it". Before, CI applied such a PR silently.
    
    The report marks a skipped dependency with "merged": true. A failure
    report marks a closed dependency with "closed": true and the error
    code dependency_closed. The comment shows "already merged, skipped",
    "closed without merge", and the reason for the failure.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 .github/workflows/build.yml              | 37 ++++++++++++++++++++++++++++++--
 .github/workflows/depends-on-comment.yml | 14 ++++++------
 .github/workflows/membrowse-report.yml   | 22 +++++++++++++++++++
 3 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 84f4a9ffc07..b7c97ffeecd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -146,6 +146,8 @@ jobs:
       - name: Apply depends-on PRs
         if: ${{ steps.gittargets.outputs.depends_on != '' }}
         shell: bash
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
           set -uo pipefail
 
@@ -153,9 +155,10 @@ jobs:
           git config --global user.name "github-actions"
 
           # Pass only fixed error codes to the trusted comment workflow.
+          # $3 and $4 name a dependency that is closed without merge.
           mark_failed() {
           echo "::error::could not apply $1 ($2)"
-          python3 - "$2" <<'PY'
+          python3 - "$2" "${3:-}" "${4:-}" <<'PY'
           import json, sys
           p = "depends-on-report/result.json"
           try:
@@ -164,6 +167,9 @@ jobs:
               d = {"version": 1, "pr_number": None, "head_sha": None, 
"dependencies": [], "warnings": []}
           d["status"] = "failed"
           d["error_code"] = sys.argv[1]
+          for dep in d.get("dependencies", []):
+              if [dep.get("repo"), str(dep.get("number"))] == sys.argv[2:4]:
+                  dep["closed"] = True
           open(p, "w").write(json.dumps(d))
           PY
           }
@@ -205,8 +211,30 @@ jobs:
             fi
 
             DEP_SHA=$(git -C "$REPO_PATH" rev-parse "dep-${DEP_PR_NUM}")
+
+            # Skip a dependency whose merge commit is already in the checkout.
+            # Its commits are in the target branch and cannot be applied again.
+            # A dependency that is closed without merge is an error.
+            if ! DEP_INFO=$(gh api "repos/${DEP_REPO}/pulls/${DEP_PR_NUM}" \
+                --jq 'if .merged then "merged \(.merge_commit_sha)" else 
.state end'); then
+              echo "::warning::Could not read the state of ${DEP}; applying 
it."
+              DEP_INFO=""
+            fi
+            read -r DEP_STATE MERGE_SHA <<< "$DEP_INFO"
+            if [ "$DEP_STATE" = "merged" ] && \
+               git -C "$REPO_PATH" merge-base --is-ancestor "$MERGE_SHA" HEAD 
2>/dev/null; then
+              echo "Dependency ${DEP} is merged as ${MERGE_SHA}; skipping it"
+              printf '%s\t%s\t%s\tmerged\n' "$DEP_REPO" "$DEP_PR_NUM" 
"$DEP_SHA" >> depends-on-report/applied.tsv
+              continue
+            fi
+
             printf '%s\t%s\t%s\n' "$DEP_REPO" "$DEP_PR_NUM" "$DEP_SHA" >> 
depends-on-report/applied.tsv
 
+            if [ "$DEP_STATE" = "closed" ]; then
+              echo "::error::Dependency ${DEP} is closed without merge; remove 
it from depends-on or reopen it."
+              mark_failed "$DEP" "dependency_closed" "$DEP_REPO" 
"$DEP_PR_NUM"; exit 1
+            fi
+
             # Stop on unrelated histories; HEAD..dep would otherwise include
             # every dependency commit and could cherry-pick unrelated changes.
             COMMON_BASE=$(git -C "$REPO_PATH" merge-base "dep-${DEP_PR_NUM}" 
HEAD || true)
@@ -237,12 +265,15 @@ jobs:
           python3 - <<'PY'
           import json
           shas = {}
+          merged = set()
           try:
               with open("depends-on-report/applied.tsv", encoding="utf-8") as 
f:
                   for line in f:
                       p = line.rstrip("\n").split("\t")
-                      if len(p) == 3:
+                      if len(p) in (3, 4):
                           shas[(p[0], p[1])] = p[2]
+                      if len(p) == 4 and p[3] == "merged":
+                          merged.add((p[0], p[1]))
           except FileNotFoundError:
               pass
           with open("depends-on-report/result.json", encoding="utf-8") as f:
@@ -251,6 +282,8 @@ jobs:
               key = (dep.get("repo"), str(dep.get("number")))
               if key in shas:
                   dep["head_sha"] = shas[key]
+              if key in merged:
+                  dep["merged"] = True
           with open("depends-on-report/result.json", "w", encoding="utf-8") as 
f:
               json.dump(d, f)
           PY
diff --git a/.github/workflows/depends-on-comment.yml 
b/.github/workflows/depends-on-comment.yml
index b6ade4d341c..2e515bdfd18 100644
--- a/.github/workflows/depends-on-comment.yml
+++ b/.github/workflows/depends-on-comment.yml
@@ -135,6 +135,10 @@ jobs:
             const runUrl = context.payload.workflow_run.html_url;
             const shortSha = (s) =>
               (typeof s === 'string' && /^[0-9a-f]{7,40}$/i.test(s)) ? ` @ 
${s.slice(0, 10)}` : '';
+            const depLine = (d) =>
+              `- 
https://github.com/${d.repo}/pull/${d.number}${shortSha(d.head_sha)}` +
+              (d.merged === true ? ' (already merged, skipped)' : '') +
+              (d.closed === true ? ' ❌ **closed without merge**' : '');
 
             let body;
             if (status === 'ok') {
@@ -142,9 +146,7 @@ jobs:
                 core.info('status=ok but no valid dependencies after 
validation; skipping.');
                 return;
               }
-              const list = deps
-                .map((d) => `- 
https://github.com/${d.repo}/pull/${d.number}${shortSha(d.head_sha)}`)
-                .join('\n');
+              const list = deps.map(depLine).join('\n');
               body =
                 `${marker}\n` +
                 `### 🔗 Cross-repo PR dependencies\n\n` +
@@ -157,9 +159,7 @@ jobs:
                 core.info('status=failed but no valid dependencies after 
validation; skipping.');
                 return;
               }
-              const list = deps
-                .map((d) => `- 
https://github.com/${d.repo}/pull/${d.number}${shortSha(d.head_sha)}`)
-                .join('\n');
+              const list = deps.map(depLine).join('\n');
               // Render only fixed text selected by an allowed error code.
               const REASONS = {
                 fetch_failed: 'the dependency PR could not be fetched (it may 
not exist)',
@@ -168,6 +168,8 @@ jobs:
                 cherry_pick_conflict: 'cherry-pick failed (if your PR has 
merge commits, rebase instead)',
                 unsupported_repo: 'the dependency repository is not supported',
                 report_parse_failed: 'the dependency report could not be read',
+                dependency_closed: 'a dependency PR is closed without merge; ' 
+
+                  'remove it from depends-on or reopen it',
               };
               const code = typeof report.error_code === 'string' ? 
report.error_code : '';
               const reason = REASONS[code] ? `\n\nReason: ${REASONS[code]}` : 
'';
diff --git a/.github/workflows/membrowse-report.yml 
b/.github/workflows/membrowse-report.yml
index 07f35e2adf1..bafc4988e75 100644
--- a/.github/workflows/membrowse-report.yml
+++ b/.github/workflows/membrowse-report.yml
@@ -316,6 +316,28 @@ jobs:
             fi
 
             DEP_SHA=$(git -C "$REPO_PATH" rev-parse "dep-${DEP_NUM}")
+
+            # Skip a merged dependency and reject a closed one, as build.yml
+            # does.
+            if ! DEP_INFO=$(gh api "repos/${DEP_REPO}/pulls/${DEP_NUM}" \
+                --jq 'if .merged then "merged \(.merge_commit_sha)" else 
.state end'); then
+              echo "::warning::Could not read the state of ${DEP}; applying 
it."
+              DEP_INFO=""
+            fi
+            read -r DEP_STATE MERGE_SHA <<< "$DEP_INFO"
+            if [ "$DEP_STATE" = "merged" ] && \
+               git -C "$REPO_PATH" merge-base --is-ancestor "$MERGE_SHA" HEAD 
2>/dev/null; then
+              echo "Dependency ${DEP} is merged as ${MERGE_SHA}; skipping it."
+              echo "- \`${DEP}\` @ \`${DEP_SHA}\` (already merged, skipped)" \
+                >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
+              continue
+            fi
+
+            if [ "$DEP_STATE" = "closed" ]; then
+              echo "::error::Dependency ${DEP} is closed without merge; remove 
it from depends-on or reopen it."
+              exit 1
+            fi
+
             if [ -z "$COMMITS" ]; then
               echo "Dependency ${DEP} is already included."
               echo "- \`${DEP}\` @ \`${DEP_SHA}\` (already in the checkout)" \

Reply via email to