From 6464161d630cc39d99f11c9fa3ae520c47fc8365 Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Tue, 28 Apr 2026 07:53:00 -0700 Subject: [PATCH 1/4] validate-pr: relax Launchpad link check; recognize UBUNTU-local commits Two improvements addressing false-positive failures on tracked-branch PRs: 1. Launchpad bug link: drop the requirement that the URL appear on a line prefixed with 'BugLink:' or 'LP:'. The check now only ensures a https://bugs.launchpad.net/... URL exists somewhere in the PR body. Avoids false negatives when authors paste the link in prose. 2. UBUNTU-local commits (e.g. 'UBUNTU: [Config] ...', 'UBUNTU: [Packaging] ...', 'UBUNTU: SAUCE: ...') have no upstream equivalent and previously triggered R6 ("not SAUCE/Revert but has no upstream reference trailer"). Add an is_ubuntu_local() helper that matches the 'UBUNTU: ' prefix; exempt those commits from R6 and R9, label them [UBUNTU] in the digest table, and verify only the Signed-off-by trailer is present. Signed-off-by: Nirmoy Das --- .github/scripts/validate-pr | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/.github/scripts/validate-pr b/.github/scripts/validate-pr index 9c068560f9563..c2fa29448eeb0 100755 --- a/.github/scripts/validate-pr +++ b/.github/scripts/validate-pr @@ -40,6 +40,14 @@ def get_backport_url(message): def is_sauce(subject): return bool(re.match(r'^NVIDIA:.*SAUCE:', subject)) +def is_ubuntu_local(subject): + """Ubuntu-local commits with no upstream equivalent. + + Catches 'UBUNTU: SAUCE: ...', 'UBUNTU: [Config] ...', + 'UBUNTU: [Packaging] ...', 'UBUNTU: Ubuntu-X.X.X-...', etc. + """ + return bool(re.match(r'^UBUNTU:\s', subject)) + def is_revert(subject): return subject.startswith('Revert "') @@ -336,6 +344,9 @@ def build_digest(commits, repo, upstream_remote=None): # Strip outer Revert "..." wrapper and SAUCE prefix for display inner = re.sub(r'^Revert\s+"', '', subj).rstrip('"') display = _norm_sauce_subject(inner) or inner + elif is_ubuntu_local(subj): + kind = 'UBUNTU' + display = re.sub(r'^UBUNTU:\s*', '', subj) else: kind = 'SAUCE' display = _norm_sauce_subject(subj) or subj @@ -460,20 +471,24 @@ def lint_commits(commits): # Classification for R6/R7/R9/R10 sauce = is_sauce(subject) + ubuntu = is_ubuntu_local(subject) revert = is_revert(subject) - # R9: subject length — exempt SAUCE and Reverts of SAUCE; the mandatory - # "NVIDIA: [VR: ]SAUCE:" prefix already consumes 15–20 characters. + # R9: subject length — exempt SAUCE, UBUNTU local and Reverts of SAUCE; + # the mandatory "NVIDIA: [VR: ]SAUCE:" / "UBUNTU: [Config]" prefix + # already consumes 12–20 characters. revert_of_sauce = revert and bool(re.match(r'^Revert "NVIDIA:.*SAUCE:', subject)) - if len(subject) > 72 and not sauce and not revert_of_sauce: + if len(subject) > 72 and not sauce and not ubuntu and not revert_of_sauce: warnings.append("W: {}: subject {} chars (>72)".format(label, len(subject))) cp_sha = get_cherry_pick_sha(commit.message) bp_url = get_backport_url(commit.message) - # R6: non-SAUCE, non-Revert commits must have an upstream reference trailer - if not sauce and not revert and cp_sha is None and bp_url is None: + # R6: non-SAUCE, non-UBUNTU, non-Revert commits must have an upstream + # reference trailer. UBUNTU local commits (e.g. UBUNTU: [Config]) have + # no upstream equivalent. + if not sauce and not ubuntu and not revert and cp_sha is None and bp_url is None: errors.append( - "E: {}: not SAUCE/Revert but has no upstream reference trailer" + "E: {}: not SAUCE/UBUNTU/Revert but has no upstream reference trailer" " (cherry picked from commit ... or backported from ...)".format(label)) # R7: detect wrong trailer for LKML in-review backports @@ -508,19 +523,18 @@ def check_pr_metadata(pr_title, pr_body_path, base_branch): warnings.append( "W: PR title missing [] prefix: \"{}\"".format(pr_title[:80])) - # R3: BugLink required for tracked branches + # R3: Launchpad bug link required for tracked branches if pr_body_path and base_branch and TRACKED_BRANCH_RE.match(base_branch): try: body = open(pr_body_path).read() except OSError as e: errors.append("E: cannot read --pr-body {}: {}".format(pr_body_path, e)) body = '' - buglink_re = re.compile( - r'^(?:BugLink:|LP:)\s+https://bugs\.launchpad\.net/', re.MULTILINE) + buglink_re = re.compile(r'https://bugs\.launchpad\.net/') if not buglink_re.search(body): errors.append( - "E: PR targets {} but body has no 'BugLink:' or 'LP:'" - " https://bugs.launchpad.net/... line".format(base_branch)) + "E: PR targets {} but body has no" + " https://bugs.launchpad.net/... link".format(base_branch)) return warnings, errors From be8756b8ca75172f1e63b4196a5c4689e358fd57 Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Wed, 6 May 2026 11:24:25 -0700 Subject: [PATCH 2/4] patchscan: safely write PR metadata files --- .github/workflows/patchscan.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/patchscan.yml b/.github/workflows/patchscan.yml index 22ac72afaf877..c23b90e4de054 100644 --- a/.github/workflows/patchscan.yml +++ b/.github/workflows/patchscan.yml @@ -80,15 +80,12 @@ jobs: - name: Write PR title and body to files env: GH_TOKEN: ${{ github.token }} + PR_REPO: ${{ steps.pr.outputs.pr_repo }} + PR_NUMBER: ${{ steps.pr.outputs.pr_number }} run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - pr_json=$(gh api repos/${{ steps.pr.outputs.pr_repo }}/pulls/${{ steps.pr.outputs.pr_number }}) - echo "$pr_json" | jq -r '.title' > pr_title.txt - echo "$pr_json" | jq -r '.body // ""' > pr_body.txt - else - printf '%s' "${{ github.event.pull_request.title }}" > pr_title.txt - printf '%s' "${{ github.event.pull_request.body }}" > pr_body.txt - fi + pr_json=$(gh api "repos/${PR_REPO}/pulls/${PR_NUMBER}") + printf '%s\n' "$pr_json" | jq -r '.title // ""' > pr_title.txt + printf '%s\n' "$pr_json" | jq -r '.body // ""' > pr_body.txt - name: Fetch scripts from github-actions branch run: | From b57c820feaa1bb7fd93c4f6a31f7eb74138697b7 Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Wed, 6 May 2026 11:41:31 -0700 Subject: [PATCH 3/4] validate-pr: recognize backported commit trailers --- .github/scripts/validate-pr | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/scripts/validate-pr b/.github/scripts/validate-pr index c2fa29448eeb0..c851dfc5e2b67 100755 --- a/.github/scripts/validate-pr +++ b/.github/scripts/validate-pr @@ -33,6 +33,18 @@ def get_cherry_pick_sha(message): m = re.search(r'\(cherry picked from commit ([a-fA-F0-9]+)\)', message) return m.group(1) if m else None +def get_backported_commit_sha(message): + m = re.search(r'\(backported from commit ([a-fA-F0-9]+)\)', message) + return m.group(1) if m else None + +def get_upstream_commit_sha(message): + for getter in (get_cherry_pick_sha, get_backported_commit_sha): + sha = getter(message) + if sha: + return sha + m = re.search(r'^[Uu]pstream commit ([a-fA-F0-9]+)', message, re.MULTILINE) + return m.group(1) if m else None + def get_backport_url(message): m = re.search(r'\(backported from (https?://[^\)]+)\)', message) return m.group(1) if m else None @@ -330,7 +342,7 @@ def build_digest(commits, repo, upstream_remote=None): errors = [] for commit in commits: - src_sha = get_cherry_pick_sha(commit.message) + src_sha = get_upstream_commit_sha(commit.message) bp_url = get_backport_url(commit.message) # --- SAUCE / Revert: no upstream reference, show as informational row --- @@ -480,13 +492,14 @@ def lint_commits(commits): revert_of_sauce = revert and bool(re.match(r'^Revert "NVIDIA:.*SAUCE:', subject)) if len(subject) > 72 and not sauce and not ubuntu and not revert_of_sauce: warnings.append("W: {}: subject {} chars (>72)".format(label, len(subject))) - cp_sha = get_cherry_pick_sha(commit.message) + upstream_sha = get_upstream_commit_sha(commit.message) + cp_sha = get_cherry_pick_sha(commit.message) bp_url = get_backport_url(commit.message) # R6: non-SAUCE, non-UBUNTU, non-Revert commits must have an upstream # reference trailer. UBUNTU local commits (e.g. UBUNTU: [Config]) have # no upstream equivalent. - if not sauce and not ubuntu and not revert and cp_sha is None and bp_url is None: + if not sauce and not ubuntu and not revert and upstream_sha is None and bp_url is None: errors.append( "E: {}: not SAUCE/UBUNTU/Revert but has no upstream reference trailer" " (cherry picked from commit ... or backported from ...)".format(label)) From e20ac88fa76be1bf5ba20d880570594b7e63bcbf Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Fri, 8 May 2026 09:33:05 -0700 Subject: [PATCH 4/4] validate-pr: show upstream subject in digest --- .github/scripts/validate-pr | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/scripts/validate-pr b/.github/scripts/validate-pr index c851dfc5e2b67..7e65f43cb64cd 100755 --- a/.github/scripts/validate-pr +++ b/.github/scripts/validate-pr @@ -149,7 +149,7 @@ def describe_sob_chain_backport(message): return "ok, backporter: {}".format(' '.join(names)) -COL_WIDTHS = [12, 45, 10, 7, 25] +COL_WIDTHS = [12, 64, 10, 7, 25] HEADERS = ['Local', 'Referenced upstream / Patch subject', 'Patch-ID', 'Subject', 'SoB chain'] def _hline(left, sep, right): @@ -179,6 +179,11 @@ def print_digest_table(rows): print(_hline('└', '┴', '┘')) +def format_upstream_ref(sha12, subject): + """Return compact digest text for a referenced upstream commit.""" + return "{} {}".format(sha12, subject) + + def _shorten_lkml_url(url): """Shorten a lore.kernel.org URL to a compact display form.""" m = re.search(r'lore\.kernel\.org/[^/]+/([^/]+)', url) @@ -434,6 +439,7 @@ def build_digest(commits, repo, upstream_remote=None): # Subject local_subj = subject_of(commit) upstream_subj = subject_of(upstream) + upstream_ref = format_upstream_ref(upstream_sha12, upstream_subj) if local_subj == upstream_subj: subj_status = 'match' subj_error = False @@ -450,7 +456,7 @@ def build_digest(commits, repo, upstream_remote=None): local_sha12, subject_of(commit)[:40], sob_status)) has_error = pid_error or subj_error or sob_error - rows.append(dict(local=local_sha12, upstream=upstream_sha12, + rows.append(dict(local=local_sha12, upstream=upstream_ref, patch_id=pid_status, subject=subj_status, sob=sob_status, error=has_error))