From 962916e8891332154d1917ee502eca3390aecd42 Mon Sep 17 00:00:00 2001 From: Matthieu Baerts Date: Thu, 23 Jul 2026 12:26:57 +0200 Subject: [PATCH 1/4] pw-brancher: set contest failure in case of conflicts When a pull request or a series cannot be applied for some reasons -- e.g. in case of conflicts with pending patches, or with patches applied in net, but not in net-next yet -- the errors were ignored. Because of that, it was easy to miss that a series has not been fully validated as expected, e.g. [1]. When such errors happen, a PatchWork 'contest' check is now created for each patch, with the 'fail' state and a short description. No URL is added, because such logs are currently not publicly available. Link: https://patchwork.kernel.org/project/netdevbpf/patch/20260710134242.216538-10-alice.kernel@fastmail.im/ [1] Signed-off-by: Matthieu Baerts --- pw_brancher.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pw_brancher.py b/pw_brancher.py index 0ac022f7..52114db5 100755 --- a/pw_brancher.py +++ b/pw_brancher.py @@ -119,7 +119,12 @@ def pwe_get_pending(pw, config) -> List: return things -def apply_pending_patches(pw, config, tree) -> Tuple[List, List]: +def pwe_set_apply_error(pw, patch_id, branch_name): + pw.post_check(patch_id, name="contest", state="fail", url="", + desc=f"Conflicts with pending/net patches ({branch_name})") + + +def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: log_open_sec("Get pending submissions from patchwork") things = pwe_get_pending(pw, config) log(f"Have {len(things)} pending things from patchwork") @@ -140,7 +145,7 @@ def apply_pending_patches(pw, config, tree) -> Tuple[List, List]: tree.pull(entry["pull_url"], reset=False) applied_prs.add(entry["id"]) except PullError: - pass + pwe_set_apply_error(pw, entry["id"], branch_name) else: log_open_sec("Applying: " + entry["series"][0]["name"]) seen_series.add(series_id) @@ -151,7 +156,9 @@ def apply_pending_patches(pw, config, tree) -> Tuple[List, List]: tree.apply(p) applied_series.add(series_id) except PatchApplyError: - pass + series_pw = pw.get("series", series_id) + for patch in series_pw["patches"]: + pwe_set_apply_error(pw, patch["id"], branch_name) log_end_sec() log_end_sec() @@ -280,7 +287,7 @@ def create_new(pw, config, state, tree, tgt_remote) -> None: state["hashes"][branch_name] = tree.head_hash() - series, prs = apply_pending_patches(pw, config, tree) + series, prs = apply_pending_patches(pw, config, tree, branch_name) state["info"][branch_name] |= {"series": series, "prs": prs} extras = apply_local_patches(config, tree) From 2712d28fab7d06665b4c7632fb62852154a61d19 Mon Sep 17 00:00:00 2001 From: Matthieu Baerts Date: Thu, 23 Jul 2026 12:52:20 +0200 Subject: [PATCH 2/4] pw-brancher: add error in contest failure It will not be nicely formatted on PW, but it should help to understand with what it was conflicting. Signed-off-by: Matthieu Baerts --- core/tree.py | 11 ++++++----- pw_brancher.py | 12 ++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/core/tree.py b/core/tree.py index 5844b270..481e3b90 100644 --- a/core/tree.py +++ b/core/tree.py @@ -15,13 +15,14 @@ from core import Patch -# TODO: add patch and CmdError as init here class PatchApplyError(Exception): - pass + def __init__(self, err): + super().__init__(err) class PullError(Exception): - pass + def __init__(self, err): + super().__init__(err) class TreeNotClean(Exception): @@ -237,7 +238,7 @@ def _apply_patch_safe(self, patch): self.git(["am", "--abort"]) except CMD.CmdError: pass - raise PatchApplyError(e) from e + raise PatchApplyError(e.stderr) from e def apply(self, thing): if isinstance(thing, Patch): @@ -278,7 +279,7 @@ def _pull_safe(self, pull_url, trust_rerere, ff): self.git(["merge", "--abort"]) except CMD.CmdError: pass - raise PullError(e) from e + raise PullError(e.stderr) from e def pull(self, pull_url, reset=True, trust_rerere=None, ff=None): core.log_open_sec("Pulling " + pull_url) diff --git a/pw_brancher.py b/pw_brancher.py index 52114db5..40250751 100755 --- a/pw_brancher.py +++ b/pw_brancher.py @@ -119,9 +119,9 @@ def pwe_get_pending(pw, config) -> List: return things -def pwe_set_apply_error(pw, patch_id, branch_name): +def pwe_set_apply_error(pw, patch_id, branch_name, e): pw.post_check(patch_id, name="contest", state="fail", url="", - desc=f"Conflicts with pending/net patches ({branch_name})") + desc=f"Conflicts with pending/net patches ({branch_name}): {e}") def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: @@ -144,8 +144,8 @@ def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: try: tree.pull(entry["pull_url"], reset=False) applied_prs.add(entry["id"]) - except PullError: - pwe_set_apply_error(pw, entry["id"], branch_name) + except PullError as e: + pwe_set_apply_error(pw, entry["id"], branch_name, e) else: log_open_sec("Applying: " + entry["series"][0]["name"]) seen_series.add(series_id) @@ -155,10 +155,10 @@ def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: try: tree.apply(p) applied_series.add(series_id) - except PatchApplyError: + except PatchApplyError as e: series_pw = pw.get("series", series_id) for patch in series_pw["patches"]: - pwe_set_apply_error(pw, patch["id"], branch_name) + pwe_set_apply_error(pw, patch["id"], branch_name, e) log_end_sec() log_end_sec() From 820133fb391813788cf2e2586b4c0e7510bb3cab Mon Sep 17 00:00:00 2001 From: Matthieu Baerts Date: Fri, 24 Jul 2026 09:51:03 +0200 Subject: [PATCH 3/4] pw-brancher: only send one contest failure We should try to be good citizens and avoid adding a new 'check' each time a new branch is created and the same conflict happened. Instead, check if the last contest check was already mentioning the conflicts. A request is done on PW, because conflicts should be exceptional, but also conflicts could happen later on, e.g. after a sync with Linus tree, or if a newer, but urgent and conflicting patch is applied first. Signed-off-by: Matthieu Baerts --- pw_brancher.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/pw_brancher.py b/pw_brancher.py index 40250751..22d1d288 100755 --- a/pw_brancher.py +++ b/pw_brancher.py @@ -42,6 +42,7 @@ psql_conn = None ignore_delegate = {} gate_checks = {} +conflict_msg = "Conflicts with pending/net patches" def write_json_atomic(path, data): @@ -119,9 +120,23 @@ def pwe_get_pending(pw, config) -> List: return things +def pwe_has_contest_check(pw, entry) -> bool: + if "checks" not in entry: + return False + + checks = pw.request(entry["checks"]) + desc = "" + for c in checks: + # Take the last one + if c["context"] == "contest": + desc = c["description"] if "description" in c else "" + + return desc.startswith(conflict_msg) + + def pwe_set_apply_error(pw, patch_id, branch_name, e): pw.post_check(patch_id, name="contest", state="fail", url="", - desc=f"Conflicts with pending/net patches ({branch_name}): {e}") + desc=f"{conflict_msg} ({branch_name}):\n{e}") def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: @@ -145,7 +160,8 @@ def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: tree.pull(entry["pull_url"], reset=False) applied_prs.add(entry["id"]) except PullError as e: - pwe_set_apply_error(pw, entry["id"], branch_name, e) + if not pwe_has_contest_check(pw, entry): + pwe_set_apply_error(pw, entry["id"], branch_name, e) else: log_open_sec("Applying: " + entry["series"][0]["name"]) seen_series.add(series_id) @@ -156,9 +172,10 @@ def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: tree.apply(p) applied_series.add(series_id) except PatchApplyError as e: - series_pw = pw.get("series", series_id) - for patch in series_pw["patches"]: - pwe_set_apply_error(pw, patch["id"], branch_name, e) + if not pwe_has_contest_check(pw, entry): + series_pw = pw.get("series", series_id) + for patch in series_pw["patches"]: + pwe_set_apply_error(pw, patch["id"], branch_name, e) log_end_sec() log_end_sec() From 50c89d2617535e2e1f62efbe71cdecd6910e214b Mon Sep 17 00:00:00 2001 From: Matthieu Baerts Date: Fri, 24 Jul 2026 10:18:52 +0200 Subject: [PATCH 4/4] pw-brancher: log contest failures These conflicts should be exceptional, and there could be failures with PW. Better log these actions then. Signed-off-by: Matthieu Baerts --- pw_brancher.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/pw_brancher.py b/pw_brancher.py index 22d1d288..4594f995 100755 --- a/pw_brancher.py +++ b/pw_brancher.py @@ -134,11 +134,25 @@ def pwe_has_contest_check(pw, entry) -> bool: return desc.startswith(conflict_msg) -def pwe_set_apply_error(pw, patch_id, branch_name, e): - pw.post_check(patch_id, name="contest", state="fail", url="", +def pwe_post_check(pw, entry, branch_name, e): + log("Setting 'fail' contest state for: " + entry["name"]) + pw.post_check(entry["id"], name="contest", state="fail", url="", desc=f"{conflict_msg} ({branch_name}):\n{e}") +def pwe_set_apply_error(pw, entry, branch_name, e, series_id=None): + log_open_sec("Set PW 'contest' check state") + if pwe_has_contest_check(pw, entry): + log("Skip: already has 'contest' check") + elif series_id is not None: + series_pw = pw.get("series", series_id) + for patch in series_pw["patches"]: + pwe_post_check(pw, patch, branch_name, e) + else: + pwe_post_check(pw, entry, branch_name, e) + log_end_sec() + + def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: log_open_sec("Get pending submissions from patchwork") things = pwe_get_pending(pw, config) @@ -160,8 +174,7 @@ def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: tree.pull(entry["pull_url"], reset=False) applied_prs.add(entry["id"]) except PullError as e: - if not pwe_has_contest_check(pw, entry): - pwe_set_apply_error(pw, entry["id"], branch_name, e) + pwe_set_apply_error(pw, entry, branch_name, e) else: log_open_sec("Applying: " + entry["series"][0]["name"]) seen_series.add(series_id) @@ -172,10 +185,7 @@ def apply_pending_patches(pw, config, tree, branch_name) -> Tuple[List, List]: tree.apply(p) applied_series.add(series_id) except PatchApplyError as e: - if not pwe_has_contest_check(pw, entry): - series_pw = pw.get("series", series_id) - for patch in series_pw["patches"]: - pwe_set_apply_error(pw, patch["id"], branch_name, e) + pwe_set_apply_error(pw, entry, branch_name, e, series_id) log_end_sec() log_end_sec()