From patchwork Fri May 1 10:59:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2244 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633261; bh=RXJO9BKrXmooq1ZK2hlrsk/6KFOaMXTV8ZxRmn0xYRg=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=krOeUeX7zZTMIV5mmgAlFRxEQbSzVAxFEp9ZP+tzg2myW1HyT2XKyvupk4OPfxoE0 MTfVpTCzCGx+no8wVdqvcBHIaow33FQNISIj5Iw1G383Uj9A5jCH/IX01ndYkXEaV2 eLIj+0547m0xuFFU/8tAGf/csRrOUGdMQt9vQklM= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 046076A837 for ; Fri, 1 May 2026 05:01:01 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 5--5uSyyksiw for ; Fri, 1 May 2026 05:01:00 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633260; bh=RXJO9BKrXmooq1ZK2hlrsk/6KFOaMXTV8ZxRmn0xYRg=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=XPnztBrv9wcsZN0iZNzLX/knqKKOZVArR2PjY47sdoHxfXAj6Azn3P4O+Q02iaLGk aPHuAf5/WOMETksm1MeTAOqzj3gXrcojOrB/pEwT1iVqcPgG/cxsp0dAaYTQVaRmaV g04Q5MwS6Y196Z3ZZeCRC6HipIymYMtZTexPmZrg= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E78666A7AF for ; Fri, 1 May 2026 05:01:00 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633258; bh=oq9XLCi55yIIMwHxrLoGbi6AgILry+Vj1x0lgKes/BU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n0SLrCdeVt78ExAqGKaNbwxhzkbcG47FSm/tAb3TmDDmCExwfz9oqpHhq85Fr7wnY 4n9TPO5YSEfPfFjNa6f5lOwdW4kne068vVRNrSS4xIm4cJqB2kSgju1WxANjZPLKDF k/e2RQuwZZEM58HekClhlje4FGksSuCxMLW+gviQ= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D2FB16A82E; Fri, 1 May 2026 05:00:58 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id Z7rN5QuReiBv; Fri, 1 May 2026 05:00:58 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633254; bh=BR0SJsIDmDUIj4vJm1RKuh1kDfscVCdi+8Otb0VBfb4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=s/GubcDDI7DcJGfEkFOCddX1TNZCb3TfT2jwMwMNthy725zpKSSMbcToxzhdcxvIS 6DK91i/9SHW3dwKFMHD5fgTxUW+UAg4tXDdRfMPdKjzU/siKQiCYEueQ3Ou+T4jONV FxhiVfEGk5aJSZnbz8O1DFQtKJgj9fubvaHVJGZI= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 5BF586A7AF; Fri, 1 May 2026 05:00:54 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 04:59:53 -0600 Message-ID: <20260501110040.1874719-2-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: DALOZ3633RLO2DSXXLQDDCDUQVLFKNEZ X-Message-ID-Hash: DALOZ3633RLO2DSXXLQDDCDUQVLFKNEZ X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 01/29] buildman: Hide 'Boards not built' by default List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass When building a tools-only series, every commit shows a 'Boards not built' line listing all selected boards, which clutters the output without adding useful information. Add a --show-not-built flag to control this output. The default is now to hide the list; pass --show-not-built to see it. Signed-off-by: Simon Glass --- tools/buildman/builder.py | 3 ++- tools/buildman/buildman.rst | 5 +++++ tools/buildman/cmdline.py | 3 +++ tools/buildman/control.py | 3 ++- tools/buildman/func_test.py | 2 +- tools/buildman/outcome.py | 1 + tools/buildman/resulthandler.py | 3 ++- tools/buildman/test.py | 12 ++++++++---- tools/buildman/test_builder.py | 3 ++- tools/buildman/worker.py | 3 ++- 10 files changed, 28 insertions(+), 10 deletions(-) diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py index b3cc136d036..b07eb675ea9 100644 --- a/tools/buildman/builder.py +++ b/tools/buildman/builder.py @@ -359,7 +359,8 @@ class Builder: self._opts = DisplayOptions( show_errors=True, show_sizes=False, show_detail=False, show_bloat=False, show_config=False, show_environment=False, - show_unknown=True, ide=False, list_error_boards=False) + show_unknown=True, ide=False, list_error_boards=False, + show_not_built=False) self._filter_dtb_warnings = False self._filter_migration_warnings = False diff --git a/tools/buildman/buildman.rst b/tools/buildman/buildman.rst index 2bc0265a61d..4a6c8ee0796 100644 --- a/tools/buildman/buildman.rst +++ b/tools/buildman/buildman.rst @@ -1442,6 +1442,11 @@ If there are both warnings and errors, errors win, so Buildman returns 100. The -y option is provided (for use with -s) to ignore the bountiful device-tree warnings. Similarly, -Y tells Buildman to ignore the migration warnings. +When some boards cannot be built (e.g. because a toolchain is missing), +Buildman can report which boards were skipped. Use ``--show-not-built`` to +enable this. It is off by default since it adds noise when building series +that only affect tools or a subset of architectures. + Sometimes you might get an error in a thread that is not handled by Buildman, perhaps due to a failure of a tool that it calls. You might see the output, but then Buildman hangs. Failing to handle any eventuality is a bug in Buildman and diff --git a/tools/buildman/cmdline.py b/tools/buildman/cmdline.py index 5396ee640fa..34e88048468 100644 --- a/tools/buildman/cmdline.py +++ b/tools/buildman/cmdline.py @@ -94,6 +94,9 @@ def add_upto_m(parser): default=False, help="Don't convert y to 1 in configs") parser.add_argument('-l', '--list-error-boards', action='store_true', default=False, help='Show a list of boards next to each error/warning') + parser.add_argument('--show-not-built', action='store_true', + default=False, + help='Show boards that were not built for each commit') parser.add_argument('-L', '--no-lto', action='store_true', default=False, help='Disable Link-time Optimisation (LTO) for builds') parser.add_argument('--list-tool-chains', action='store_true', default=False, diff --git a/tools/buildman/control.py b/tools/buildman/control.py index bb866910491..87b7ffa09d8 100644 --- a/tools/buildman/control.py +++ b/tools/buildman/control.py @@ -1108,7 +1108,8 @@ def do_buildman(args, toolchains=None, make_func=None, brds=None, show_environment=args.show_environment, show_unknown=args.show_unknown, ide=args.ide, - list_error_boards=args.list_error_boards) + list_error_boards=args.list_error_boards, + show_not_built=args.show_not_built) result_handler = ResultHandler(col, display_options) # Create a new builder with the selected args diff --git a/tools/buildman/func_test.py b/tools/buildman/func_test.py index 90c4974960e..98b2a899e28 100644 --- a/tools/buildman/func_test.py +++ b/tools/buildman/func_test.py @@ -706,7 +706,7 @@ Idx Name Size VMA LMA File off Algn # Now show summary - should report boards not built terminal.get_print_test_lines() # Clear self._run_control('-b', TEST_BRANCH, '-o', self._output_dir, '-s', - clean_dir=False) + '--show-not-built', clean_dir=False) lines = terminal.get_print_test_lines() text = '\n'.join(line.text for line in lines) diff --git a/tools/buildman/outcome.py b/tools/buildman/outcome.py index 18c97e523a0..42a9f1f196b 100644 --- a/tools/buildman/outcome.py +++ b/tools/buildman/outcome.py @@ -27,6 +27,7 @@ DisplayOptions = namedtuple('DisplayOptions', [ 'show_unknown', # Show unknown boards in summary 'ide', # IDE mode - output to stderr 'list_error_boards', # Include board list with error lines + 'show_not_built', # Show boards that were not built ]) # Error line information for display diff --git a/tools/buildman/resulthandler.py b/tools/buildman/resulthandler.py index fca8e7d6ba1..85f0112e765 100644 --- a/tools/buildman/resulthandler.py +++ b/tools/buildman/resulthandler.py @@ -174,7 +174,8 @@ class ResultHandler: self._base_config = config self._base_environment = environment - self._show_not_built(board_selected, board_dict) + if self._opts.show_not_built: + self._show_not_built(board_selected, board_dict) def _get_error_lines(self): """Get the number of error lines output diff --git a/tools/buildman/test.py b/tools/buildman/test.py index 998f2227281..74a39c9556d 100644 --- a/tools/buildman/test.py +++ b/tools/buildman/test.py @@ -200,7 +200,8 @@ class TestBuildBase(unittest.TestCase): self._opts = DisplayOptions( show_errors=False, show_sizes=False, show_detail=False, show_bloat=False, show_config=False, show_environment=False, - show_unknown=False, ide=False, list_error_boards=False) + show_unknown=False, ide=False, list_error_boards=False, + show_not_built=False) self._result_handler = ResultHandler(self._col, self._opts) self.base_dir = tempfile.mkdtemp() @@ -272,7 +273,8 @@ class TestBuildOutput(TestBuildBase): opts = DisplayOptions( show_errors=show_errors, show_sizes=False, show_detail=False, show_bloat=False, show_config=False, show_environment=False, - show_unknown=False, ide=False, list_error_boards=list_error_boards) + show_unknown=False, ide=False, list_error_boards=list_error_boards, + show_not_built=False) build = builder.Builder(self.toolchains, self.base_dir, None, threads, 2, self._col, ResultHandler(self._col, opts), checkout=False) @@ -1233,7 +1235,8 @@ class TestBuildSummary(TestBuildBase): opts = DisplayOptions( show_errors=False, show_sizes=False, show_detail=False, show_bloat=False, show_config=False, show_environment=False, - show_unknown=False, ide=False, list_error_boards=False) + show_unknown=False, ide=False, list_error_boards=False, + show_not_built=False) build = builder.Builder(self.toolchains, self.base_dir, None, 1, 2, self._col, ResultHandler(self._col, opts), checkout=False) @@ -1288,7 +1291,8 @@ class TestBuildSummary(TestBuildBase): opts = DisplayOptions( show_errors=False, show_sizes=False, show_detail=False, show_bloat=False, show_config=False, show_environment=False, - show_unknown=False, ide=False, list_error_boards=False) + show_unknown=False, ide=False, list_error_boards=False, + show_not_built=False) build = builder.Builder(self.toolchains, self.base_dir, None, 1, 2, self._col, ResultHandler(self._col, opts), checkout=False) diff --git a/tools/buildman/test_builder.py b/tools/buildman/test_builder.py index 74f19ec9528..282d446b1ec 100644 --- a/tools/buildman/test_builder.py +++ b/tools/buildman/test_builder.py @@ -24,7 +24,8 @@ from u_boot_pylib import terminal DEFAULT_OPTS = DisplayOptions( show_errors=False, show_sizes=False, show_detail=False, show_bloat=False, show_config=False, show_environment=False, - show_unknown=False, ide=False, list_error_boards=False) + show_unknown=False, ide=False, list_error_boards=False, + show_not_built=False) class TestPrintFuncSizeDetail(unittest.TestCase): diff --git a/tools/buildman/worker.py b/tools/buildman/worker.py index ddf023a1979..c0d561f877e 100644 --- a/tools/buildman/worker.py +++ b/tools/buildman/worker.py @@ -658,7 +658,8 @@ def _create_builder(state, num_threads, num_jobs): opts = DisplayOptions( show_errors=False, show_sizes=False, show_detail=False, show_bloat=False, show_config=False, show_environment=False, - show_unknown=False, ide=True, list_error_boards=False) + show_unknown=False, ide=True, list_error_boards=False, + show_not_built=False) result_handler = ResultHandler(col, opts) bldr = builder_mod.Builder( From patchwork Fri May 1 10:59:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2245 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633263; bh=7Anzd6WIEDJIoqFusXeFYrFfhwUbJFL9tiMcqWHYA/s=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=FkE5AdWP/T1XuHOfaC+DuqJRA4UFUjEvGxfVzqVSAjAFXNeu641JqOluq8QLA6XyL s6mYJ7YFXpX8ROd6KUXSMfYDDb7YXRgjhXbpSKdfQvm7RJHpFa0FNPNKptfmxrlcIc 1Kioig50Db5gUzun86g8AfYc8u5DyelvvVt2ko9c= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6086E6A833 for ; Fri, 1 May 2026 05:01:03 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id doPhCKGfAgxK for ; Fri, 1 May 2026 05:01:03 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633262; bh=7Anzd6WIEDJIoqFusXeFYrFfhwUbJFL9tiMcqWHYA/s=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=XBIjwOSEn6mJ1kh3RUBHG8/+qRcu+Xwjh9qOKpyhfny9WwNVeY9vVkPE9t2DCE37m RJJ1IDfH26+LtwXYE1pKJDKXKImFpkXf3fdK3bV2IfHhr5zyCSlCFaWnEHR/9z0iWk bx2BcM9bIolYP4TfwilSFi8vN3I7QPRcFkPzS6YA= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5895D6A834 for ; Fri, 1 May 2026 05:01:02 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633259; bh=CbC1Jc4opFRvZ4tVhxFgM8xow0fl4HUEW0IZBnqJGmI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pR15Mlv2jISBViSd6+58+atJwxw555mtXGVwi/Y5dnyVFA5f9On072IhUs3tiPWCj Wh1apmLsXf6mmMMTBhZDAIMBwfZGsg2yrNMxA86zd8hXWgQQ3+WuiKoWetqL8l6EzM E3Z69JnuZCrJo1TspL4uXIinGiErS44/zNt0zIM0= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C66D16A82E; Fri, 1 May 2026 05:00:59 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id fEYW8jHGEHM3; Fri, 1 May 2026 05:00:59 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633259; bh=F8mUTY5We5+ukou59JuvVVG4tQZx3GW3Ea5jj9FwvUg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n7F1hPQSOs5RYrxUg3eXW/32pAQDqqAir7w+t9JHel79T15Lf2sPpd89drfWfwixS n1yXy4n67xwkjhx7zYXUoEXpfLqXtPY3nZ1nt8RrZaSBA3VSLLYy7ue4jfSFCfl4jJ Nli9mlO+bdGB0/GvYjH/MTQM1ZvtFN4o+1g1LrVM= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 330A96A7AF; Fri, 1 May 2026 05:00:59 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 04:59:54 -0600 Message-ID: <20260501110040.1874719-3-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: XR3VNIPOBWWRWDGIDZSKOH65H7UTE7TU X-Message-ID-Hash: XR3VNIPOBWWRWDGIDZSKOH65H7UTE7TU X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 02/29] u_boot_pylib: Add run_interactive() for PTY commands List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Add a helper that runs a command with its stdout and stderr connected to a PTY (so interactive programs flush before prompting) while the child inherits stdin from the parent process. A reader thread copies PTY output to fd 1 and accumulates it for the caller. This is similar to cros_subprocess with PIPE_PTY, but cros_subprocess always intercepts stdin which prevents the user from responding to prompts directly. Letting Popen inherit stdin from fd 0 (rather than referencing sys.stdin directly) keeps the helper usable inside test runners that close sys.stdin in forked workers. Add unit tests covering stdout capture, stderr-through-PTY capture, the silent-failure return value, and the cwd argument. Register the new TestRunInteractive class with both 'patman test' and the u_boot_pylib test runner so the tests are picked up by either entry point. Signed-off-by: Simon Glass --- tools/patman/__main__.py | 3 +- tools/u_boot_pylib/__main__.py | 4 ++- tools/u_boot_pylib/command.py | 51 ++++++++++++++++++++++++++ tools/u_boot_pylib/test_command.py | 58 ++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 tools/u_boot_pylib/test_command.py diff --git a/tools/patman/__main__.py b/tools/patman/__main__.py index 5dce54bf676..44219b3eb9a 100755 --- a/tools/patman/__main__.py +++ b/tools/patman/__main__.py @@ -40,13 +40,14 @@ def run_patman(): from patman import func_test from patman import test_checkpatch from patman import test_cseries + from u_boot_pylib import test_command to_run = args.testname if args.testname not in [None, 'test'] else None result = test_util.run_test_suites( 'patman', False, args.verbose, args.no_capture, args.test_preserve_dirs, None, to_run, None, [test_checkpatch.TestPatch, func_test.TestFunctional, 'settings', - test_cseries.TestCseries]) + test_cseries.TestCseries, test_command.TestRunInteractive]) sys.exit(0 if result.wasSuccessful() else 1) # Process commits, produce patches files, check them, email them diff --git a/tools/u_boot_pylib/__main__.py b/tools/u_boot_pylib/__main__.py index 5687f9b51a5..ee03f51bb9e 100755 --- a/tools/u_boot_pylib/__main__.py +++ b/tools/u_boot_pylib/__main__.py @@ -29,13 +29,15 @@ def run_tests(): args = parser.parse_args() from u_boot_pylib import test_claude + from u_boot_pylib import test_command to_run = args.testname if args.testname not in [None, 'test'] else None result = test_util.run_test_suites( 'u_boot_pylib', False, args.verbose, False, False, None, to_run, None, ['u_boot_pylib.terminal', 'u_boot_pylib.gitutil', - cros_subprocess.TestSubprocess, test_claude.TestClaude]) + cros_subprocess.TestSubprocess, test_claude.TestClaude, + test_command.TestRunInteractive]) sys.exit(0 if result.wasSuccessful() else 1) diff --git a/tools/u_boot_pylib/command.py b/tools/u_boot_pylib/command.py index c44bed6acc0..f54c381e589 100644 --- a/tools/u_boot_pylib/command.py +++ b/tools/u_boot_pylib/command.py @@ -5,7 +5,10 @@ Shell command ease-ups for Python Copyright (c) 2011 The Chromium OS Authors. """ +import os +import pty import subprocess +import threading from u_boot_pylib import cros_subprocess @@ -160,6 +163,54 @@ def run_pipe(pipe_list, infile=None, outfile=None, capture=False, return result +def run_interactive(cmd, cwd=None): + """Run an interactive command with a PTY for correct output ordering + + Similar to cros_subprocess.Popen with PIPE_PTY, but the child's stdin is + inherited from the parent process so the user can respond to prompts + directly. cros_subprocess always intercepts stdin which prevents interactive + use. + + The child's stdout and stderr go through a PTY so interactive programs (like + git send-email) flush before prompting. A reader thread copies PTY output to + fd 1 and accumulates it for the caller. + + Args: + cmd (list of str): Command to run + cwd (str or None): Working directory + + Returns: + str: All output produced by the command + """ + parent_fd, child_fd = pty.openpty() + captured = [] + + def reader(): + """Drain the PTY: copy each chunk to fd 1 and remember it""" + try: + while True: + data = os.read(parent_fd, 4096) + if not data: + break + try: + os.write(1, data) + except OSError: + pass + captured.append(data) + except OSError: + pass + + thr = threading.Thread(target=reader, daemon=True) + thr.start() + + proc = subprocess.Popen(cmd, cwd=cwd, stdout=child_fd, stderr=child_fd) + os.close(child_fd) + proc.wait() + thr.join(timeout=2) + os.close(parent_fd) + return b''.join(captured).decode('utf-8', errors='replace') + + def output(*cmd, **kwargs): """Run a command and return its output diff --git a/tools/u_boot_pylib/test_command.py b/tools/u_boot_pylib/test_command.py new file mode 100644 index 00000000000..ab3cec0fa89 --- /dev/null +++ b/tools/u_boot_pylib/test_command.py @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2026 Simon Glass + +"""Tests for the u_boot_pylib.command module""" + +import contextlib +import os +import unittest + +from u_boot_pylib import command + + +@contextlib.contextmanager +def _silence_fd1(): + """Redirect raw fd 1 to /dev/null so PTY echo does not leak""" + saved = os.dup(1) + devnull = os.open(os.devnull, os.O_WRONLY) + try: + os.dup2(devnull, 1) + yield + finally: + os.dup2(saved, 1) + os.close(saved) + os.close(devnull) + + +class TestRunInteractive(unittest.TestCase): + """Tests for command.run_interactive()""" + + def test_captures_stdout(self): + """run_interactive() returns text written to stdout""" + with _silence_fd1(): + out = command.run_interactive(['printf', 'hello']) + self.assertIn('hello', out) + + def test_captures_stderr(self): + """run_interactive() also captures stderr through the PTY""" + with _silence_fd1(): + out = command.run_interactive( + ['sh', '-c', 'printf out; printf err >&2']) + self.assertIn('out', out) + self.assertIn('err', out) + + def test_silent_failing_command(self): + """run_interactive() returns empty for a silent failing command""" + with _silence_fd1(): + out = command.run_interactive(['false']) + self.assertEqual('', out) + + def test_cwd(self): + """run_interactive() honours the cwd argument""" + with _silence_fd1(): + out = command.run_interactive(['pwd'], cwd='/tmp') + self.assertIn('/tmp', out) + + +if __name__ == '__main__': + unittest.main() From patchwork Fri May 1 10:59:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2246 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633263; bh=W3K5uaTai6boFmoI0hpi4Vqs86TYxZbl93WtbslcflY=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=Nu4HU5IwLwayXfwXRv9nl920h64NsRD34fBx32VYEJLAS+eUsceRhKuKvi8lFkMmR 3rNGaAiWijX4d+PL5bmOyEwRdLxjb+f5exMHF6mG2mlLQI27rwenAmL524nv7i2nO3 GhFHwWk9/DwKd8XehBhUeqMewhWOvz3TIjYggyUI= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id ADE9B6A840 for ; Fri, 1 May 2026 05:01:03 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id mVHNK2s33Mnv for ; Fri, 1 May 2026 05:01:03 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633262; bh=W3K5uaTai6boFmoI0hpi4Vqs86TYxZbl93WtbslcflY=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=FJ7pTPk69TOtP9+i5ZoElH/ig3fsfX7qc6suIJlZsB2LYUflCZlu0t3ernuMNa67t qIS1qHPhlQnqyKWYvGFwmQocw0BN56JAXO1zFJlAFf6rdpifFW50JHvp0tva2IKvME PTGQLaoFbatQ6Ye2s6ZtgMowpTGW124EJkuozIVM= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C75A76A841 for ; Fri, 1 May 2026 05:01:02 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633260; bh=StlHkPj5/3ZKYLgcCar8pWiHLFNJf9dpdd3UefDnP5U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Cc2CyHTUKOxZVQ6vUYAO4iqOeA+kyC2r7G+s2eNLDr2LdJm4QfxQWh7RW9tjuMGqo O1d0F/RNqbNQfovzWB+DNzZPLsNhynvxIGQf/wQ3C5DtrNT8bC9qvm66fw2vdp+Thq WEXnT43HqUJRlVxhsnSvVhYUVHZZvDXrFSxBB8ZQ= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DB40C6A833; Fri, 1 May 2026 05:01:00 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id at1zd5bu9bJl; Fri, 1 May 2026 05:01:00 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633260; bh=r7LJ+/XP1DEldt/QiSljJ3h7muJ5oVZrQFei9U3JSu0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nZOf/lJrpAAA8O2NR6wRzp8fNCatp+cSr5IJZL4S/07ZeQhw8wekmkp31p/ox8AqW odKw6X40MLJ3wYpSgXJbrOPInWfjRwN8Jejj660/ltdlLTKD6k5fL5L8BrIzBRe5CZ oi0nDEE988Ls5459SBZKVhdQ9PU111+X3GtskAhE= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 5598A6A7AF; Fri, 1 May 2026 05:01:00 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 04:59:55 -0600 Message-ID: <20260501110040.1874719-4-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 4SJX3CRJ5YKLA25P5UTV2BBOETP2MTLO X-Message-ID-Hash: 4SJX3CRJ5YKLA25P5UTV2BBOETP2MTLO X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 03/29] u_boot_pylib: Add a way to inspect agent output List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass When the Claude Agent SDK interrupts mid-run (e.g. due to a blocked tool or an internal limit), only the text blocks are printed and any surrounding message metadata is lost. This makes it hard to see why the interruption happened. Add an env-var hook: setting PATMAN_DEBUG_AGENT=1 dumps every message and block received from the SDK to stderr. This is verbose so it is off by default, but invaluable when diagnosing a stuck apply or review run. Signed-off-by: Simon Glass --- tools/u_boot_pylib/claude.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/u_boot_pylib/claude.py b/tools/u_boot_pylib/claude.py index 6ba980b6b2e..82fd29ea8d7 100644 --- a/tools/u_boot_pylib/claude.py +++ b/tools/u_boot_pylib/claude.py @@ -50,11 +50,19 @@ async def run_agent_collect(prompt, options): tuple: (success, conversation_log) where success is bool and conversation_log is the agent's output text """ + import os + debug = os.environ.get('PATMAN_DEBUG_AGENT') conversation_log = [] try: async for message in query(prompt=prompt, options=options): + if debug: + tout.error(f'AGENT MSG: {type(message).__name__}: ' + f'{message!r}') if hasattr(message, 'content'): for block in message.content: + if debug: + tout.error(f' BLOCK: {type(block).__name__}: ' + f'{block!r}') if hasattr(block, 'text'): print(block.text) conversation_log.append(block.text) From patchwork Fri May 1 10:59:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2247 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633268; bh=618WphB3f8voSkhx170WO7Hrg8Ml65aryD4OYPllctE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=ACxYXfZxB8mObRyoKJ+FS2PM6PIXT9zmaRuEVgrLXS3W/RAuYy+hMi91OJnkadJo7 oFesf8Qt9AL/1AZ/jF18PAld+DfTFp13zNA1yB4mJX/Z5wjyM1IqQE3m81loRx+SeX MqnUHPID9O9HAuSYugiH6TJ5DaVTY0C3uF6hQO9A= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 532BB6A834 for ; Fri, 1 May 2026 05:01:08 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Yv6exc_mmWBZ for ; Fri, 1 May 2026 05:01:08 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633268; bh=618WphB3f8voSkhx170WO7Hrg8Ml65aryD4OYPllctE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=ACxYXfZxB8mObRyoKJ+FS2PM6PIXT9zmaRuEVgrLXS3W/RAuYy+hMi91OJnkadJo7 oFesf8Qt9AL/1AZ/jF18PAld+DfTFp13zNA1yB4mJX/Z5wjyM1IqQE3m81loRx+SeX MqnUHPID9O9HAuSYugiH6TJ5DaVTY0C3uF6hQO9A= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3D1226A82E for ; Fri, 1 May 2026 05:01:08 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633266; bh=jVwsQywn4NTo34iflhI54CxeaVWP3p/scPHd6jJfzg4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Nlefwe11KK+TruABUYkfYmYN1eB2iYJTpONpxU0Dhxz+Czk5qZyHUFOxpUH505XqF Rx690C/62AveN2SV65b2HFqewaTvz1wtehTJWqv3fHiPKRO96oA0VHeuRtB+ZEBlZh zhSEAzNfUjj1yfBcCgne2mdhjkQs8wdElZaSc1tY= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0CF1E6A78B; Fri, 1 May 2026 05:01:06 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id koN28msN-0AB; Fri, 1 May 2026 05:01:05 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633261; bh=dxXwc1Qb+DdIYu0JW43Y4GJ3yRUl1wmyj88Bcfa5uwY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Awy5vdnqwVdRyBXoBM4DBTIRBqCWNWTz5cPZETeeYK1WKs/Sg6+PKZwsMrNnvEQdj nzx+7KHXwpSNKnVlYBe3zVddQfE6oLs5cmcKcuQqYU/kmq5Um62HTfamZIl3yNahq6 azXja+nIWIA0B28W7pTc8958R3bFIdNLaBlNuZMU= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 627186A82E; Fri, 1 May 2026 05:01:01 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 04:59:56 -0600 Message-ID: <20260501110040.1874719-5-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: M67G6A25IDLNY46X4P5VNTD3VDJSNXDU X-Message-ID-Hash: M67G6A25IDLNY46X4P5VNTD3VDJSNXDU X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 04/29] u_boot_pylib: Add gitutil helpers for repo-aware git operations List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass review.py and other patman code paths run a fair number of git commands directly via command.output() / command.run_one(), driving git from a working-tree path passed as cwd. The existing gitutil helpers mostly take an explicit --git-dir, which is a different calling convention and so does not fit those sites cleanly. Add a small set of wrappers that take a working-tree path (passed through subprocess cwd, with git's discovery rules locating the repository from there). Each one returns the obvious thing for its operation: - count_revs(): commits in a range as int, or None on failure. - diff_stat(): output of 'git diff --stat ', or '' on failure. - checkout_branch(): runs 'git checkout '. - stash_save(): runs 'git stash', returns the resulting stdout. - stash_pop(): runs 'git stash pop'. - ref_exists(): bool indicating whether a ref resolves. - current_branch(): name of the current branch, or 'HEAD' if detached. No callers yet; a follow-up patch switches review.py over. Signed-off-by: Simon Glass --- tools/u_boot_pylib/gitutil.py | 107 +++++++++++++++++++++++++ tools/u_boot_pylib/test_gitutil.py | 122 +++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 tools/u_boot_pylib/test_gitutil.py diff --git a/tools/u_boot_pylib/gitutil.py b/tools/u_boot_pylib/gitutil.py index 202afe745d3..e216fd8393f 100644 --- a/tools/u_boot_pylib/gitutil.py +++ b/tools/u_boot_pylib/gitutil.py @@ -206,6 +206,113 @@ def count_commits_in_range(git_dir, range_expr): return patch_count, None +def count_revs(git_dir, range_expr): + """Count revisions in a range using 'git rev-list --count'. + + Args: + git_dir (str): Directory containing git repo, or None for the + current working directory + range_expr (str): Range to count, e.g. 'upstream..branch' + Return: + int: Number of revisions in the range, or None if the range is + invalid (e.g. one of the refs does not exist). + """ + result = command.run_one('git', 'rev-list', '--count', range_expr, + cwd=git_dir, capture=True, + capture_stderr=True, raise_on_error=False) + if result.return_code: + return None + try: + return int(result.stdout.strip()) + except ValueError: + return None + + +def diff_stat(commit_range, git_dir=None): + """Get a 'git diff --stat' summary for a commit range. + + Args: + commit_range (str): Range to diff (e.g. 'upstream..HEAD') + git_dir (str): Directory containing git repo, or None for the + current working directory + Return: + str: Output of 'git diff --stat ', or '' on failure + """ + result = command.run_one('git', 'diff', '--stat', commit_range, + cwd=git_dir, capture=True, + capture_stderr=True, raise_on_error=False) + if result.return_code: + return '' + return result.stdout + + +def checkout_branch(branch, git_dir=None): + """Run 'git checkout ' from a working tree. + + Args: + branch (str): Branch name to check out + git_dir (str): Directory containing git repo, or None for the + current working directory + """ + command.output('git', 'checkout', branch, cwd=git_dir) + + +def stash_save(git_dir=None, include_untracked=False): + """Save the working tree to a new stash. + + Args: + git_dir (str): Directory containing git repo, or None for the + current working directory + include_untracked (bool): True to also stash untracked files + ('git stash -u'), False for the default tracked-only behaviour + Return: + str: Output from 'git stash' + """ + cmd = ['git', 'stash'] + if include_untracked: + cmd.append('-u') + return command.output(*cmd, cwd=git_dir) + + +def stash_pop(git_dir=None): + """Pop the most-recent stash back into the working tree. + + Args: + git_dir (str): Directory containing git repo, or None for the + current working directory + """ + command.output('git', 'stash', 'pop', cwd=git_dir) + + +def ref_exists(ref, git_dir=None): + """Check whether a git ref resolves. + + Args: + ref (str): Ref to check (branch name, tag, commit, etc.) + git_dir (str): Directory containing git repo, or None for the + current working directory + Return: + bool: True if the ref resolves, False otherwise + """ + result = command.run_one('git', 'rev-parse', '--verify', ref, + cwd=git_dir, capture=True, + capture_stderr=True, raise_on_error=False) + return result.return_code == 0 + + +def current_branch(git_dir=None): + """Get the name of the currently checked-out branch. + + Args: + git_dir (str): Directory containing git repo, or None for the + current working directory + Return: + str: Branch name, or 'HEAD' if the worktree is detached + """ + return command.output('git', 'rev-parse', '--abbrev-ref', 'HEAD', + cwd=git_dir).strip() + + def count_commits_in_branch(git_dir, branch, include_upstream=False): """Returns the number of commits in the given branch. diff --git a/tools/u_boot_pylib/test_gitutil.py b/tools/u_boot_pylib/test_gitutil.py new file mode 100644 index 00000000000..c66d68ddf1c --- /dev/null +++ b/tools/u_boot_pylib/test_gitutil.py @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2026 Simon Glass + +"""Tests for the cwd-style helpers in u_boot_pylib.gitutil""" + +import os +import shutil +import subprocess +import tempfile +import unittest + +from u_boot_pylib import gitutil + + +def _git(*args, cwd): + """Run git silently in cwd, raising on failure.""" + subprocess.run(['git', *args], cwd=cwd, check=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def _commit(cwd, fname, body, msg): + """Add a tracked file and commit it.""" + with open(os.path.join(cwd, fname), 'w') as f: + f.write(body) + _git('add', fname, cwd=cwd) + _git('-c', 'user.email=t@e', '-c', 'user.name=T', + 'commit', '-m', msg, cwd=cwd) + + +class TestGitutilHelpers(unittest.TestCase): + """Tests for the gitutil helpers introduced for review.py.""" + + def setUp(self): + self.tmpdir = tempfile.mkdtemp(prefix='gitutil-test-') + _git('init', '-q', '-b', 'main', cwd=self.tmpdir) + _commit(self.tmpdir, 'a', 'first', 'first') + + def tearDown(self): + shutil.rmtree(self.tmpdir, ignore_errors=True) + + def test_count_revs_empty_range(self): + """count_revs() returns 0 when the range contains no commits.""" + self.assertEqual(0, gitutil.count_revs(self.tmpdir, 'main..main')) + + def test_count_revs_with_commits(self): + """count_revs() returns the number of commits in the range.""" + _git('checkout', '-q', '-b', 'work', cwd=self.tmpdir) + _commit(self.tmpdir, 'b', 'second', 'second') + _commit(self.tmpdir, 'c', 'third', 'third') + self.assertEqual(2, gitutil.count_revs(self.tmpdir, 'main..work')) + + def test_count_revs_invalid_range(self): + """count_revs() returns None when one side of the range is bad.""" + self.assertIsNone( + gitutil.count_revs(self.tmpdir, 'no-such-ref..main')) + + def test_diff_stat(self): + """diff_stat() reports a stat summary for a valid range.""" + _git('checkout', '-q', '-b', 'work', cwd=self.tmpdir) + _commit(self.tmpdir, 'b', 'second', 'second') + out = gitutil.diff_stat('main..work', self.tmpdir) + self.assertIn('b', out) + self.assertIn('insert', out) + + def test_diff_stat_invalid(self): + """diff_stat() returns '' when the range is invalid.""" + self.assertEqual( + '', gitutil.diff_stat('no-such-ref..main', self.tmpdir)) + + def test_ref_exists(self): + """ref_exists() resolves valid refs and rejects missing ones.""" + self.assertTrue(gitutil.ref_exists('main', self.tmpdir)) + self.assertFalse(gitutil.ref_exists('does-not-exist', self.tmpdir)) + + def test_current_branch(self): + """current_branch() returns the active branch name.""" + self.assertEqual('main', gitutil.current_branch(self.tmpdir)) + _git('checkout', '-q', '-b', 'feature', cwd=self.tmpdir) + self.assertEqual('feature', gitutil.current_branch(self.tmpdir)) + + def test_checkout_branch(self): + """checkout_branch() swaps the active branch.""" + _git('branch', 'other', cwd=self.tmpdir) + gitutil.checkout_branch('other', self.tmpdir) + self.assertEqual('other', gitutil.current_branch(self.tmpdir)) + + def test_stash_save_pop(self): + """stash_save() saves a tracked-file change; stash_pop() restores it.""" + with open(os.path.join(self.tmpdir, 'a'), 'w') as f: + f.write('changed') + out = gitutil.stash_save(self.tmpdir) + self.assertNotIn('No local changes', out) + with open(os.path.join(self.tmpdir, 'a')) as f: + self.assertEqual('first', f.read()) + gitutil.stash_pop(self.tmpdir) + with open(os.path.join(self.tmpdir, 'a')) as f: + self.assertEqual('changed', f.read()) + + def test_stash_save_clean_tree(self): + """stash_save() reports no changes when nothing is modified.""" + out = gitutil.stash_save(self.tmpdir) + self.assertIn('No local changes', out) + + def test_stash_save_include_untracked(self): + """stash_save(include_untracked=True) covers untracked files too.""" + path = os.path.join(self.tmpdir, 'extra') + with open(path, 'w') as f: + f.write('untracked') + + # Default: untracked files are not stashed. + out = gitutil.stash_save(self.tmpdir) + self.assertIn('No local changes', out) + self.assertTrue(os.path.exists(path)) + + # With include_untracked, the file is stashed away. + out = gitutil.stash_save(self.tmpdir, include_untracked=True) + self.assertNotIn('No local changes', out) + self.assertFalse(os.path.exists(path)) + + +if __name__ == '__main__': + unittest.main() From patchwork Fri May 1 10:59:57 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2248 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633272; bh=dNClcfk6E6GmCXcD1KprZOKyVav9cH1YZiFnPOVbELY=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=azmG2hY5k8pni8hwdKvvs/ClQ57U3rcjxlXBMP90w+BEiq3B9dTZYDIysytTDByUs 2+nEWH75esKsuIK4PCGmq6ZwXx7ix3Q2cwgcLJ+PI906mDhZ0zByYmj9H2IF/Oo+Zq Zw4m4JkbwoHB5oVOLFeaZoPalyIhLykK3gttMpxM= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4A21E6A836 for ; Fri, 1 May 2026 05:01:12 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id yo7SdHxIiJbI for ; Fri, 1 May 2026 05:01:12 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633272; bh=dNClcfk6E6GmCXcD1KprZOKyVav9cH1YZiFnPOVbELY=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=azmG2hY5k8pni8hwdKvvs/ClQ57U3rcjxlXBMP90w+BEiq3B9dTZYDIysytTDByUs 2+nEWH75esKsuIK4PCGmq6ZwXx7ix3Q2cwgcLJ+PI906mDhZ0zByYmj9H2IF/Oo+Zq Zw4m4JkbwoHB5oVOLFeaZoPalyIhLykK3gttMpxM= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 316FD6A833 for ; Fri, 1 May 2026 05:01:12 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633270; bh=CPxxMGWdOGYf0tXvT7sGV9uh1JRQ/D7s/wPZtUoUNbk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qZlpq8JEO5yEFHrDckk8tAB+ZQQVwbC9wLgyiSuUVXbqxAJ/B85Vz24yd1aHd3f1H Lkj+bJRRhq7MvJf+J6hj4AIUw5xBfS7WKUl7tUI8buuytEAgnCN3t/vTInEA3SYHT8 yobNLMmyyI1Oi7swyVYPnbaJ4b7dK9D4n0qP8Vqc= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9B0836A836; Fri, 1 May 2026 05:01:10 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id WwY2QM1u5ja7; Fri, 1 May 2026 05:01:10 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633266; bh=TzLzMF3mhAK3xZqkcTy0tUrppAGewU8QOTtkCEw0SRI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sbdKIA4Iz+54QAsPSTjYVNHc6WKFV34GaMmKLaUe9y5QePPomOuBuI6jpqtuVRwGw fZzgRE70eO8GfyLAt25koiNIYngJbZC8hEWDxLtIBdmRtPM6zHSL24q07dX9Y9oDZ6 96OLYbGlCmd6wFRL7UWpOfeqtfzbZvAYg99SeHMo= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 391256A7AF; Fri, 1 May 2026 05:01:06 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 04:59:57 -0600 Message-ID: <20260501110040.1874719-6-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: HSTPBJUXTSNYKUJZQXZVRJYZKOT3E57E X-Message-ID-Hash: HSTPBJUXTSNYKUJZQXZVRJYZKOT3E57E X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 05/29] patman: Use gitutil helpers in review.py List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass review.py runs a handful of git commands directly through command.output() and command.run_one(): a 'diff --stat' for the review prompt, checkout/stash/stash-pop for branch-and-stash bookkeeping around an apply, a 'rev-parse --verify' to choose between the upstream's '/next' and '/master' branches, and 'rev-parse --abbrev-ref HEAD' to remember the current branch. The helpers added in 'u_boot_pylib: Add gitutil helpers for repo-aware git operations' cover all of these. Switch each call site to the corresponding helper: - diff_stat() for the review-prompt diffstat - checkout_branch() and stash_pop() for the cleanup path - ref_exists() for the upstream-branch probe - current_branch() and stash_save(include_untracked=True) for the pre-apply bookkeeping review.py no longer assembles 'git ...' argv lists itself. Signed-off-by: Simon Glass --- tools/patman/review.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/tools/patman/review.py b/tools/patman/review.py index f0fa4e865e4..09614c85ed7 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -983,9 +983,8 @@ async def _review_single_patch(ctx, cmt, seq, all_commits): commit_msg = body else: commit_msg = (cmt.subject + '\n' + body).strip() - ctx.diffstat = command.output('git', 'diff', '--stat', - f'{cmt.hash}~..{cmt.hash}', - cwd=ctx.repo_path).strip() + ctx.diffstat = gitutil.diff_stat(f'{cmt.hash}~..{cmt.hash}', + ctx.repo_path).strip() previous_review = ctx.previous_reviews.get(seq) prompt = _build_review_prompt(ctx, cmt.hash, seq, all_commits, @@ -1134,9 +1133,9 @@ def _git_restore(orig_branch, had_stash, repo_path): try: if orig_branch and repo_path: - command.output('git', 'checkout', orig_branch, cwd=repo_path) + gitutil.checkout_branch(orig_branch, repo_path) if had_stash: - command.output('git', 'stash', 'pop', cwd=repo_path) + gitutil.stash_pop(repo_path) except command.CommandExc: pass @@ -1582,9 +1581,7 @@ def _get_upstream_branch(args, cser): ups = cser.db.upstream_get_default() if ups: branch = f'{ups}/next' - ret = command.run_one('git', 'rev-parse', '--verify', branch, - capture=True, raise_on_error=False) - if ret.return_code: + if not gitutil.ref_exists(branch): branch = f'{ups}/master' return branch return 'origin/master' @@ -1607,9 +1604,9 @@ def _apply_and_check(ctx, link): ctx.upstream_branch, repo_path) if success: - applied = command.output('git', 'rev-list', '--count', - f'{ctx.upstream_branch}..{branch_name}', cwd=repo_path).strip() - if int(applied) == 0: + applied = gitutil.count_revs( + repo_path, f'{ctx.upstream_branch}..{branch_name}') + if not applied: success = False if not success: @@ -1774,10 +1771,9 @@ def do_review(args, pwork, cser): try: ctx.upstream_branch = _get_upstream_branch(args, cser) ctx.repo_path = gitutil.get_top_level() - orig_branch = command.output('git', 'rev-parse', '--abbrev-ref', - 'HEAD', cwd=ctx.repo_path).strip() + orig_branch = gitutil.current_branch(ctx.repo_path) try: - stash_out = command.output('git', 'stash', cwd=ctx.repo_path) + stash_out = gitutil.stash_save(ctx.repo_path) if 'No local changes' not in stash_out: had_stash = True except command.CommandExc: From patchwork Fri May 1 10:59:58 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2249 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633274; bh=yVnNaW9BgAT3V7tKtlylnUdHwP8314U6YYiQcdOn/xU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=HCtT0BgDZJO8kjzB0mZ17BExJYjmEEymVTMNPo2O/m6ja9hr8ORAYuHq4aeEpJej0 YpbppOJOrnKXWRP3fb5+dqVUieB8Y/auRrVg89wWZTiKq8X/TFiK8+ddRFuxomZQAj gwZzVhI+5fiZQyGdlWYPPWa0fqdQUefL5OJXtoos= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B72196A83B for ; Fri, 1 May 2026 05:01:14 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 5e-Dl1ZzD6BX for ; Fri, 1 May 2026 05:01:14 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633273; bh=yVnNaW9BgAT3V7tKtlylnUdHwP8314U6YYiQcdOn/xU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=kBfMGI0+atMf11VfjilUuRbf1RD5DrrAzm3I5RHjQ3VE7RLai8zoI13MTu8aS5V9w a5C/9dTiABtmRuuJFE0LAuwuPiidOAq+4g1MVqgx89X2mikqIZkTVNYNyUZjLlQVwE pISdwBev5DCmLa3QWGRq/YL+aLjSzuOmTq0E73m4= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B63FF6A834 for ; Fri, 1 May 2026 05:01:13 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633271; bh=kEXs3EccrvFlGTF+aj5p3Zj9lejY3JN1KDFhG2CH4P4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hz+a6zFw+7sqIsDRXRN6PS1F0AQJ1zRDPOLPdr299q4OArvcz+hvVibdPla73HmiA W+zTfPYquLpVJeNcvVbuMbP+0XmoHcqOeflqI8j3p1yiQKFrkKBAmXoZbY2djFddN+ FtZLxtCfpL36ZcufDLyBP2cqmlRmAbClYt46/o7k= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C7ADA6A82E; Fri, 1 May 2026 05:01:11 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id gglqyGxkv4dP; Fri, 1 May 2026 05:01:11 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633271; bh=clGqQxRZQnfao/6Arczm4x659A+xgNsiVG7239sNS3U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kB3+iCAZ3zjmWnYosSIY8A6CEi+yfVcoAq6SEp0R74MFxX2cAp60muBEt+NKZCyLm nxFP3TwQfmUA5XqAKEkKOaVbJQdmrhUJJOE1wV7nMVrpOLfTFz/k5ZEelIGgT9ezb5 EgcLLZTI90GC55aeJA+j5VVV1zrYGQEw7YkxHlWE= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 2FD466A7AF; Fri, 1 May 2026 05:01:11 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 04:59:58 -0600 Message-ID: <20260501110040.1874719-7-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 2XXUBACQMWU3AQDORASOVHUQBZICKQR3 X-Message-ID-Hash: 2XXUBACQMWU3AQDORASOVHUQBZICKQR3 X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 06/29] patman: Use patchwork.py for all HTTP access in review.py List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass review.py reaches into patchwork in two ways: via public Patchwork methods (get_series, get_cover_comments, etc.) and, in a few remaining places, by either constructing patchwork URLs itself or calling Patchwork._request() directly. The direct paths are: - fetch_mbox() builds 'series//mbox/' itself and downloads via aiohttp, so the apply_series chain takes 'pwork_url' (a bare string) rather than the Patchwork instance. - _do_learn_voice() pokes pwork._request(client, 'projects/') to find the list_email for the configured project. Add a get_series_mbox() method to Patchwork so the mbox URL is expressed once, and a search_patches() public wrapper around the existing 'patches/?project=...&q=...' subpath. Then switch review.py over: fetch_mbox(), apply_series(), and apply_series_sync() now take the Patchwork instance, _apply_and_check() passes ctx.pwork instead of ctx.pwork.url, and _do_learn_voice() uses pwork.get_projects(). review.py no longer constructs patchwork URLs or calls the private _request(). Signed-off-by: Simon Glass --- tools/patman/patchwork.py | 46 +++++++++++++++++++++++++++++++++++++++ tools/patman/review.py | 42 +++++++++++++++-------------------- 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py index 5613cbb3383..46bacee50d2 100644 --- a/tools/patman/patchwork.py +++ b/tools/patman/patchwork.py @@ -449,6 +449,52 @@ class Patchwork: """ return await self._request(client, f'series/{link}/') + async def get_series_mbox(self, client, link): + """Download the raw mbox file for a series. + + The mbox URL lives directly under '/series//mbox/' rather + than the JSON API, so this fetches the bytes directly rather + than routing through self._request(). + + Args: + client (aiohttp.ClientSession): Session to use + link (str): Patchwork series link/ID + + Returns: + bytes: Raw mbox content + + Raises: + ValueError: if the download fails + """ + self.request_count += 1 + full_url = f'{self.url}/series/{link}/mbox/' + async with self.semaphore: + async with client.get(full_url) as response: + if response.status != 200: + raise ValueError( + f'Failed to download mbox: HTTP {response.status}') + return await response.read() + + async def search_patches(self, client, query, project_id=None, + per_page=20): + """Search patches by free-text query, most-recent first. + + Args: + client (aiohttp.ClientSession): Session to use + query (str): Text to match against patch titles + project_id (int): Project ID to scope to, or None to use + self.proj_id + per_page (int): Maximum number of results to return + + Returns: + list of dict: Patch records matching the query + """ + if project_id is None: + project_id = self.proj_id + subpath = (f'patches/?project={project_id}&q={query}' + f'&order=-date&per_page={per_page}') + return await self._request(client, subpath) + async def get_patch(self, client, patch_id): """Read information about a patch diff --git a/tools/patman/review.py b/tools/patman/review.py index 09614c85ed7..eb2e46a0431 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -100,11 +100,11 @@ class ReviewContext: # pylint: disable=R0902 return self.series_data.get('date', '') -async def fetch_mbox(pwork_url, link): +async def fetch_mbox(pwork, link): """Download the series mbox file from patchwork Args: - pwork_url (str): Patchwork server URL + pwork (Patchwork): Patchwork instance to fetch from link (str): Patchwork series link/ID Returns: @@ -113,19 +113,13 @@ async def fetch_mbox(pwork_url, link): Raises: ValueError: if the download fails """ - url = f'{pwork_url}/series/{link}/mbox/' - tout.notice(f'Downloading mbox from {url}') + tout.notice(f'Downloading mbox for series {link} from {pwork.url}') mbox_path = os.path.join(tempfile.gettempdir(), f'patman_review_{link}.mbox') async with aiohttp.ClientSession() as client: - async with client.get(url) as response: - if response.status != 200: - raise ValueError( - f'Failed to download mbox: HTTP {response.status}') - - content = await response.read() - if not content: - raise ValueError(f'Empty mbox downloaded from {url}') + content = await pwork.get_series_mbox(client, link) + if not content: + raise ValueError(f'Empty mbox downloaded for series {link}') tools.write_file(mbox_path, content) tout.notice(f'Downloaded {len(content)} bytes to {mbox_path}') @@ -205,7 +199,7 @@ IMPORTANT: ''' -async def apply_series(pwork_url, link, branch_name, upstream_branch, +async def apply_series(pwork, link, branch_name, upstream_branch, repo_path): """Download and apply a patch series to a new local branch @@ -213,7 +207,7 @@ async def apply_series(pwork_url, link, branch_name, upstream_branch, resolution. Args: - pwork_url (str): Patchwork server URL + pwork (Patchwork): Patchwork instance to fetch from link (str): Patchwork series link/ID branch_name (str): Name for the new branch upstream_branch (str): Branch to base from @@ -228,7 +222,7 @@ async def apply_series(pwork_url, link, branch_name, upstream_branch, return False, None # Download the mbox - mbox_path = await fetch_mbox(pwork_url, link) + mbox_path = await fetch_mbox(pwork, link) # Build the prompt and run the agent prompt = _build_apply_prompt(mbox_path, branch_name, upstream_branch) @@ -1058,11 +1052,11 @@ def review_patches_sync(ctx): return loop.run_until_complete(review_patches(ctx)) -def apply_series_sync(pwork_url, link, branch_name, upstream_branch, repo_path): +def apply_series_sync(pwork, link, branch_name, upstream_branch, repo_path): """Synchronous wrapper for apply_series() Args: - pwork_url (str): Patchwork server URL + pwork (Patchwork): Patchwork instance to fetch from link (str): Patchwork series link/ID branch_name (str): Name for the new branch upstream_branch (str): Branch to base from @@ -1073,7 +1067,7 @@ def apply_series_sync(pwork_url, link, branch_name, upstream_branch, repo_path): """ loop = asyncio.get_event_loop() return loop.run_until_complete(apply_series( - pwork_url, link, branch_name, upstream_branch, repo_path)) + pwork, link, branch_name, upstream_branch, repo_path)) def search_series(pwork, title): @@ -1273,12 +1267,10 @@ def _do_learn_voice(args, pwork): if pwork and pwork.proj_id: async def _get_list_email(): - async with aiohttp.ClientSession() as client: - # pylint: disable=W0212 - projects = await pwork._request(client, 'projects/') - for proj in projects: - if proj['id'] == pwork.proj_id: - return proj.get('list_email') + projects = await pwork.get_projects() + for proj in projects: + if proj['id'] == pwork.proj_id: + return proj.get('list_email') return None loop = asyncio.get_event_loop() @@ -1600,7 +1592,7 @@ def _apply_and_check(ctx, link): """ branch_name = f'review{ctx.series_id}' repo_path = gitutil.get_top_level() - success, branch_name = apply_series_sync(ctx.pwork.url, link, branch_name, + success, branch_name = apply_series_sync(ctx.pwork, link, branch_name, ctx.upstream_branch, repo_path) if success: From patchwork Fri May 1 10:59:59 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2250 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633279; bh=SgW2/2QxZ8ufnYFCSQtKhZAR4qCms/QYxQhY+TGm7rI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=A0gIq4PoNSlXRfebcNtiOoz205hcGR/79UeQrWITkM9VprxNz5XnCcIwIyf3vnG1T 9U5GzqCCzeKpXnZv5ALew0bFRIK8U/u4WbvgHwj4TeGqLGyzRXCQNdmGIM9ZRA8D4S kZvd+k1/d9kQrisOcYlgURJ//68AsCeKjKCvTIYw= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 38A176A834 for ; Fri, 1 May 2026 05:01:19 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id gRqyHtRL0JAl for ; Fri, 1 May 2026 05:01:19 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633279; bh=SgW2/2QxZ8ufnYFCSQtKhZAR4qCms/QYxQhY+TGm7rI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=A0gIq4PoNSlXRfebcNtiOoz205hcGR/79UeQrWITkM9VprxNz5XnCcIwIyf3vnG1T 9U5GzqCCzeKpXnZv5ALew0bFRIK8U/u4WbvgHwj4TeGqLGyzRXCQNdmGIM9ZRA8D4S kZvd+k1/d9kQrisOcYlgURJ//68AsCeKjKCvTIYw= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 28C9D6A833 for ; Fri, 1 May 2026 05:01:19 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633277; bh=xWfjcDmD6MXsH0+ja7iuIjQcJVv6/pckitsAHSRoPOU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YkZpSqCXU58kr4mMRgLFBpqFegHhEq7AInutY40xb5s44+IqhrKY/qinHwM1hoo7s rm3UNiWqUL7ACNZLT1jSWN6Du+SynfwjFYMpdITwf5Z6NuyZxB7lF8XWEqfu3bz/8c c3xqwS1Wwe0IygJps1L14mtnmGrchF6cq/OvjSCk= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0B5076A78B; Fri, 1 May 2026 05:01:17 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 6vpP24VtoJar; Fri, 1 May 2026 05:01:16 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633272; bh=J2lqJ1nea3ZRx6TP8b6uYVwojN+aUhFfzFvLilhjTiw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=b5Lw3rNYa2+D7j/bcAnw6udfHSIDe8aqwTOWEpullwQEm2nhSoAtiZghyWyT//J/Z D8C4Z/nFqXibBUtkGpFQZoPjdDD2acoDi6GUXgvHsPF6RcM/atmjR0Zf+yMriqYjtk jHWKhDxRLUl7owNfXAxL3U69yxCJ/56XLz3CeGt0= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 65CC76A7AF; Fri, 1 May 2026 05:01:12 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 04:59:59 -0600 Message-ID: <20260501110040.1874719-8-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: EMX6EJSQWZLDOB6IK52FT6L3FXJHYW5A X-Message-ID-Hash: EMX6EJSQWZLDOB6IK52FT6L3FXJHYW5A X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 07/29] patman: Handle email-mismatch warning from checkpatch List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The checkpatch "From:/Signed-off-by: email address mismatch" warning has no file or line information, but is not in the list of known no-file messages. This causes a "failed to find file / line information" error during parsing. Add 'email address mismatch' to the no-file pattern list so patman reports it cleanly as a patch-level warning. Signed-off-by: Simon Glass --- tools/patman/checkpatch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/patman/checkpatch.py b/tools/patman/checkpatch.py index 6f99a36a08d..b6ddaad0924 100644 --- a/tools/patman/checkpatch.py +++ b/tools/patman/checkpatch.py @@ -94,6 +94,7 @@ def check_patch_parse_one_message(message): no_file_match = any(s in message for s in [ '\nSubject:', 'Missing Signed-off-by:', 'does MAINTAINERS need updating', + 'email address mismatch', 'should have a', ]) From patchwork Fri May 1 11:00:00 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2251 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633284; bh=+L3nNBaQo1vV6IFY8YaIbiHYU0n2x0aU3zu8VnQ+q5E=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=DYtpmU5KxHJDzTs4D8XcDx63Ewaho/R+fhILyB/BVUQJ/MaGicQn6RwkWFxq91SIs 50zHYgDhyAxxjX9OfeAS3HKrbY8M8RXwgfthwazh2fn3PtKLyhxpb03T06YXO24SLc /3u0tnWbusXvb5SzH3RkB1BpdgLQrjpYQanSEJl8= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 055D36A834 for ; Fri, 1 May 2026 05:01:24 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 8gmjTy6d7JJU for ; Fri, 1 May 2026 05:01:23 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633283; bh=+L3nNBaQo1vV6IFY8YaIbiHYU0n2x0aU3zu8VnQ+q5E=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=jirbI8ubvXpzqRK9h+StywVwGnuge4Wlm5SYyqvQOf8aqDbZVtVi1uf88iUCJgren TlTvbznfsvChm4tGJz8go94vOAOVr+7ug5Wqps9rDmZJh+7Fn38mEXsi/qSIH3LceX 36dsv7HVGWErAz85vWFAvcAgZSam+/oknspRd+Xo= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E6E6D6A82E for ; Fri, 1 May 2026 05:01:23 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633281; bh=ZFnf4Uu0WTADdP+6wg57XZu6SxepHpoDrVZ0uAR9axc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qQCzvSS7IfWsPQZ1wtx2phKOLLMbccB5kyLvvm7LvL/Tp5SWuf3mpRpSbDul5WTmF TvKQs+VwgPyGqYkgI0/ceiXTrkV68VRqVIHHJ9lx/dFwx02Ga5k1uwA8M9LszHl0IV pWOVVb+xrXHoQHZeImP0mZpmT+LHMcMeo9cuNC9k= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B08BF6A82E; Fri, 1 May 2026 05:01:21 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 205eRdsgvsMf; Fri, 1 May 2026 05:01:21 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633277; bh=l3xoWo7BuuvsjUDKC8iqYVfeIRPc95lDuX1Nbu0VKPU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LjgAiXP+GQ6dthjkrDTIWzla5s/zgeCSJ8BqrTjTyDSbtxXSzmIdQUdGOj/PKyYRG ulizjQJxPM8xEhAvj6njNoI5MvF+Rn90X4YheUv34RUaRCqXD7JYsDYmeBDiKy/OQs 3bkegj1hcxjPxD1VNm30i3LnkakFwv3U6qm/yrHs= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 509C96A7AF; Fri, 1 May 2026 05:01:17 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:00 -0600 Message-ID: <20260501110040.1874719-9-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: ITG4IY5MZ7FL4DIDDXQRUI2YY3YYQF2I X-Message-ID-Hash: ITG4IY5MZ7FL4DIDDXQRUI2YY3YYQF2I X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 08/29] patman: Fix patchwork search for titles containing '+' List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The query_series() method replaces spaces with '+' for the patchwork API query string. This means a literal '+' in a series title (e.g. 'FS and FW loader + FIP loader') becomes a space, corrupting the search. Use urllib.parse.quote() for proper URL encoding so that special characters like '+' are escaped as '%2B'. Signed-off-by: Simon Glass --- tools/patman/patchwork.py | 4 +++- tools/patman/test_cseries.py | 39 ++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py index 46bacee50d2..f71214d4b8f 100644 --- a/tools/patman/patchwork.py +++ b/tools/patman/patchwork.py @@ -7,6 +7,7 @@ import asyncio import re +from urllib.parse import quote_plus import aiohttp from collections import namedtuple @@ -281,8 +282,9 @@ class Patchwork: Return: list of series matches, each a dict, see get_series() """ - query = desc.replace(' ', '+') + query = quote_plus(desc, safe=':') subpath = f'series/?project={self.proj_id}&q={query}' + tout.info(f"Searching for '{desc}'") tout.debug(f' GET {self.url}/api/1.2/{subpath}') return await self._request(client, subpath) diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 7d6a6179c1e..2d56b409a57 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -1101,16 +1101,19 @@ Tested-by: Mary Smith # yak self.assertFalse(cser.project_get()) cser.project_set(pwork, 'U-Boot', quiet=True) - self.assertEqual( - (self.SERIES_ID_SECOND_V1, None, 'second', 1, - 'Series for my board'), - cser.link_search(pwork, 'second', 1)) + with terminal.capture(): + self.assertEqual( + (self.SERIES_ID_SECOND_V1, None, 'second', 1, + 'Series for my board'), + cser.link_search(pwork, 'second', 1)) with terminal.capture(): cser.increment('second') - self.assertEqual((457, None, 'second', 2, 'Series for my board'), - cser.link_search(pwork, 'second', 2)) + with terminal.capture(): + self.assertEqual( + (457, None, 'second', 2, 'Series for my board'), + cser.link_search(pwork, 'second', 2)) def test_series_link_auto_name(self): """Test finding the patchwork link for a cseries with auto name""" @@ -1197,13 +1200,15 @@ Tested-by: Mary Smith # yak self.assertFalse(cser.project_get()) cser.project_set(pwork, 'U-Boot', quiet=True) - self.assertEqual( - (self.SERIES_ID_SECOND_V1, None, 'second', 1, - 'Series for my board'), - cser.link_search(pwork, 'second', 1)) - self.assertEqual((457, None, 'second', 2, 'Series for my board'), - cser.link_search(pwork, 'second', 2)) - res = cser.link_search(pwork, 'second', 3) + with terminal.capture(): + self.assertEqual( + (self.SERIES_ID_SECOND_V1, None, 'second', 1, + 'Series for my board'), + cser.link_search(pwork, 'second', 1)) + self.assertEqual( + (457, None, 'second', 2, 'Series for my board'), + cser.link_search(pwork, 'second', 2)) + res = cser.link_search(pwork, 'second', 3) self.assertEqual( (None, [{'id': self.SERIES_ID_SECOND_V1, 'name': 'Series for my board', @@ -1444,7 +1449,9 @@ Tested-by: Mary Smith # yak with terminal.capture() as (out, _): self.run_args('series', 'autolink-all', '-a', '--no-update', pwork=pwork) - itr = iter(out.getvalue().splitlines()) + lines = [ln for ln in out.getvalue().splitlines() + if not ln.startswith('Searching for ')] + itr = iter(lines) self.assertEqual( '1 series linked, 1 already linked, 1 not found (3 requests)', next(itr)) @@ -3784,7 +3791,9 @@ Date: .* cser.set_fake_time(h_sleep) with terminal.capture() as (out, _): cser.link_auto(pwork, 'second3', 3, True, 50) - itr = iter(out.getvalue().splitlines()) + lines = [ln for ln in out.getvalue().splitlines() + if not ln.startswith('Searching for ')] + itr = iter(lines) # Matches shown only once (they don't change between retries) self.assertEqual( From patchwork Fri May 1 11:00:01 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2252 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633288; bh=tYNHEXmBLZc5IoYI+FmhTU0vywTrlsI3Ek4TCPlh6Qg=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=aNoG7zLwrzEksuAjm3iYD7qUao1XolXqjxpt6iNnvaj/vxNg47ajQXNS5aeKdpxI2 6THEvaYhX0QUrpfVJ9rXYvtXeWuRfMoZGqHJGeZyBRIGjIOCwFMgjJXyObiZuaoVc8 /mh6WCLEf9FMnUrCwOOL63vpgk5dN2DdB9wJWRzc= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 71B0F6A834 for ; Fri, 1 May 2026 05:01:28 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id ugMvhkbJkj4S for ; Fri, 1 May 2026 05:01:28 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633288; bh=tYNHEXmBLZc5IoYI+FmhTU0vywTrlsI3Ek4TCPlh6Qg=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=aNoG7zLwrzEksuAjm3iYD7qUao1XolXqjxpt6iNnvaj/vxNg47ajQXNS5aeKdpxI2 6THEvaYhX0QUrpfVJ9rXYvtXeWuRfMoZGqHJGeZyBRIGjIOCwFMgjJXyObiZuaoVc8 /mh6WCLEf9FMnUrCwOOL63vpgk5dN2DdB9wJWRzc= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5D5E06A82E for ; Fri, 1 May 2026 05:01:28 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633286; bh=cQkJ+raHd8niiOKsMpmmbViJeIUm0J5AEFIxdRcBTM4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rU4nIgqObEHH36uCQiEgxL2tzCaymXvHvCCEfM5ozY5c49uSwgd/xF7/moKmTVa6I CwkU2rwXqeiNGmAvrr0ci4Q6K1l45zQQyn0YQSlnTnE1Gff7/BFCcLxpEXSaYDAS7k SB4KK/Yl3tE9JBg6oeRdt9kExglmhFI0N2is0V/A= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 668566A7AF; Fri, 1 May 2026 05:01:26 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id fNct4b2TrVg0; Fri, 1 May 2026 05:01:26 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633282; bh=sRaC+WVfu5bXCTpiWhDxgqyQOq/OYVyG+GLwPa5WlQs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AFht/g4MgfbkreXL80MoLhWyxHasTIbtNywccX/F+C/TN5FQtkw0C6wje1q0sXYoO 1lU5DKdnCK9MhKBLpSL5i7J++DFwM1EXPz2pbrUjgzBN4u9GpO9OqGbflEqWlZHaNH BxlKkifTieFRFnujCSylQVLdYmhAbFHXDitYnHAg= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 3CF836A78B; Fri, 1 May 2026 05:01:22 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:01 -0600 Message-ID: <20260501110040.1874719-10-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: MUZ4RHYJK3S6PT5476HJPQ3GSSWEZN3N X-Message-ID-Hash: MUZ4RHYJK3S6PT5476HJPQ3GSSWEZN3N X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 09/29] patman: Add review display and colour to series info List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Reviews collected by 'patman review' land in the database but there is no way to read them back from the command line - 'series info' shows progress and tags but skips the review text entirely. Anyone wanting to revisit a review has to dig through the patchwork web interface or query the database by hand. Extend 'series info' with a '-r' flag that prints the review text for each patch alongside the existing summary. The flag accepts an optional list of patch numbers (e.g. '-r 1 3') so a single patch can be inspected without scrolling past the rest of the series. The output is piped through the pager so long reviews scroll cleanly when stdout is a terminal. To make the dump readable at a glance, label fields are shown in blue, version headers in yellow, accepted and approved markers in green, quoted diff lines in magenta, review comments in white and notes in cyan. Signed-off-by: Simon Glass --- tools/patman/cmdline.py | 4 +- tools/patman/control.py | 3 +- tools/patman/cseries.py | 113 ++++++++++++++++++++++++++++++---------- 3 files changed, 90 insertions(+), 30 deletions(-) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index cbb6e4742b7..13aa63b76c8 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -294,7 +294,9 @@ def add_series_subparser(subparsers): series_subparsers.add_parser('get-link') series_subparsers.add_parser('inc') - series_subparsers.add_parser('info') + info = series_subparsers.add_parser('info') + info.add_argument('-r', '--reviews', nargs='*', type=int, default=None, + help='Show review text (optionally for specific patches)') ls = series_subparsers.add_parser('ls', aliases=['list']) _add_archived(ls) diff --git a/tools/patman/control.py b/tools/patman/control.py index 28d53ef60d2..ac3a27a3e0e 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -202,7 +202,8 @@ def do_series(args, test_db=None, pwork=None, cser=None): elif args.subcmd == 'dec': cser.decrement(args.series, args.dry_run) elif args.subcmd == 'info': - cser.show_info(args.series) + cser.show_info(args.series, + show_reviews=getattr(args, 'reviews', None)) elif args.subcmd == 'gather': cser.gather(pwork, args.series, args.version, args.show_comments, args.show_cover_comments, args.gather_tags, diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index 3af9ef19eab..74e86eeeb37 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -13,6 +13,7 @@ import pygit2 from u_boot_pylib import cros_subprocess from u_boot_pylib import gitutil from u_boot_pylib import terminal +from u_boot_pylib.terminal import tprint from u_boot_pylib import tools from u_boot_pylib import tout @@ -892,24 +893,42 @@ class Cseries(cser_helper.CseriesHelper): tout.notice(f"No review notes for '{ser.name}'") return for version, notes in all_notes: - terminal.tprint(f'\n--- v{version} ---', + tprint(f'\n--- v{version} ---', colour=terminal.Color.YELLOW) print(notes) print() - def show_info(self, series): + def show_info(self, series, show_reviews=None): """Show detailed information about a series and all its versions Args: series (str): Series name, or None for current branch + show_reviews (list of int or None): If not None, show review + text. An empty list means all patches; otherwise only + the listed patch numbers (1-based). """ ser, _ = self._parse_series_and_version(series, None) if not ser.idnum: raise ValueError(f"Series '{ser.name}' not found in database") - print(f"Series: {ser.name}") - print(f" Description: {ser.desc}") - print(f" Upstream: {ser.upstream or '(none)'}") + with terminal.pager(): + self._show_info(ser, show_reviews) + + def _show_info(self, ser, show_reviews): + """Show series info (called within a pager context) + + Args: + ser (Series): Series object with idnum set + show_reviews (list of int or None): Patch numbers to show + reviews for, or empty list for all, or None for none + """ + col = self.col + tprint('Series: ', newline=False, colour=col.BLUE, col=col) + tprint(ser.name, colour=col.WHITE, col=col) + tprint(' Description: ', newline=False, colour=col.BLUE, col=col) + tprint(ser.desc, col=col) + tprint(' Upstream: ', newline=False, colour=col.BLUE, col=col) + tprint(ser.upstream or '(none)', col=col) versions = self.db.ser_ver_get_for_series(ser.idnum) if not isinstance(versions, list): @@ -917,32 +936,70 @@ class Cseries(cser_helper.CseriesHelper): for sv in versions: link_str = sv.link or '(none)' - print(f"\n Version {sv.version}:") - print(f" Link: {link_str}") - print(f" Description: {sv.desc or '(none)'}") + tprint(f'\n Version {sv.version}:', colour=col.YELLOW, col=col) + tprint(' Link: ', newline=False, colour=col.BLUE, col=col) + tprint(link_str, col=col) + tprint(' Description: ', newline=False, colour=col.BLUE, col=col) + tprint(sv.desc or '(none)', col=col) if sv.name: - print(f" Cover: {sv.name}") + tprint(' Cover: ', newline=False, colour=col.BLUE, col=col) + tprint(sv.name, col=col) if sv.archive_tag: - print(f" Archive tag: {sv.archive_tag}") + tprint(' Archive tag: ', newline=False, colour=col.BLUE, + col=col) + tprint(sv.archive_tag, col=col) - # Show patches - try: - pclist = self.db.pcommit_get_list(sv.idnum) - print(f" Patches: {len(pclist)}") - for pc in pclist: - state = f' [{pc.state}]' if pc.state else '' - print(f" {pc.seq + 1}: {pc.subject}{state}") - except (ValueError, AttributeError): - pass - - # Show notes if any - if sv.notes: - lines = sv.notes.strip().splitlines() - print(f" Notes: {lines[0]}") - for line in lines[1:3]: - print(f" {line}") - if len(lines) > 3: - print(f" ... ({len(lines)} lines)") + self._show_version_info(sv, show_reviews) + + def _show_version_info(self, sv, show_reviews): + """Show patches, reviews and notes for one series version + + Args: + sv (SerVer): Series-version record to display + show_reviews (list of int or None): Patch numbers to show + reviews for, or empty list for all, or None for none + """ + col = self.col + + # Show patches + pclist = self.db.pcommit_get_list(sv.idnum) + tprint(' Patches: ', newline=False, colour=col.BLUE, col=col) + tprint(str(len(pclist)), col=col) + for pc in pclist: + state = f' [{pc.state}]' if pc.state else '' + colour = col.GREEN if pc.state == 'accepted' else None + tprint(f' {pc.seq + 1}: {pc.subject}{state}', colour=colour, + col=col) + + # Show reviews if requested + if show_reviews is not None: + reviews = self.db.review_get_for_version(sv.idnum) + if reviews: + shown = [r for r in reviews if not show_reviews + or r.seq in show_reviews] + tprint(' Reviews: ', newline=False, colour=col.BLUE, col=col) + tprint(f'{len(shown)}/{len(reviews)}', col=col) + for rev in shown: + colour = col.GREEN if rev.approved else col.YELLOW + stat = 'approved' if rev.approved else 'comments' + tprint(f' Patch {rev.seq}: [{stat}]', colour=colour, + col=col) + for line in rev.body.splitlines(): + if line.startswith('> '): + tprint(f' {line}', colour=col.MAGENTA, + col=col) + else: + tprint(f' {line}', colour=col.WHITE, col=col) + + # Show notes if any + if sv.notes: + lines = sv.notes.strip().splitlines() + tprint(' Notes: ', newline=False, colour=col.BLUE, col=col) + tprint(lines[0], colour=col.CYAN, col=col) + for line in lines[1:3]: + tprint(f' {line}', colour=col.CYAN, col=col) + if len(lines) > 3: + tprint(f' ... ({len(lines)} lines)', col=col) def set_upstream(self, series, ups, dry_run=False): """Set the upstream for a series From patchwork Fri May 1 11:00:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2253 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633290; bh=39M3JYWfdwLSffdrqTANkLuDCO4p04dvMSDbTRj1exI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=GcHz+Dn8ClB11ZevRBjFUIVrFvXmwUBVtHIYQpmJS1azLDAfgFd1XXT4l382xPpkm f/4GwAiTYTY778zNqeAWN7/l/a5ivT290sHvJdxUg1tIbdz9q1RJzFfsHbvDyHnE+z 0gaA3Via8OYeFzofhX32PRD2B8uCJlQdUInY7M3g= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id CDFAF6A83F for ; Fri, 1 May 2026 05:01:30 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id cvdY-M6O_3bd for ; Fri, 1 May 2026 05:01:30 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633288; bh=39M3JYWfdwLSffdrqTANkLuDCO4p04dvMSDbTRj1exI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=lodjf89fu8W18tzlKVHSNlXsLe7PBVY03449RDJ27oCezllBIwNnN+jbI6hVFdDJw LmTFgdcH1H0soB29BWh8sb1bnxcjdoooGKw+zgNaIxT3xqIsSFmmwkCQ3ESjeS6+fG w1DKfDNz+mgB1siIZqNBOLEr/3UcQ77b70KlKwsk= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C70536A842 for ; Fri, 1 May 2026 05:01:28 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633287; bh=+J3/aqy1Nj/XvQSkYYb5gCZnTRQxKliow/aTenjITyQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n53eSF1ro4U/5qib7pbfQM4MuJoWVAmhoPfCWk3ZSWQHcpbaF2Weokj8xUCccQtP9 ARr/KFZxsBEwayI+21/uJ4dq1OpIgRJOpfkQ8gUVc6KIMlpsrQBYp5JXb4oDS64iPX R5giTpsHUCJL35A4X5lQJQ01uvgUabOaZhCEL24E= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1A28A6A82E; Fri, 1 May 2026 05:01:27 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id M-dMUsk0bDkB; Fri, 1 May 2026 05:01:27 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633286; bh=pT0Rnj47+oDflFStWK0OB+j0tDLf3Bi1OHyPgzD/BLM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HfiRjmKKqVgpLO8UaqMziMaAC/WZJybTxf8pT4QYzxRnk/AwfZP5wieNDBRxLrNmi qzLz7gzXsiPl0k0UY73tNwlacddNDuWjIjQT4azD1ULHOkWmqr3unMPK3N1JJyhV+5 FD937W9a7QbCcfoTt00n4TgxyxvQl7ON/AVVDGSE= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 9856C6A78B; Fri, 1 May 2026 05:01:26 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:02 -0600 Message-ID: <20260501110040.1874719-11-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: H4JOL67EVMS6R7KS5YF44H5CGNE2HYWS X-Message-ID-Hash: H4JOL67EVMS6R7KS5YF44H5CGNE2HYWS X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 10/29] patman: Use run_interactive() for git send-email List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The output of 'git send-email' arrives in the wrong order: the 'Send this email?' prompt shows up before the email headers because email_patches() runs the command through cros_subprocess with an echo callback, which delivers PTY chunks unpredictably. Switch to run_interactive(), which keeps the child's stdout and stderr on a real PTY so the prompt and headers appear in the order git produces them. Signed-off-by: Simon Glass --- tools/u_boot_pylib/gitutil.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tools/u_boot_pylib/gitutil.py b/tools/u_boot_pylib/gitutil.py index e216fd8393f..a7db37fd7df 100644 --- a/tools/u_boot_pylib/gitutil.py +++ b/tools/u_boot_pylib/gitutil.py @@ -655,14 +655,8 @@ def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname, cmd += args num_sent = 0 if not dry_run: - def echo_output(_stream, data): - os.write(sys.stdout.fileno(), data) - return False - - result = command.run_pipe( - [cmd], capture=True, output_func=echo_output, - raise_on_error=False, cwd=cwd, merge_stderr=True) - num_sent = result.stdout.count('Result: ') + captured = command.run_interactive(cmd, cwd) + num_sent = captured.count('Result: ') cmd_str = ' '.join([f'"{x}"' if ' ' in x and '"' not in x else x for x in cmd]) return cmd_str, num_sent From patchwork Fri May 1 11:00:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2254 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633293; bh=LYUNpjkDxQAy623DYQFLdZ+UY+bQEJ6/fecjIYGY5WY=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=spBVNEWxsT3GKTz2alQrC5ztW0jtQHcF1HREK+uvcKTb3tr7NIIN6r86SZXunyKD4 YkWrEBLS8FOBrBRUXtZqmWa+1IiGDdUy1MInhP44EMDTggRfYDdsC3UYVuwPaTUerh n4w4HWJ50kUnqJ2TF6hwkx6Dt1Mk/s5iGQBaQ/wY= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BB8426A836 for ; Fri, 1 May 2026 05:01:33 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id nhqjeIMKTHqh for ; Fri, 1 May 2026 05:01:33 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633293; bh=LYUNpjkDxQAy623DYQFLdZ+UY+bQEJ6/fecjIYGY5WY=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=spBVNEWxsT3GKTz2alQrC5ztW0jtQHcF1HREK+uvcKTb3tr7NIIN6r86SZXunyKD4 YkWrEBLS8FOBrBRUXtZqmWa+1IiGDdUy1MInhP44EMDTggRfYDdsC3UYVuwPaTUerh n4w4HWJ50kUnqJ2TF6hwkx6Dt1Mk/s5iGQBaQ/wY= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AA2B26A833 for ; Fri, 1 May 2026 05:01:33 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633293; bh=jaR3qSDzkJyHhnrrVcN2mX7OE/X62RNmFe334Xx6DMU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jF1k96v1Jd9vBFJoHbnDcPwNg3IKo0aGI8yhH7T/pBJRfZiw2jFwh8mhoA3Leu3Yf 57c3w406AwqZsgT+05fZLxNhE46+hqv77omwgryvSLkO5PMkxDh+HxzFIWOXXm7NMt sguAsZBZu6MZuVDIIITlZfyzIwKBDS1A1S2VMRKA= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 34CF36A82E; Fri, 1 May 2026 05:01:33 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id dUZha7CCAfgo; Fri, 1 May 2026 05:01:33 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633287; bh=YniseeuF3ucIqAGaKY9Bwu731/WFkxyAcTVCVOnsjjk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LnnsgaTHRKSpBoAfuiL3Udp7Al/mZLH42WVds1Eda01TxF+X+Jj/Oo0P3tylQ8/vE k23mJLnHZAJE21gpvw39vdIyFBAzZ4MJoWroEcQMVCbJjJqIWhuHj7L63QNhHF8C/g EM5IDEbgkWvfT/U3MyFTJ10dsNk5Oiy+zDEhCuAs= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id AAFED6A78B; Fri, 1 May 2026 05:01:27 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:03 -0600 Message-ID: <20260501110040.1874719-12-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: BUZTMI23AEXD7EMR25FW4S2FASU2SEQR X-Message-ID-Hash: BUZTMI23AEXD7EMR25FW4S2FASU2SEQR X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 11/29] patman: Detect when a series is accepted upstream List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass After gathering tags from patchwork, check whether all patches in the series version have state 'accepted'. If so, notify the user that the series has been applied upstream. This runs after both 'patman series gather' (single series) and 'patman series gather-all' (all series), giving immediate visibility when a maintainer applies the patches. Signed-off-by: Simon Glass --- tools/patman/cser_helper.py | 23 +++++++++++++++++++++++ tools/patman/cseries.py | 7 +++++++ 2 files changed, 30 insertions(+) diff --git a/tools/patman/cser_helper.py b/tools/patman/cser_helper.py index 31a5e3663dc..032d5218e9e 100644 --- a/tools/patman/cser_helper.py +++ b/tools/patman/cser_helper.py @@ -1378,6 +1378,29 @@ class CseriesHelper: return updated, 1 if cover else 0 + def check_applied(self, svid, series_name, version): + """Check if all patches in a series version are accepted + + If every patch has state 'accepted', notify the user that the + series has been applied upstream. + + Args: + svid (int): Ser/ver ID + series_name (str): Series name (for display) + version (int): Version number (for display) + + Returns: + bool: True if all patches are accepted + """ + pclist = self.db.pcommit_get_list(svid) + if not pclist: + return False + if all(pc.state == 'accepted' for pc in pclist): + tout.notice(f"Series '{series_name}' v{version}: all patches " + 'accepted upstream') + return True + return False + async def _gather(self, pwork, link, show_cover_comments): """Sync the series status from patchwork diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index 74e86eeeb37..5ffc2989dd5 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -1343,6 +1343,7 @@ class Cseries(cser_helper.CseriesHelper): if not dry_run: self.commit() + self.check_applied(svid, ser.name, version) else: self.rollback() tout.info('Dry run completed') @@ -1377,8 +1378,14 @@ class Cseries(cser_helper.CseriesHelper): f"{tot_cover} cover letter{'s' if tot_cover != 1 else ''} " f'updated, {missing} missing ' f"link{'s' if missing != 1 else ''} ({requests} requests)") + applied = [] if not dry_run: self.commit() + for svid, sync in to_fetch.items(): + if self.check_applied(svid, sync.series_name, sync.version): + applied.append(sync.series_name) + if applied: + tout.notice(f'{len(applied)} series applied upstream') else: self.rollback() tout.info('Dry run completed') From patchwork Fri May 1 11:00:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2255 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633298; bh=aeGj/dUrGk+A3dZEGxKwmZggRmUMb7GrGhZzVBUzMUk=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=NPzpq7Qx81eMsgDmqmHzGjFOggK8DXZX4beKtdD0SpU8Sxl87VjTTD6Ocj846DZrr opNvrhIJ/pNSY76z90bV+sF0XAsAV9awT6xRevmlii9cH0PjPssNVhvog/3xQVryXF IxWbze6iWEPw/+exRSSu0Vc23Tj/H55HCt5eT9zA= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3DBDF6A6F8 for ; Fri, 1 May 2026 05:01:38 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id b1P4v_MLfdE8 for ; Fri, 1 May 2026 05:01:38 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633298; bh=aeGj/dUrGk+A3dZEGxKwmZggRmUMb7GrGhZzVBUzMUk=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=NPzpq7Qx81eMsgDmqmHzGjFOggK8DXZX4beKtdD0SpU8Sxl87VjTTD6Ocj846DZrr opNvrhIJ/pNSY76z90bV+sF0XAsAV9awT6xRevmlii9cH0PjPssNVhvog/3xQVryXF IxWbze6iWEPw/+exRSSu0Vc23Tj/H55HCt5eT9zA= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2C8E26A7AF for ; Fri, 1 May 2026 05:01:38 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633297; bh=V6XjCj9B3atOTYRXTHz9A7iLQT5L1lfMkVFAagC/hE0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ciYd+ythgELSc5ydIT0cL+moSzDg96vw1KScE6mU4q2CW2+P4fdty9iG80fZAJtA/ hk9MxAVA3c+2vPqZOt+dHA0V5MIYMwTtybJOhf+AN6BgnTLjlxDKrXqIKS0NWMTmxM VepytaIm6Bs2+Do1JpDwproGHXtoyM3pe1aU5VRs= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0BFB56A6F8; Fri, 1 May 2026 05:01:37 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id mpo2GQPaAdPM; Fri, 1 May 2026 05:01:36 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633292; bh=Yb3T5/CDBHsfLiOWw37NLWF0FwpcSDh0ZeG7YCiTQos=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=biri/GsYHXAky6RsYjqNVXsC8uo1YbwI4dtZYDyvZ44b+OC/n1dtZRgdw6dHMxln2 8TTKtIXWZL6tmk9SVhRCIFc4XNIuam9zeA6fjZ/D69Vur5ICcT/NCRCDyhIPUUGHwr Ie5SHzh//k1hRH/78FRvFyDMBxAD4FGLC/Nbya90= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 9E7A56A7AF; Fri, 1 May 2026 05:01:32 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:04 -0600 Message-ID: <20260501110040.1874719-13-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: S7O7PJJTGTUPCKABRETPP3B6HBVVFV4F X-Message-ID-Hash: S7O7PJJTGTUPCKABRETPP3B6HBVVFV4F X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 12/29] patman: Refine review prompts and fix review handling List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The AI review output mixes double and single quotes around identifiers and lets periods land directly after code tokens, both of which make quoted function names inconvenient to copy from a terminal. Update the per-patch and cover-letter prompts to use single quotes around non-string identifiers and to avoid trailing periods after code references. Reviews with comments drop the diffstat that approved reviews carry, so a recipient cannot tell at a glance which files the comments cover. Quote up to 30 lines of diffstat after the commit message in those reviews, matching the format already produced for approved ones. If a review is interrupted after the series row is registered but before any review rows are stored, the next attempt silently exits instead of resuming. The '--force' path also re-derives lookup data that the resume path already has. Fix '_find_or_register()' to resume from a half-registered series and rework the '--force' path to reuse the existing IDs. Signed-off-by: Simon Glass --- tools/patman/review.py | 45 ++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) -- 2.43.0 diff --git a/tools/patman/review.py b/tools/patman/review.py index eb2e46a0431..775ffc22a34 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -379,8 +379,11 @@ Rules: - NEVER use backticks — this is plain-text email, not markdown. For functions, always use parentheses with no quotes: malloc() not 'malloc()' or `malloc`. For other identifiers (variables, struct - names, filenames) do not quote them unless they are common English - words where the reader might not realise they are code references + names, filenames) use single quotes: 'my_var' not "my_var", unless + it is a string literal (use double quotes for strings). Do not + quote identifiers that are obviously code (e.g. CONFIG_FOO) +- Never put a period directly after a code identifier — rephrase, + omit the period, or use an em dash to start the next clause - If another reviewer has already made a point, do NOT repeat it, rephrase it, or add to it — skip it entirely - Focus on what is wrong and what to do instead @@ -479,8 +482,12 @@ VERDICT: changes_needed Rules: - NEVER use backticks — this is plain-text email, not markdown. For functions, always use parentheses with no quotes: malloc() not - 'malloc()' or `malloc`. For other identifiers do not quote them - unless they are common English words that might confuse the reader + 'malloc()' or `malloc`. For other identifiers (variables, struct + names, filenames) use single quotes: 'my_var' not "my_var", unless + it is a string literal (use double quotes for strings). Do not + quote identifiers that are obviously code (e.g. CONFIG_FOO) +- Never put a period directly after a code identifier — rephrase, + omit the period, or use an em dash to start the next clause - Use {ctx.spelling} spelling - Be brief — only raise series-level concerns, not per-patch nits - Do NOT repeat issues that belong on individual patches @@ -657,7 +664,14 @@ def _format_with_comments(ctx, greeting, verdict, comments, lines.append(f'> {cl}') if len(msg_lines) > max_quote: lines.append('> [...]') - lines.append('') + diffstat = getattr(ctx, 'diffstat', None) + if diffstat: + ds_lines = diffstat.strip().splitlines() + if len(ds_lines) <= 30: + lines.append('>') + for dl in ds_lines: + lines.append(f'> {dl}') + lines.append('') for hunk, comment in comments: if hunk: @@ -1698,10 +1712,15 @@ def _find_or_register(ctx, args, clean_name, link): if not existing: return None - _, _, _, svid = existing + series_id, _, _, svid = existing reviews = ctx.cser.db.review_get_for_version(svid) - if reviews and not args.force: + if not reviews: + # Interrupted previous attempt — resume with existing record + tout.notice('Resuming incomplete review') + return series_id, svid + + if not args.force: _, db_name, db_version, _ = existing tout.notice(f"Already reviewed: '{db_name}' v{db_version}") _show_reviews(reviews, ctx.series_data) @@ -1710,14 +1729,10 @@ def _find_or_register(ctx, args, clean_name, link): ctx.cser) return None - if reviews and args.force: - ctx.cser.db.review_delete_for_version(svid) - ctx.cser.commit() - tout.notice('Re-reviewing (forced)') - - # Re-register for re-review - return _register_series(ctx.cser, clean_name, ctx.version, link, - ctx.series_data) + ctx.cser.db.review_delete_for_version(svid) + ctx.cser.commit() + tout.notice('Re-reviewing (forced)') + return series_id, svid def do_review(args, pwork, cser): From patchwork Fri May 1 11:00:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2256 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633300; bh=XMDqAgFYwr+UbaEi6k2wo+/iniAldzPbPS2oKTTI6bY=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=bd36+kSuW/xS4meY0RrZONHVlJLtwZmFIkLxPo27vPeYfYosoWkVotxx3GPPxBauc SomILzl4OMYxHjBpNHHrzMBBY3zS1NSTBSEUbOpJ2S24cJQrdSs/9JJ73Q3PgmqNWX HELGr0CiY/gaaK6ccJMKcD5uoYWo78UJO2bLbUaU= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A1F3F6A837 for ; Fri, 1 May 2026 05:01:40 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id UV_lVL-PeMan for ; Fri, 1 May 2026 05:01:40 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633299; bh=XMDqAgFYwr+UbaEi6k2wo+/iniAldzPbPS2oKTTI6bY=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=qliL670eleNc6EDaE3S/ERfq2ZY6zoZIZxQbCtfTQeFnO9ahQbzAM2hIJuuQwD9HW 6UD/OFTgdGRUX61pF6w9F480tq8SBMUxSp8nBZ0sWdf7phkVJaF8mOVBusr9cImWWA fSJpPiXgZ2SqgKbrnoc0WqkK5eC1f29o89vWXjBQ= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9B46D6A7AF for ; Fri, 1 May 2026 05:01:39 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633298; bh=d6j7s6HzoITgbwerMOSbIjp2EJvvfFRjsMc7onMrK4s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AizmLzkzgA5OKZTCfVTSaKx/U8/B3+H6OLGTjERnwcCBdvQv7BcJKYhDpwUx+ww3U FASkd5qljcqoCS4hb+WjagQGhHcokq+tIJXg4b9+nwd6LoH+PWZjdheo6vWsyPui9e qfR5iQ9V2MJ8x/cya2cuaPe9phzj7/ckxVYcu82k= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 23D946A82E; Fri, 1 May 2026 05:01:38 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id yh2StUxvC4Kl; Fri, 1 May 2026 05:01:38 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633297; bh=nt1HkUwHtFz1nJIgbbPVIfPEuyBR6k0mZP+JK1xT9YI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jNo7fuMivXeU1ADXuXnC4zH2WUS9fPxOcdEB1c7dpYrr5S1MsBSWMPJMPqmSCWFA9 L9IBAjXARVSzlv1Vq/RJjRUrmaFif1RqLT/hu+E4IobPkK6I+ex/TC1iF3uIaKmuMr FbLy7bp6p0ovnEfoz5YRjkVBxJvvWSx3O9cYIQYw= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 9CCD56A7AF; Fri, 1 May 2026 05:01:37 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:05 -0600 Message-ID: <20260501110040.1874719-14-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: AHEDHVLSFYHJVKXU6XUZBP7XQ2Q4KEIB X-Message-ID-Hash: AHEDHVLSFYHJVKXU6XUZBP7XQ2Q4KEIB X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 13/29] patman: Add -d short option for --create-drafts List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Add a single-character shortcut so that creating Gmail drafts during review can be requested with 'patman review -d' instead of the longer 'patman review --create-drafts' Signed-off-by: Simon Glass --- tools/patman/cmdline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index 13aa63b76c8..758d84c0397 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -555,7 +555,7 @@ def add_review_subparser(subparsers): default=False, help='Show what would be done without creating drafts') review.add_argument( - '--create-drafts', action='store_true', + '-d', '--create-drafts', action='store_true', help='Create Gmail draft emails for each review') review.add_argument( '--gmail-account', type=str, default=None, From patchwork Fri May 1 11:00:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2257 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633304; bh=C/0HaKuDJDQ+VDAGu39GuyR3Aoj5qGlSt/fnDllzlwE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=AbdA77yTvKONG8ofb21HFIBWAtFpInedSzh9fVTWHQoGb142nF0w3WuuEPJ8ZhMf0 h0WZ1n5yJvjCLmNsrhEZWZNJolo1fYjaqbM2BxAHunKliKW0xVtnsbRF8q17fTFe3S a48m+N+abY9DrYhatFN3Qsibilcs3d2DTxtnWHM8= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1B9646A82E for ; Fri, 1 May 2026 05:01:44 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id VT5hL38lYtQ0 for ; Fri, 1 May 2026 05:01:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633304; bh=C/0HaKuDJDQ+VDAGu39GuyR3Aoj5qGlSt/fnDllzlwE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=AbdA77yTvKONG8ofb21HFIBWAtFpInedSzh9fVTWHQoGb142nF0w3WuuEPJ8ZhMf0 h0WZ1n5yJvjCLmNsrhEZWZNJolo1fYjaqbM2BxAHunKliKW0xVtnsbRF8q17fTFe3S a48m+N+abY9DrYhatFN3Qsibilcs3d2DTxtnWHM8= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0B34D6A833 for ; Fri, 1 May 2026 05:01:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633303; bh=LNZaJksgIpeftpBEouJaJOw36DUZu2b10uPZRc4maOA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=i+ezHlMH01qom4GES9niDo0OCKAF74tCzByNMxI1C1l1OraF8Vq4q9Y+Af8WbhyOh j2UJsTNgBx7nFGtZ14U7TPCf2m+R8e5K8z58qOXgEy5mYyqIQVHTM1g6gmoN8yMdHz l6mRJpTG4WQ7qs/0PWgRsEVGps9m3V0o5ImlA6YY= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2662C6A6F8; Fri, 1 May 2026 05:01:43 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 1jqpNMQAT_-5; Fri, 1 May 2026 05:01:43 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633298; bh=ppgnOTu2OQRLn6kqif8nlpDiHmMm1JgEznDNNSdwLqs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=a631b/VfJmwO2jDi0rbh+9nEPNrBTIR7GaaTSl/k1aj/YodHfkJWMrpDbJijyRN8Z wOfZN7uf5aaDbqLjpDfqwBC5p8WCaSnjFewFmH1zo/NX9o3NbxpKNobTkHUiRbrk+O 6fNYlsEEWRht6bk+It1CpP0aRqiQDk11NyN7/c/M= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id AC1B36A834; Fri, 1 May 2026 05:01:38 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:06 -0600 Message-ID: <20260501110040.1874719-15-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: IDDSHZJU7IKGEHALY6U4SHXUOLMST2OA X-Message-ID-Hash: IDDSHZJU7IKGEHALY6U4SHXUOLMST2OA X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 14/29] patman: Hide review series from default listing List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Review series are named after the cover letter title, which collides with the user's own series and clutters 'patman series ls'. Use the branch name format '--review' (e.g. 'us-498633-review') instead, and filter review series from the default listing. Add '-r' to 'patman series ls' to show only review series. Also fix series_find_review_by_name() to search by description rather than name, since the name is now a branch identifier. The description stores the cover letter title (or first patch subject) so matching across versions still works. Signed-off-by: Simon Glass --- tools/patman/cmdline.py | 2 ++ tools/patman/control.py | 3 ++- tools/patman/cseries.py | 7 +++++-- tools/patman/database.py | 39 ++++++++++++++++++++++++------------ tools/patman/patchwork.py | 1 + tools/patman/review.py | 31 ++++++++++++++++++++++------ tools/patman/test_cseries.py | 2 +- 7 files changed, 62 insertions(+), 23 deletions(-) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index 758d84c0397..4ec9df7acb4 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -299,6 +299,8 @@ def add_series_subparser(subparsers): help='Show review text (optionally for specific patches)') ls = series_subparsers.add_parser('ls', aliases=['list']) _add_archived(ls) + ls.add_argument('-r', '--reviews', action='store_true', + help='Show only review series') mar = series_subparsers.add_parser('mark') mar.add_argument('-m', '--allow-marked', action='store_true', diff --git a/tools/patman/control.py b/tools/patman/control.py index ac3a27a3e0e..14d74028ae9 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -218,7 +218,8 @@ def do_series(args, test_db=None, pwork=None, cser=None): elif args.subcmd == 'inc': cser.increment(args.series, args.dry_run) elif args.subcmd == 'ls': - cser.series_list(args.include_archived) + cser.series_list(args.include_archived, + reviews_only=getattr(args, 'reviews', False)) elif args.subcmd == 'open': cser.open(pwork, args.series, args.version) elif args.subcmd == 'mark': diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index 5ffc2989dd5..893e1b4d212 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -463,7 +463,7 @@ class Cseries(cser_helper.CseriesHelper): return summary - def series_list(self, include_archived=False): + def series_list(self, include_archived=False, reviews_only=False): """List all series Lines all series along with their description, number of patches @@ -471,8 +471,11 @@ class Cseries(cser_helper.CseriesHelper): Args: include_archived (bool): True to include archived series also + reviews_only (bool): True to show only review series """ - sdict = self.db.series_get_dict(include_archived) + sdict = self.db.series_get_dict(include_archived, + include_reviews=reviews_only, + reviews_only=reviews_only) print(f"{'Name':15} {'Description':40} Accepted Us Versions") border = f"{'-' * 15} {'-' * 40} -------- -- {'-' * 15}" print(border) diff --git a/tools/patman/database.py b/tools/patman/database.py index 50b9ea0dca5..7f1771cbf11 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -369,20 +369,29 @@ class Database: # pylint:disable=R0904 """ return self.cur.rowcount - def _get_series_list(self, include_archived): + def _get_series_list(self, include_archived, include_reviews=False, + reviews_only=False): """Get a list of Series objects from the database Args: include_archived (bool): True to include archives series + include_reviews (bool): True to include review series + reviews_only (bool): True to show only review series Return: list of Series """ + conditions = [] + if not include_archived: + conditions.append('archived = 0') + if reviews_only: + conditions.append("source = 'review'") + elif not include_reviews: + conditions.append("(source IS NULL OR source != 'review')") + where = ('WHERE ' + ' AND '.join(conditions)) if conditions else '' res = self.execute( - 'SELECT id, name, desc, upstream FROM series ' + - ('WHERE archived = 0' if not include_archived else '')) - return [Series.from_fields(idnum=idnum, name=name, desc=desc, - ups=ups) + f'SELECT id, name, desc, upstream FROM series {where}') + return [Series.from_fields(idnum=idnum, name=name, desc=desc, ups=ups) for idnum, name, desc, ups in res.fetchall()] # series functions @@ -447,11 +456,14 @@ class Database: # pylint:disable=R0904 raise ValueError(f'No series found (id {idnum} len {len(recs)})') return recs[0] - def series_get_dict(self, include_archived=False): + def series_get_dict(self, include_archived=False, + include_reviews=False, reviews_only=False): """Get a dict of Series objects from the database Args: include_archived (bool): True to include archives series + include_reviews (bool): True to include review series + reviews_only (bool): True to show only review series Return: OrderedDict: @@ -459,7 +471,8 @@ class Database: # pylint:disable=R0904 value: Series with idnum, name and desc filled out """ sdict = OrderedDict() - for ser in self._get_series_list(include_archived): + for ser in self._get_series_list(include_archived, include_reviews, + reviews_only): sdict[ser.name] = ser return sdict @@ -1440,14 +1453,14 @@ class Database: # pylint:disable=R0904 return res.fetchone() def series_find_review_by_name(self, name): - """Find a review series by its name + """Find a review series by its description - Looks for series with source='review' matching the given name, - so that new versions of a previously reviewed series can be - added under the same series record. + Looks for series with source='review' whose description matches + the given name, so that new versions of a previously reviewed + series can be added under the same series record. Args: - name (str): Series name to search for + name (str): Series description (cover letter title) Return: tuple or None: (series_id, name, max_version) if found @@ -1456,6 +1469,6 @@ class Database: # pylint:disable=R0904 'SELECT s.id, s.name, MAX(sv.version) ' 'FROM series s ' 'JOIN ser_ver sv ON sv.series_id = s.id ' - "WHERE s.source = 'review' AND s.name = ? " + "WHERE s.source = 'review' AND s.desc = ? " 'GROUP BY s.id', (name,)) return res.fetchone() diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py index f71214d4b8f..de211f95878 100644 --- a/tools/patman/patchwork.py +++ b/tools/patman/patchwork.py @@ -180,6 +180,7 @@ class Patchwork: self.fake_request = None self.proj_id = None self.link_name = None + self.upstream = None self._show_progress = show_progress self.semaphore = asyncio.Semaphore( 1 if single_thread else MAX_CONCURRENT) diff --git a/tools/patman/review.py b/tools/patman/review.py index 775ffc22a34..a850ec180df 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -1439,7 +1439,22 @@ def _clean_series_name(name): return name -def _register_series(cser, clean_name, version, link, series_data): +def _make_review_name(link, upstream=None): + """Build a series name for a review branch + + Args: + link (str): Patchwork series link/ID + upstream (str or None): Upstream name + + Returns: + str: Branch-style name, e.g. 'us-498633-review' + """ + ups = upstream or 'pw' + return f'{ups}-{link}-review' + + +def _register_series(cser, clean_name, version, link, series_data, + upstream=None): """Register a series in the database for review Creates or finds the series record, adds a ser_ver entry and pcommit @@ -1451,6 +1466,7 @@ def _register_series(cser, clean_name, version, link, series_data): version (int): Series version number link (str): Patchwork series link/ID series_data (dict): Series data from patchwork + upstream (str or None): Upstream name for branch naming Returns: tuple or None: (series_id, svid) or None if already reviewed @@ -1465,12 +1481,13 @@ def _register_series(cser, clean_name, version, link, series_data): tout.notice(f"Previously reviewed '{db_name}' v{prev_version};" f" adding v{version}") else: + branch_name = _make_review_name(link, upstream) series_id = cser.db.series_find_by_name( - clean_name, include_archived=True) + branch_name, include_archived=True) if not series_id: desc = series_data.get('cover_letter', {}) - desc = desc.get('name', '') if desc else '' - series_id = cser.db.series_add(clean_name, desc) + desc = desc.get('name', '') if desc else clean_name + series_id = cser.db.series_add(branch_name, desc, ups=upstream) cser.db.series_set_source(series_id, 'review') svid = cser.db.ser_ver_add(series_id, version, link=str(link)) @@ -1604,7 +1621,8 @@ def _apply_and_check(ctx, link): Returns: str or None: Branch name, or None on failure """ - branch_name = f'review{ctx.series_id}' + ups = ctx.pwork.upstream if ctx.pwork else None + branch_name = _make_review_name(link, ups) repo_path = gitutil.get_top_level() success, branch_name = apply_series_sync(ctx.pwork, link, branch_name, ctx.upstream_branch, repo_path) @@ -1703,8 +1721,9 @@ def _find_or_register(ctx, args, clean_name, link): tuple or None: (series_id, svid) or None if already reviewed and not forcing """ + ups = ctx.pwork.upstream if ctx.pwork else None result = _register_series(ctx.cser, clean_name, ctx.version, link, - ctx.series_data) + ctx.series_data, upstream=ups) if result is not None: return result diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 2d56b409a57..125e6db7b69 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -4616,7 +4616,7 @@ Date: .* result = cser.db.series_find_by_link(str(self.REVIEW_LINK)) self.assertIsNotNone(result) series_id, name, version, svid = result - self.assertEqual(self.REVIEW_NAME, name) + self.assertEqual(f'pw-{self.REVIEW_LINK}-review', name) self.assertEqual(1, version) # Check source is 'review' From patchwork Fri May 1 11:00:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2258 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633305; bh=NRv41r8mk2b37iQWr9VS+bpaIC6FVTw0475Ar10+oPk=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=hRpckkUP0TCWjYCjWcswnOmb6MKpGrAE7k8M66CVsiQzoYLmp625lmjxkt1Xvg2I8 iPz2AidOUi2Kir4DNJXOiERaYEzTcDrCKS+pHiJadhvsMlhXwxs+ihVoB5kxdS8pzp bgh9hlQ5Ue3hqBmnzdHaTXqoRwvx34HBEmyuC9wY= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D798C6A836 for ; Fri, 1 May 2026 05:01:45 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id e-vIIUsbbodJ for ; Fri, 1 May 2026 05:01:45 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633305; bh=NRv41r8mk2b37iQWr9VS+bpaIC6FVTw0475Ar10+oPk=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=hRpckkUP0TCWjYCjWcswnOmb6MKpGrAE7k8M66CVsiQzoYLmp625lmjxkt1Xvg2I8 iPz2AidOUi2Kir4DNJXOiERaYEzTcDrCKS+pHiJadhvsMlhXwxs+ihVoB5kxdS8pzp bgh9hlQ5Ue3hqBmnzdHaTXqoRwvx34HBEmyuC9wY= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C0BA56A7AF for ; Fri, 1 May 2026 05:01:45 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633304; bh=gClK8Gl+qdg7/mc7FRwwQEe/Ni385880b4HOZ43fE/k=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hodgS0iM0c/HhbClzTMwzT/eTloZtoaXaPWRmyUHXwTJdbud9aEtRHtZAy8qffcdj CLd/klxUw3mdh8oKtQewinZMZ298kYZYQ3oUGWOMvxWIVoDmiHyPh6P5MwGJE2EL0f FxZxiqOHS6AsLD9cLij0t8LlSJliFExz37gT+ZFM= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 43E9E6A836; Fri, 1 May 2026 05:01:44 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 70bqdKKLsWoa; Fri, 1 May 2026 05:01:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633303; bh=6BqtYHwwwhpAY6qhrE81ZTjiAwOdke7XCMiKKizG/og=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WZ/QhLHNZ/D1r8AGrXyRFoFrkFN40X7tAOq/VpWDKM0qYAHLqF8aN08skXLsipPYg Cym99ZC+r6STiAqQumi6hXVn1fbpUQ4ZRgmyko3HGzFpIPL2PkhtYU1tbcBqumQ1Xt qSsQdVZ9O6bHpLLkmyiJNMWmAe2U2iN1TqclqmNo= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id BACB46A7AF; Fri, 1 May 2026 05:01:43 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:07 -0600 Message-ID: <20260501110040.1874719-16-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: ESWHZVZ322CSGW7NEX2JLYAFQZW3DYWI X-Message-ID-Hash: ESWHZVZ322CSGW7NEX2JLYAFQZW3DYWI X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 15/29] patman: Record workflow entry when reviewing a series List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass After completing a review, add a 'reviewed' workflow entry and a follow-up todo for 7 days later. This makes reviews visible in 'patman wf ls' alongside sent series, so the user can track which series need follow-up. Signed-off-by: Simon Glass --- tools/patman/review.py | 2 ++ tools/patman/workflow.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/tools/patman/review.py b/tools/patman/review.py index a850ec180df..92fbcfa8a2d 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -32,6 +32,7 @@ from u_boot_pylib import tout from patman import database from patman import gmail from patman import patchstream +from patman import workflow try: from claude_agent_sdk import ClaudeAgentOptions @@ -1821,6 +1822,7 @@ def do_review(args, pwork, cser): ctx.comments_path = _write_comments_file(series_data, pwork) _run_and_store_reviews(ctx, args) + workflow.reviewed(cser, ctx.series_id, ctx.svid) _git_restore(orig_branch, had_stash, ctx.repo_path) orig_branch = None diff --git a/tools/patman/workflow.py b/tools/patman/workflow.py index 299686e3269..97e3b1bbafc 100644 --- a/tools/patman/workflow.py +++ b/tools/patman/workflow.py @@ -13,6 +13,7 @@ class Wtype(str, enum.Enum): """Types of workflow entry""" SENT = 'sent' TODO = 'todo' + REVIEWED = 'reviewed' def friendly_time(now, when): @@ -61,6 +62,24 @@ def sent(cser, series_id, ser_ver_id=None): cser.commit() +def reviewed(cser, series_id, ser_ver_id=None): + """Record that a series was reviewed and create a follow-up todo + + Args: + cser (CseriesHelper): Series helper with open database + series_id (int): ID of the series that was reviewed + ser_ver_id (int or None): ID of the ser_ver record + """ + ts = cser.get_now().strftime('%Y-%m-%d %H:%M:%S') + cser.db.workflow_archive(Wtype.REVIEWED, series_id) + cser.db.workflow_add(Wtype.REVIEWED, series_id, ts, ser_ver_id=ser_ver_id) + when = cser.get_now() + timedelta(days=7) + todo_ts = when.strftime('%Y-%m-%d %H:%M:%S') + cser.db.workflow_archive(Wtype.TODO, series_id) + cser.db.workflow_add(Wtype.TODO, series_id, todo_ts) + cser.commit() + + def todo(cser, series, days): """Mark a series as a todo item after a number of days From patchwork Fri May 1 11:00:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2259 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633308; bh=k3p7UuCsUlrPx2Q9BGBeNzoQjcwL3xm1zu0oBnqAr64=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=HeglSkmWn01Mq3/S6z0ijgIYWRwNSK2+OuERvv8mQalEm8ww5PEFGn4j7YS22Be+C DiSJQrWQ0o9oiwzcidPfRk3zCRrI/rqwFxu2P47uuLgjpYzEwdApqL46jm9qpW9Aby WyaPjWGTE4hzj2EpvxSCVR65URxfG3EnEO5eAxaA= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 415BB6A834 for ; Fri, 1 May 2026 05:01:48 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 8WYyalEdiq8V for ; Fri, 1 May 2026 05:01:48 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633306; bh=k3p7UuCsUlrPx2Q9BGBeNzoQjcwL3xm1zu0oBnqAr64=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=TVRNIrnj81lw0qKGZAadhumE03XlVoi/oTG7HYoS7UvZ/Bxto/J+OD5Td66odo1ZR sAGqlBegYjTEqfWVGGwgppielyM/B/e/McqmlyogLjDilKTiJ4Z/CiF+iHnqCBURco tWRWTvwkkazblkma9pscCPvtSd+VG6tD4tnullJs= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3F3AD6A84B for ; Fri, 1 May 2026 05:01:46 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633305; bh=Zc+hyNf5aso7pG5X5L4zNQwC9IwAvy4fQRRwzEykA3k=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=k3s5gCYdqJ29FKbjABqLTLclCqQvuHc0TQwlCJK4uWFk9QR23LN7Z6KQrQgcT+ehG aEq1YkqkyohkU68E5gRn9MibI6SZkYPaTjFsxyR+CAD3oyZez0uBXVIAgkO9M+40B5 aADSrAAeckTtoHJwfUC5PmkOS0yzoJkoe9Z9Vg2Q= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 435976A7AF; Fri, 1 May 2026 05:01:45 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id h5hAJ0LoQnMr; Fri, 1 May 2026 05:01:45 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633305; bh=a4fytX3pxMwp9pJMb1A4tzlUPrI4ePXKASPK3IG0A7E=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IENa84JDhWgfNQDUH0LGLr8GZ9EapjeaReRTYWKMYNS4GRWhF6V46gL6h/2QlIpno QyX7ln1RLJVZpDbxe6ctf4D2TpHr+gxhfowsFnHderIvYPKoT/dEy5djUZpT1cXo3D GTWVXWtbvbKKWAVsxD1xln9KdubobVB6KgzO1Gi8= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id C358F6A849; Fri, 1 May 2026 05:01:44 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:08 -0600 Message-ID: <20260501110040.1874719-17-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: DNOGIB454MJ6ME55365AJ5Q3DXFR4M22 X-Message-ID-Hash: DNOGIB454MJ6ME55365AJ5Q3DXFR4M22 X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 16/29] patman: Let the apply agent edit files for manual fixups List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The apply prompt tells the agent to fall back to editing the target source file manually when 'git am' and 'patch --fuzz' both fail, but the agent only has Bash, Read and Grep in its allowed-tools list. Without Edit and Write it cannot make the manual adjustments and reports back that it is waiting for file permission. Add Edit and Write to the allowed tools so the apply-and-fix-up loop described in the prompt can complete. Signed-off-by: Simon Glass --- tools/patman/review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/patman/review.py b/tools/patman/review.py index 92fbcfa8a2d..66580e48ee6 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -228,8 +228,8 @@ async def apply_series(pwork, link, branch_name, upstream_branch, # Build the prompt and run the agent prompt = _build_apply_prompt(mbox_path, branch_name, upstream_branch) options = ClaudeAgentOptions( - allowed_tools=['Bash', 'Read', 'Grep'], cwd=repo_path, - max_buffer_size=claude_mod.MAX_BUFFER_SIZE) + allowed_tools=['Bash', 'Read', 'Grep', 'Edit', 'Write'], + cwd=repo_path, max_buffer_size=claude_mod.MAX_BUFFER_SIZE) tout.notice(f'Applying series to branch {branch_name}...') success, _ = await claude_mod.run_agent_collect(prompt, options) From patchwork Fri May 1 11:00:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2260 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633311; bh=YV6IJ5/Yw6ZjejGf6zH7H4SdQqeFDc8Vwieg/UtE0pQ=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=cs8ffZ75f4BS4lgOmVU93GZ/PzvCF/Er700rJYkNiPJhC7J0/4EWwqE/mZZh1wyoA tYn0lZ8PbFoFd1g917+0xMKNahc279CarPA325IV1aX2cwIxbEWFwBBhecohDxTZcD zhxrC820/3TREJYq/0z4ZBJjaaE6ErYnPxDTpLgs= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C8CD86A836 for ; Fri, 1 May 2026 05:01:51 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id t1gvJlNh9yHU for ; Fri, 1 May 2026 05:01:51 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633311; bh=YV6IJ5/Yw6ZjejGf6zH7H4SdQqeFDc8Vwieg/UtE0pQ=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=cs8ffZ75f4BS4lgOmVU93GZ/PzvCF/Er700rJYkNiPJhC7J0/4EWwqE/mZZh1wyoA tYn0lZ8PbFoFd1g917+0xMKNahc279CarPA325IV1aX2cwIxbEWFwBBhecohDxTZcD zhxrC820/3TREJYq/0z4ZBJjaaE6ErYnPxDTpLgs= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B083A6A833 for ; Fri, 1 May 2026 05:01:51 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633308; bh=ceHfWaTPmi127ryxUJ+XtJor4b2auvbwmMKxV8k4Qy0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EvpqaFpR1/aOz/6eDo11b2MubAJ+kV3870aTyhr6qgs40QIyKiOySYs0pzDA4BKae SrGDSQkas9Ug9nlRAXn9R7tlVeYwws7UtIEY6eIHei1QZ6rhzOPs540X69BDAFMID7 VVhYUP9bwaihDIqEL5bo0wPCO/SQpjpMAbHyX4KU= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 544666A840; Fri, 1 May 2026 05:01:48 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id A1l3cPAs_-Cx; Fri, 1 May 2026 05:01:48 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633306; bh=PN44c0ZnSYDMHQLPVcHULbEExNdmrPeRkehU+PUl2sw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BVY4U8SkJcgZ0cSTejYcc0ssobLODaTWO0Imhjr4BBOTczP3o9sGR8aoTvfv2IDTA PhBu5qVsJMmzCAnSpg63qYJzP8/63chLBQInk5XPjxQ/6nR5F78i+nIAftDX7Y/1fE m+2isJfuWOCXazXwyCCvEy/za/UzyZUC/akJEhQI= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id C383A6A833; Fri, 1 May 2026 05:01:45 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:09 -0600 Message-ID: <20260501110040.1874719-18-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 7TS3G25JBLLOPYFHEXTRYJ7RPZQW5U75 X-Message-ID-Hash: 7TS3G25JBLLOPYFHEXTRYJ7RPZQW5U75 X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 17/29] patman: Abort review on partial or interrupted apply List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass When the apply agent finishes successfully but only some of the patches actually land on the review branch, the subsequent review runs against an incomplete series and produces confusing output: the cover letter claims N patches but only M commits exist. Compare the applied count from gitutil.count_revs() against patch_count and abort with a clear message when they differ, asking the user to resolve the conflicts manually and retry. The 'no commits' case (None from count_revs(), or zero) already routes through the existing failure path that rolls back the database and restores the original branch. Signed-off-by: Simon Glass --- tools/patman/review.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/patman/review.py b/tools/patman/review.py index 66580e48ee6..8af13b65711 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -1632,6 +1632,12 @@ def _apply_and_check(ctx, link): applied = gitutil.count_revs( repo_path, f'{ctx.upstream_branch}..{branch_name}') if not applied: + # Zero commits, or branch missing because apply was interrupted + success = False + elif applied != ctx.patch_count: + tout.error(f'Only {applied} of {ctx.patch_count} patches ' + f'applied to {branch_name}; aborting. Fix the ' + 'conflicts manually and retry.') success = False if not success: From patchwork Fri May 1 11:00:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2261 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633312; bh=jN+PNBITHkYDhK/retus0Wfj7X6AxsguiElZ68Ypw7g=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=RKF2fBOIpz+7rv1aOwRrk2rMr2Cx8AxZqpzbfddi9i5EMjJ2C7qu/6DQQeVoKsOKX 5y2Fvbj8nNsjY6wnTi8x72wRyp3yq2GkIRhIYv2aSO0bQv14y0lMbd4FfkYj2tR8kB EM6GuKs3cw/2QlZbN8Ks1wTZXyO2DRwcMwyk/Ruo= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 31A9B6A851 for ; Fri, 1 May 2026 05:01:52 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id ZU9mlMElHc7d for ; Fri, 1 May 2026 05:01:52 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633312; bh=jN+PNBITHkYDhK/retus0Wfj7X6AxsguiElZ68Ypw7g=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=RKF2fBOIpz+7rv1aOwRrk2rMr2Cx8AxZqpzbfddi9i5EMjJ2C7qu/6DQQeVoKsOKX 5y2Fvbj8nNsjY6wnTi8x72wRyp3yq2GkIRhIYv2aSO0bQv14y0lMbd4FfkYj2tR8kB EM6GuKs3cw/2QlZbN8Ks1wTZXyO2DRwcMwyk/Ruo= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 20C9D6A833 for ; Fri, 1 May 2026 05:01:52 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633310; bh=E3BdHgjYFOmcl6mZ1b8z3uAYFdyanbBgrKHHeCXc9W8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bgXSne2epRJMyYPefYEiSBO2I9QmnS6vFEp5gjPtLrJcadRsRM2IWBEGZO9kXG1DS 1ojrBtrmVkletCQRuvS6cAjB0gfho0BznxPpiyzjZzd1E8btSz0QaTisohOEFPtznG cdwQ1xhYhjiW2QtxLKruqVTbuplBXwXAeqD71C+k= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 8EC1C6A833; Fri, 1 May 2026 05:01:50 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id Ea-ZOMDgCmbb; Fri, 1 May 2026 05:01:50 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633307; bh=fEjT3YJpCG6e1Ny9gZib1Y9xe86xR7CZ8Ic3tOclqkw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rRYWRuE7J5/+mHATEYnOW3ro3kjY0h87+MnCFreXougwaUJC4yPVzXDpMbxeo1K/C Ed5jVUSoScNlc9KB9mn/wu86yFezcMDHGnW7zZ1MqD4DfqhAe4mlrr9DiLEGahG8Ic wGjahJiL0ZiyzXHScXhAF/RJzdwCJcM75b+Zd+ps= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 039596A7AF; Fri, 1 May 2026 05:01:46 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:10 -0600 Message-ID: <20260501110040.1874719-19-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: Z46K5X5MNJQTQB3QVZNMOMGWRI6RR7RE X-Message-ID-Hash: Z46K5X5MNJQTQB3QVZNMOMGWRI6RR7RE X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 18/29] patman: Rename review series flags to '-s/-S' List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The current '-l/--link' and '-t/--title' flags collide with new flags being added for single-patch reviews ('-p/--patch' and '-P/--patch-title'). Rename them to '-s/--series' (still landing on the 'pw_link' attribute, so internal callers are unaffected) and '-S/--series-title' so the series and patch flag pairs stay symmetric. Update the docs and test invocations to match. While here, switch the rst examples that still write '--create-drafts' to the '-d' short form added earlier, and tidy up a stray ' - ' that should be ' -- '. Signed-off-by: Simon Glass --- tools/patman/cmdline.py | 6 +++--- tools/patman/patman.rst | 16 ++++++++-------- tools/patman/test_cseries.py | 28 +++++++++++----------------- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index 4ec9df7acb4..06442fc08cc 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -547,11 +547,11 @@ def add_review_subparser(subparsers): 'review', aliases=ALIASES['review'], help='AI-powered review of a patchwork series') review.add_argument( - '-l', '--link', type=str, dest='pw_link', + '-s', '--series', type=str, dest='pw_link', help='Patchwork series link/ID number') review.add_argument( - '-t', '--title', type=str, - help='Search for series by cover-letter title') + '-S', '--series-title', type=str, dest='title', + help='Search for a series by cover-letter title') review.add_argument( '-n', '--dry-run', action='store_true', dest='dry_run', default=False, diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index 0644f4d0e08..70e6b381cc8 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -1251,30 +1251,30 @@ Basic usage Review a series by Patchwork link:: - patman review -l 497923 -U us --reviewer 'Your Name ' + patman review -s 497923 -U us --reviewer 'Your Name ' Or search by cover-letter title:: - patman review -t 'boot/bootm: Disable interrupts' -U us \ + patman review -S 'boot/bootm: Disable interrupts' -U us \ --reviewer 'Your Name ' To create Gmail drafts threaded under the original emails:: - patman review -l 497923 -U us \ + patman review -s 497923 -U us \ --reviewer 'Your Name ' \ - --create-drafts --gmail-account your@email + -d --gmail-account your@email -Use ``-n`` with ``--create-drafts`` for a dry run that shows what would -be created without calling the Gmail API. +Use ``-n`` with ``-d`` for a dry run that shows what would be created +without calling the Gmail API. Use ``--apply-only`` to download and apply patches without running the -AI review - useful for checking that patches apply cleanly. +AI review -- useful for checking that patches apply cleanly. Use ``-f`` / ``--force`` to re-review a series that has already been reviewed. This deletes the old review records and runs the review again:: - patman review -l 497923 -U us -f --reviewer 'Your Name ' + patman review -s 497923 -U us -f --reviewer 'Your Name ' If the reviewer email (from ``--reviewer`` or git config) differs from the ``--gmail-account``, patman sets the From header on the draft so diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 125e6db7b69..ea1ac32badc 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -4608,8 +4608,7 @@ Date: .* for m in mocks: stack.enter_context(m) with terminal.capture() as _: - self.run_review( '-l', str(self.REVIEW_LINK), - pwork=pwork) + self.run_review('-s', str(self.REVIEW_LINK), pwork=pwork) # Check the series was created with source='review' self.db_open() @@ -4640,8 +4639,7 @@ Date: .* for m in mocks: stack.enter_context(m) with terminal.capture() as _: - self.run_review( '-l', str(self.REVIEW_LINK), - pwork=pwork) + self.run_review('-s', str(self.REVIEW_LINK), pwork=pwork) # Review the same link again mocks = self._mock_review() @@ -4649,7 +4647,7 @@ Date: .* for m in mocks: stack.enter_context(m) with terminal.capture() as (out, err): - self.run_review('-l', str(self.REVIEW_LINK), pwork=pwork) + self.run_review('-s', str(self.REVIEW_LINK), pwork=pwork) self.assertIn('Already reviewed', out.getvalue()) def test_review_new_version(self): @@ -4664,8 +4662,7 @@ Date: .* for m in mocks: stack.enter_context(m) with terminal.capture() as _: - self.run_review( '-l', str(self.REVIEW_LINK), - pwork=pwork) + self.run_review('-s', str(self.REVIEW_LINK), pwork=pwork) # Now review v2 - should detect the previous review mocks = self._mock_review() @@ -4673,8 +4670,7 @@ Date: .* for m in mocks: stack.enter_context(m) with terminal.capture() as (out, _): - self.run_review( '-l', str(self.REVIEW_LINK_V2), - pwork=pwork) + self.run_review('-s', str(self.REVIEW_LINK_V2), pwork=pwork) self.assertIn('Previously reviewed', out.getvalue()) # Check both versions are under the same series @@ -4696,8 +4692,7 @@ Date: .* for m in mocks: stack.enter_context(m) with terminal.capture() as (out, _): - self.run_review( '-t', 'Disable interrupts', - pwork=pwork) + self.run_review('-S', 'Disable interrupts', pwork=pwork) # Should pick the most recent (v2) self.assertIn('Using most recent', out.getvalue()) @@ -4730,7 +4725,7 @@ Date: .* return_value='test'), \ mock.patch('patman.review._git_restore'): with terminal.capture() as _: - self.run_review('-l', str(self.REVIEW_LINK), + self.run_review('-s', str(self.REVIEW_LINK), pwork=pwork, expect_ret=1) def test_review_create_drafts_dry_run(self): @@ -4744,8 +4739,8 @@ Date: .* for m in mocks: stack.enter_context(m) with terminal.capture() as (out, _): - self.run_review( '-l', str(self.REVIEW_LINK), - '--create-drafts', '-n', pwork=pwork) + self.run_review('-s', str(self.REVIEW_LINK), '--create-drafts', + '-n', pwork=pwork) output = out.getvalue() self.assertIn('Would create draft', output) self.assertIn('author@posteo.net', output) @@ -4773,9 +4768,8 @@ Date: .* .list.return_value \ .execute.return_value = {'messages': []} with terminal.capture() as (out, _): - self.run_review( '-l', - str(self.REVIEW_LINK), - '--create-drafts', pwork=pwork) + self.run_review('-s', str(self.REVIEW_LINK), + '--create-drafts', pwork=pwork) output = out.getvalue() self.assertIn('Created 1 Gmail draft', output) From patchwork Fri May 1 11:00:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2262 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633316; bh=XMuUME353DTRg11EMgWHCWFMT/X5UoIg98tXvmSD0RU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=fUPT4h416dMRUURj78jhwVQTAhmX8aj7RVL/AWsrFdLSWtHihDKhxEYjkC80Mlj2G rgTX98WTDPRT26SCcdEDYp2NCU+0JeyiajeEK4Pjg1VNEz/w23uoaVkU63pjgTdomE kmscZu/klGh+M6fE1yN/ZIWNRDqGIcB160/XtkbY= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 8CEE96A84D for ; Fri, 1 May 2026 05:01:56 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id IAxPPE-Kdv2G for ; Fri, 1 May 2026 05:01:56 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633315; bh=XMuUME353DTRg11EMgWHCWFMT/X5UoIg98tXvmSD0RU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=n2U2VsEkK/UaoHmTwvhuHdXtl6ZJxjoEJZibo2qKbRTbV7PZ5XnnlekjENp4KPSFE aiMITsUEwhgGuVwyFSz3BK0Ugjhw9RqRx9NGD2woiCu5IvbscpTRXDeIFx467L5/As ENTsMheZMq3pUzxWVJwhC5Ba1Q2+1K4w9knKPJbE= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D72006A82E for ; Fri, 1 May 2026 05:01:55 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633314; bh=rJwTMJ14mHsWE5qpvvg2SDsIh925vwe7cJMfC4BN82I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IwWRj5B7GvjgdM6dLWSRzyY3mNF/MGLXybTxjJQ5jYFbXC4yknmX3+278GmUD8SSr o2I9Z0E16n9I7HMw6qHt2FusmqrFpGdTPyoI9lZJ+/eR+Mun+GsPOcAW0ozgQKyxhE wF09K4XLs3jX2c9Pe848qQimIrPhYGxYdFg7tnKk= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 59AE76A834; Fri, 1 May 2026 05:01:54 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id afGtrl7OCFPK; Fri, 1 May 2026 05:01:54 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633308; bh=PW9sF2qlsm2Jz8r8XSTEQb5pvpXfdgiHXIaXkrJSM4Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LK0RxeXJsK7DW0gb3p/A9yXdMs71/XGvNz//cOpeCeF5b4wwZf0zTDgKYNLdt7Jyl bcdpf2/Cfidtq2ioXDkedhbDdFheZ5X9s6MB7TkhoDppKqlBbG9ZGHj0NdmYM29wa2 FnHJdVkCDmFIZN2ojLOF55rfyo25NclfcLqR2ezY= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 39BD66A82E; Fri, 1 May 2026 05:01:48 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 05:00:11 -0600 Message-ID: <20260501110040.1874719-20-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 7WR5Q55NLGVHOWXKDEDEODCTU4VXTY5W X-Message-ID-Hash: 7WR5Q55NLGVHOWXKDEDEODCTU4VXTY5W X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 19/29] patman: Allow reviewing specific patches in a series List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Re-reviewing a whole series after only one or two patches change is wasteful, especially when the unchanged patches have already been reviewed and stored. The reviewer also has no way to ask for a review of a single patch without first finding its parent series and working out the patch's index by hand. Add three new entry points: '-i 1,3,5' (or '-i 2-7') selects a subset of the current series and skips the cover-letter review when a selection is active; '-p ' looks up the series for a single patch automatically and reviews just that patch; '-P ' finds the most recent patch matching a title fragment and feeds it into the same flow. When a stored review already exists for a selected patch, skip it and add a clear notice; when the selection extends an already-reviewed series, append the new patch reviews without forcing a full re-review. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/cmdline.py | 12 +++ tools/patman/patman.rst | 13 ++++ tools/patman/review.py | 157 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 174 insertions(+), 8 deletions(-) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index 06442fc08cc..fa7493bbfa8 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -552,6 +552,18 @@ def add_review_subparser(subparsers): review.add_argument( '-S', '--series-title', type=str, dest='title', help='Search for a series by cover-letter title') + review.add_argument( + '-p', '--patch', type=int, + help='Patchwork patch ID (finds the series and reviews just ' + 'that patch)') + review.add_argument( + '-P', '--patch-title', type=str, + help='Search for a patch by title (finds its series and reviews ' + 'just that patch)') + review.add_argument( + '-i', '--index', type=str, dest='patches', + help='Review only specific patches by index (e.g. 3 or 1,3,5 ' + 'or 2-7)') review.add_argument( '-n', '--dry-run', action='store_true', dest='dry_run', default=False, diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index 70e6b381cc8..532db22745f 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -1258,6 +1258,19 @@ Or search by cover-letter title:: patman review -S 'boot/bootm: Disable interrupts' -U us \ --reviewer 'Your Name <your@email>' +To review a single patch by its Patchwork patch ID (the series is +found automatically):: + + patman review -p 2219748 + +Or search for a patch by title:: + + patman review -P 'Add SPL support for Qualcomm' + +To review only specific patches by index within the series:: + + patman review -s 497923 -i 1,3,5 + To create Gmail drafts threaded under the original emails:: patman review -s 497923 -U us \ diff --git a/tools/patman/review.py b/tools/patman/review.py index 8af13b65711..76f3dbe274c 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -1007,13 +1007,37 @@ async def _review_single_patch(ctx, cmt, seq, all_commits): return format_review_email(ctx, greeting, verdict, comments, commit_msg) +def parse_patch_selection(spec): + """Parse a patch selection string into a set of patch numbers + + Supports comma-separated numbers and ranges, e.g. '1,3,5' or '2-7' + or '1,3-5,8'. + + Args: + spec (str or None): Selection string, or None for all patches + + Returns: + set of int or None: Selected patch numbers, or None for all + """ + if not spec: + return None + result = set() + for part in spec.split(','): + if '-' in part: + start, end = part.split('-', 1) + result.update(range(int(start), int(end) + 1)) + else: + result.add(int(part)) + return result + + async def review_patches(ctx): """Run AI review on each patch in the applied branch Args: ctx (ReviewContext): Review context (uses branch_name, upstream_branch, patch_count, cover_content, - previous_reviews, repo_path, etc.) + previous_reviews, repo_path, patch_selection, etc.) Returns: dict: Map of patch index (1-based) to review body @@ -1034,15 +1058,30 @@ async def review_patches(ctx): review_bodies = {} - if ctx.cover_content and ctx.patch_count > 1: + patch_sel = getattr(ctx, 'patch_selection', None) + + if ctx.cover_content and ctx.patch_count > 1 and not patch_sel: body = await _review_cover_letter(ctx, all_commits) if body: review_bodies[0] = body + # Check which patches already have stored reviews + existing_reviews = set() + if hasattr(ctx, 'svid') and ctx.svid: + for rev in ctx.cser.db.review_get_for_version(ctx.svid): + existing_reviews.add(rev.seq) reviewer_tag = ctx.reviewer_tag for i, cmt in enumerate(series.commits): seq = i + 1 + if patch_sel and seq not in patch_sel: + continue + + if seq in existing_reviews: + tout.notice(f'Skipping patch {seq}/{len(commits)}' + ' (already in database)') + continue + if (reviewer_tag in cmt.rtags.get('Reviewed-by', set()) or reviewer_tag in cmt.rtags.get('Tested-by', set())): tout.notice(f'Skipping patch {seq}/{len(commits)}' @@ -1085,6 +1124,88 @@ def apply_series_sync(pwork, link, branch_name, upstream_branch, repo_path): pwork, link, branch_name, upstream_branch, repo_path)) +def search_patch(pwork, title): + """Search patchwork for a patch by title and return its series and index + + Queries the patchwork patches API by title, picks the most recent + match, then looks up its series. + + Args: + pwork (Patchwork): Configured patchwork instance + title (str): Patch title text to search for + + Returns: + tuple: (series_link, patch_seq) + + Raises: + ValueError: if no matching patch is found + """ + from urllib.parse import quote_plus + + async def _query(): + query = quote_plus(title, safe=':') + subpath = (f'patches/?project={pwork.proj_id}&q={query}' + '&order=-date&per_page=20') + async with aiohttp.ClientSession() as client: + return await pwork._request(client, subpath) + + loop = asyncio.get_event_loop() + results = loop.run_until_complete(_query()) + + if not results: + raise ValueError(f"No patch found matching '{title}'") + + if len(results) > 1: + tout.notice(f"Found {len(results)} matching patches:") + for i, p in enumerate(results[:10]): + tout.notice(f" {i + 1}. [{p['id']}] {p['name']}") + + best = results[0] + patch_id = best['id'] + tout.notice(f"Using: [{patch_id}] {best['name']}") + return lookup_patch_series(pwork, patch_id) + + +def lookup_patch_series(pwork, patch_id): + """Look up a patch on patchwork and return its series link and position + + Args: + pwork (Patchwork): Configured patchwork instance + patch_id (int): Patchwork patch ID + + Returns: + tuple: (series_link, patch_seq) where series_link is the series + ID as a string and patch_seq is the 1-based position + + Raises: + ValueError: if the patch or its series cannot be found + """ + async def _query(): + async with aiohttp.ClientSession() as client: + return await pwork.get_patch(client, patch_id) + + loop = asyncio.get_event_loop() + data = loop.run_until_complete(_query()) + + series_list = data.get('series', []) + if not series_list: + raise ValueError(f'Patch {patch_id} has no associated series') + + series_link = str(series_list[0]['id']) + patch_name = data.get('name', '') + tout.notice(f"Patch {patch_id}: '{patch_name}'") + tout.notice(f"Series: {series_list[0].get('name', '')} " + f"(link {series_link})") + + # Fetch the series to find the patch position + series_data = _fetch_series(pwork, series_link)[0] + patches = series_data.get('patches', []) + for i, patch in enumerate(patches): + if patch.get('id') == patch_id: + return series_link, i + 1 + return series_link, 1 + + def search_series(pwork, title): """Search patchwork for a series by cover-letter title @@ -1746,6 +1867,16 @@ def _find_or_register(ctx, args, clean_name, link): tout.notice('Resuming incomplete review') return series_id, svid + # When reviewing specific patches, allow adding to existing reviews + patch_sel = parse_patch_selection(args.patches) + if patch_sel: + reviewed_seqs = {r.seq for r in reviews} + new_seqs = patch_sel - reviewed_seqs + if new_seqs: + tout.notice(f'Adding review for patch(es) ' + f'{", ".join(str(s) for s in sorted(new_seqs))}') + return series_id, svid + if not args.force: _, db_name, db_version, _ = existing tout.notice(f"Already reviewed: '{db_name}' v{db_version}") @@ -1778,12 +1909,21 @@ def do_review(args, pwork, cser): if args.sync: return _do_sync(args, cser) - if not args.pw_link and not args.title: - raise ValueError("Please provide -l <link> or -t <title> " - "to identify the series") + has_patch = getattr(args, 'patch', None) + has_patch_title = getattr(args, 'patch_title', None) + if not args.pw_link and not args.title and not has_patch and \ + not has_patch_title: + raise ValueError("Please provide -s <series>, -S <title>, " + "-p <patch-id> or -P <patch-title>") link = args.pw_link - if not link: + if not link and has_patch: + link, patch_seq = lookup_patch_series(pwork, args.patch) + args.patches = str(patch_seq) + elif not link and has_patch_title: + link, patch_seq = search_patch(pwork, args.patch_title) + args.patches = str(patch_seq) + elif not link: link = search_series(pwork, args.title) series_data, clean_name, version, patch_count = \ @@ -1820,11 +1960,12 @@ def do_review(args, pwork, cser): tout.notice('Apply-only mode; skipping review') return 0 + ctx.patch_selection = parse_patch_selection(args.patches) ctx.reviewer_name, ctx.reviewer_email = _parse_reviewer(args) - ctx.signoff = getattr(args, 'signoff', '') or None + ctx.signoff = args.signoff or None if ctx.signoff: ctx.signoff = ctx.signoff.replace('\\n', '\n') - ctx.spelling = getattr(args, 'spelling', 'British') + ctx.spelling = args.spelling ctx.comments_path = _write_comments_file(series_data, pwork) _run_and_store_reviews(ctx, args) From patchwork Fri May 1 11:00:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2263 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633321; bh=aP/1ZRhzb/TXkc0F9bTi6Jlg6BRC/5VMX7fI0VhX/Eg=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=HVcz1laUqvUMDnsuLXApSgbQAVzsDE7Afjt8YO8ZJ/x8PHYlx0Z3/vkU01nwrGwNN LB6FfTjxk9WiNRtuWfVxfYrHsXsm0oIUUpQcLfbsPkY38F/N6msYBgTlsoN0jxjYnM lubQUdwQDUw5Pojovy9si11lvzVueA4idWtyHeXQ= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 7E2E86A837 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:01 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id weCAnxBObvXY for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:01 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633321; bh=aP/1ZRhzb/TXkc0F9bTi6Jlg6BRC/5VMX7fI0VhX/Eg=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=HVcz1laUqvUMDnsuLXApSgbQAVzsDE7Afjt8YO8ZJ/x8PHYlx0Z3/vkU01nwrGwNN LB6FfTjxk9WiNRtuWfVxfYrHsXsm0oIUUpQcLfbsPkY38F/N6msYBgTlsoN0jxjYnM lubQUdwQDUw5Pojovy9si11lvzVueA4idWtyHeXQ= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6A1F86A833 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:01 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633318; bh=dMu9N8BuzJt5g7gKaxTaGkt5lZSCjAYRMWZSjj9LIuk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VHknyf+/B9+uSYtorFzvGsMGK1HkeTdZlM1Kwj36E7JAmg/K1qZmVnbNIucZdfpsl 9cbdyFqN03bVhhDq5EEkfkRhArgwfZzyZlu6BPR3L6paD1QwpNOTB3lBtplZWlmMJk MnIaOgGK4C8MsZB/iilXSbKGQa8tIsHuY3JFHfEc= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D678E6A834; Fri, 1 May 2026 05:01:58 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 0ADl68AZzXI3; Fri, 1 May 2026 05:01:58 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633313; bh=BcqUr8WsfdXTvOBysNW8Qfc0dXDsnekRGaeWA4ud4h8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kMHac/TtFm6cXqcFA9s7sJDCL+By60WxRvvjxuFmSYKMYEF0sGASzviGQ1UCczN6e /bss84YI1Sst4qDKM8t9mHlL1u3yLyaEHG63DxepofvPMqvl1xeTiv7UT4XF2FBRIy QeyH2cE+EZoW/6LVsrkyYIDefim78+gneEf0pGJo= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 1C26C6A848; Fri, 1 May 2026 05:01:53 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:12 -0600 Message-ID: <20260501110040.1874719-21-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: CQZERDY7K5KV55T2LDDE6Q2E5DUSKZRR X-Message-ID-Hash: CQZERDY7K5KV55T2LDDE6Q2E5DUSKZRR X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 20/29] patman: Add 'series find' to search by subject fragment List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/CQZERDY7K5KV55T2LDDE6Q2E5DUSKZRR/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> Add a database-only command that searches for series whose cover letter title, per-version description, or patch subjects match a given substring. Multiple matches are shown with the series name, description, and which version/patch matched. This helps locate a series when only a fragment of the subject is remembered, without needing to query patchwork. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/cmdline.py | 6 +++++ tools/patman/control.py | 2 ++ tools/patman/cseries.py | 43 +++++++++++++++++++++++++++++ tools/patman/database.py | 40 +++++++++++++++++++++++++++ tools/patman/patman.rst | 16 ++++++++++- tools/patman/review.py | 4 +-- tools/patman/test_cseries.py | 52 +++++++++++++++++++++++++++++++++++- 7 files changed, 158 insertions(+), 5 deletions(-) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index fa7493bbfa8..dbd0309090d 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -229,6 +229,7 @@ def add_series_subparser(subparsers): help='Manage series of patches') series.defaults_cmds = [ ['set-link', 'fred'], + ['find', 'dummy'], ] series.add_argument( '-n', '--dry-run', action='store_true', dest='dry_run', default=False, @@ -292,6 +293,11 @@ def add_series_subparser(subparsers): _add_show_comments(sall) _add_show_cover_comments(sall) + find = series_subparsers.add_parser( + 'find', help='Search for series by subject fragment') + find.add_argument('query', help='Text to search for') + _add_archived(find) + series_subparsers.add_parser('get-link') series_subparsers.add_parser('inc') info = series_subparsers.add_parser('info') diff --git a/tools/patman/control.py b/tools/patman/control.py index 14d74028ae9..ebe47f96481 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -201,6 +201,8 @@ def do_series(args, test_db=None, pwork=None, cser=None): dry_run=args.dry_run, show_summary=True) elif args.subcmd == 'dec': cser.decrement(args.series, args.dry_run) + elif args.subcmd == 'find': + cser.series_find(args.query, args.include_archived) elif args.subcmd == 'info': cser.show_info(args.series, show_reviews=getattr(args, 'reviews', None)) diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index 893e1b4d212..443fe324c37 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -492,6 +492,49 @@ class Cseries(cser_helper.CseriesHelper): f'{ups:2} {vlist}') print(border) + def series_find(self, query, include_archived=False): + """Search for series by subject fragment + + Args: + query (str): Text to search for in series/version/patch + subjects + include_archived (bool): True to include archived series + """ + col = self.col + rows = self.db.series_search(query, include_archived) + if not rows: + tout.notice(f"No series match '{query}'") + return + + # Deduplicate: for each (series_id, version), keep the best match + # priority: series > version > patch + priority = {'series': 0, 'version': 1, 'patch': 2} + best = {} + for sid, name, desc, version, link, mtype, mtext in rows: + key = (sid, version) + prev = best.get(key) + if prev is None or priority[mtype] < priority[prev[5]]: + best[key] = (sid, name, desc, version, link, mtype, mtext) + + with terminal.pager(): + terminal.tprint(f"{len(best)} match(es) for '{query}':", + colour=col.WHITE, col=col) + last_sid = None + for key in sorted(best, + key=lambda k: (best[k][1], best[k][3])): + sid, name, desc, version, link, mtype, mtext = best[key] + if sid != last_sid: + terminal.tprint('') + terminal.tprint(f'{name}', colour=col.YELLOW, + col=col) + terminal.tprint(f' {desc or "(no description)"}', + col=col) + last_sid = sid + link_str = link or '(no link)' + terminal.tprint(f' v{version} [{link_str}]', + colour=col.BLUE, col=col, newline=False) + terminal.tprint(f' {mtype}: {mtext}', col=col) + def list_patches(self, series, version, show_commit=False, show_patch=False): """List patches in a series diff --git a/tools/patman/database.py b/tools/patman/database.py index 7f1771cbf11..2d6e27ef6e7 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -1452,6 +1452,46 @@ class Database: # pylint:disable=R0904 'WHERE sv.link = ?', (str(link),)) return res.fetchone() + def series_search(self, query, include_archived=False): + """Search for series by subject fragment + + Matches series against the given query string in three places: + - Series description (cover letter title) + - ser_ver description (per-version title) + - Patch subjects (pcommit.subject) + + Args: + query (str): Text to search for (case-insensitive substring) + include_archived (bool): True to include archived series + + Return: + list of tuple: (series_id, name, desc, version, link, match_type, + match_text) where match_type is 'series', 'version' or + 'patch', ordered by series name and version + """ + pat = f'%{query}%' + cond = '' if include_archived else ' AND s.archived = 0' + + sql = ( + "SELECT s.id, s.name, s.desc, sv.version, sv.link, " + "'series' AS mtype, s.desc AS mtext " + 'FROM series s JOIN ser_ver sv ON sv.series_id = s.id ' + f'WHERE s.desc LIKE ?{cond} ' + 'UNION ' + "SELECT s.id, s.name, s.desc, sv.version, sv.link, " + "'version' AS mtype, sv.desc AS mtext " + 'FROM series s JOIN ser_ver sv ON sv.series_id = s.id ' + f'WHERE sv.desc LIKE ?{cond} ' + 'UNION ' + "SELECT s.id, s.name, s.desc, sv.version, sv.link, " + "'patch' AS mtype, pc.subject AS mtext " + 'FROM series s JOIN ser_ver sv ON sv.series_id = s.id ' + 'JOIN pcommit pc ON pc.svid = sv.id ' + f'WHERE pc.subject LIKE ?{cond} ' + 'ORDER BY 2, 4') + res = self.execute(sql, (pat, pat, pat)) + return res.fetchall() + def series_find_review_by_name(self, name): """Find a review series by its description diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index 532db22745f..718078cb882 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -745,11 +745,25 @@ Here is a short overview of the available subcommands: removing that version from the data. If you use this comment on branch 'video3' Patman will delete version 3 and branch 'video3'. + find + Search the database for series matching a subject fragment. + Matches against the series description, per-version description, + and individual patch subjects. Use ``-A`` to include archived + series. Example:: + + patman series find 'fs loader' + get-link Shows the Patchwork link for a series/version + info + Show detailed information about a series, including each + version's link, description, patches and any stored reviews. + Use ``-r`` to include review text. + ls - Lists the series in the database + Lists the series in the database. Use ``-r`` to show only + review series (series fetched by ``patman review``). mark Mark a series with 'Change-Id' tags so that Patman can track patches diff --git a/tools/patman/review.py b/tools/patman/review.py index 76f3dbe274c..056333b5fe3 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -1144,10 +1144,8 @@ def search_patch(pwork, title): async def _query(): query = quote_plus(title, safe=':') - subpath = (f'patches/?project={pwork.proj_id}&q={query}' - '&order=-date&per_page=20') async with aiohttp.ClientSession() as client: - return await pwork._request(client, subpath) + return await pwork.search_patches(client, query) loop = asyncio.get_event_loop() results = loop.run_until_complete(_query()) diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index ea1ac32badc..cb5724631ad 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -4461,7 +4461,6 @@ Date: .* svid2 = cser.db.ser_ver_add(series_id, 2, desc='Second version desc') # Add patches to v1 - from patman.database import Pcommit cser.db.pcommit_add_list(svid1, [ Pcommit(idnum=None, seq=0, subject='Fix the widget', svid=svid1, change_id=None, state=None, @@ -4491,6 +4490,57 @@ Date: .* self.assertIn('Second version desc', output) self.assertIn('Notes: Fixed review feedback', output) + def test_series_find(self): + """Test the series find command""" + cser = self.get_database() + + # Create two series: one with patches matching 'widget', one with + # matching cover description, one with neither + alpha_id = cser.db.series_add('alpha', 'Widget subsystem refresh') + alpha_svid = cser.db.ser_ver_add(alpha_id, 1) + cser.db.pcommit_add_list(alpha_svid, [ + Pcommit(idnum=None, seq=0, subject='Fix the widget', + svid=alpha_svid, change_id=None, state=None, + patch_id=None, num_comments=0)]) + + beta_id = cser.db.series_add('beta', 'Unrelated cleanup') + beta_svid = cser.db.ser_ver_add(beta_id, 1, + desc='Touch up the widget driver') + cser.db.pcommit_add_list(beta_svid, [ + Pcommit(idnum=None, seq=0, subject='cleanup', + svid=beta_svid, change_id=None, state=None, + patch_id=None, num_comments=0)]) + + gamma_id = cser.db.series_add('gamma', 'Something different') + gamma_svid = cser.db.ser_ver_add(gamma_id, 1) + cser.db.pcommit_add_list(gamma_svid, [ + Pcommit(idnum=None, seq=0, subject='other work', + svid=gamma_svid, change_id=None, state=None, + patch_id=None, num_comments=0)]) + cser.commit() + + # Match on cover-letter description and per-version description + with terminal.capture() as (out, _): + cser.series_find('widget') + output = out.getvalue() + self.assertIn('2 match(es)', output) + self.assertIn('alpha', output) + self.assertIn('beta', output) + self.assertNotIn('gamma', output) + + # Match only on patch subject + with terminal.capture() as (out, _): + cser.series_find('Fix the') + output = out.getvalue() + self.assertIn('1 match(es)', output) + self.assertIn('alpha', output) + + # No matches + with terminal.capture() as (out, _): + cser.series_find('nonexistent') + output = out.getvalue() + self.assertIn("No series match 'nonexistent'", output) + # Series link used by the review tests REVIEW_LINK = 497923 REVIEW_LINK_V2 = 497924 From patchwork Fri May 1 11:00:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2264 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633323; bh=5nKOck2qNMEiw+nHfGU26m7RXMs1dv3eVH7ANsUdTtw=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=J0LO1206S41sSNCkduIZBZ9pF36XR0Q/5zI7rF9ixzdOYhveQwMNDDlaSRh4nY0Vw eQvsEBxoW8z5NCDmQ61q75KrByai5ZIsYpU4+InnJ/2pn4Ugmu4EnoIo2YpKccR8QZ 4DBGh4GwL9slqj2pBFxpAqcIG4boVRJP0Dh5iQzc= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E0A8F6A848 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:03 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id c3EAqPj9rfRN for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:03 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633321; bh=5nKOck2qNMEiw+nHfGU26m7RXMs1dv3eVH7ANsUdTtw=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=VIBhN9w1zpqVtPH8dyf7OdC5RDQm2RlR0GCm1gLFX4ShCf1aYfSPyYfzxFWEcNg9A YFF5n1jVSNHJkbryy8hm8R+veIO/28Qf7B0KkD32XgV22lWkOqX518VKpnuNWXVl1n xDk5s2GzYiJcqBZtv3HpINjW2VvD5B4+xYc1yC4c= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DA1696A851 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:01 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633318; bh=GaTquQ9mzv89y3jivy4w/Np7AVSfieYYkK0tVGlk648=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Jz50vBkWizALSQIUuvb8FZ9ufLnuWMdefYVKEXmk3zt9W+2Bq81YNeeEA3CnDfQyB yUbRaPMf2rDogyqCEeNsB0dBeku9FlrgrsaqFI+FBEtwI4b+IddC2NA5sZlXr/gggL bZA+m3MyeHeErk+jDdCaR/4hsZojNMN/WFnB8voM= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DE52D6A82E; Fri, 1 May 2026 05:01:58 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id bm3s7oUPQerx; Fri, 1 May 2026 05:01:58 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633318; bh=hF9tu0spogKvRkUUNlYhdCTQrzXiP3stVjCp6KCN69k=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ijgx6luiAWcDgMJPtKFZmpys42qiQjAec1N22SSLE5ZyGehJBiAiU6hHVhbrnSJfL PATBmcKOYhLBjcryGMUXdvBfYNcE4UbxlYhZil8eBeRN7QnB/GyYVIN9tERmYargkl wD5bbKZKD3JVNo9n6Agb2N1maeWMBZihDARwG+vY= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 2BFA96A833; Fri, 1 May 2026 05:01:58 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:13 -0600 Message-ID: <20260501110040.1874719-22-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: NJVXA5SSQLII7O5JMWXGGYTOZPHZVTEM X-Message-ID-Hash: NJVXA5SSQLII7O5JMWXGGYTOZPHZVTEM X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 21/29] patman: Place base-commit before signature in cover letter List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/NJVXA5SSQLII7O5JMWXGGYTOZPHZVTEM/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> The 'base-commit:' and 'branch:' trailers are currently appended after git's '-- ' signature line, which puts them in the signature block where patchwork does not render them. The b4 tool places these trailers before the signature with a '---' separator above them, which is both more readable and correctly recognised as metadata. Insert the trailers before the '-- ' signature in both the cover letter and individual patches, preceded by '---' and followed by a blank line. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/func_test.py | 35 +++++++++++++---------- tools/patman/patchstream.py | 56 +++++++++++++++++++++++++++++-------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/tools/patman/func_test.py b/tools/patman/func_test.py index 6233957becd..58669865ca7 100644 --- a/tools/patman/func_test.py +++ b/tools/patman/func_test.py @@ -308,11 +308,13 @@ Simon Glass (2): lib/fdtdec.c | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) +--- +base-commit: 1a44532 +branch: mybranch + --\x20 2.7.4 -base-commit: 1a44532 -branch: mybranch ''' lines = tools.read_file(cover_fname, binary=False).splitlines() self.assertEqual( @@ -375,14 +377,16 @@ Changes in v2: lines = tools.read_file(args[0], binary=False).splitlines() pos = lines.index('-- ') - # We expect these lines at the end: - # -- (with trailing space) - # 2.7.4 - # (empty) + # We expect these lines before the signature: + # --- # base-commit: xxx # branch: xxx - self.assertEqual('base-commit: 1a44532', lines[pos + 3]) - self.assertEqual('branch: mybranch', lines[pos + 4]) + # (blank) + # -- (with trailing space) + # 2.7.4 + self.assertEqual('---', lines[pos - 4]) + self.assertEqual('base-commit: 1a44532', lines[pos - 3]) + self.assertEqual('branch: mybranch', lines[pos - 2]) def test_branch(self): """Test creating patches from a branch""" @@ -416,10 +420,12 @@ Changes in v2: self.assertEqual(3, len(patch_files)) cover = tools.read_file(cover_fname, binary=False) - lines = cover.splitlines()[-2:] + cover_lines = cover.splitlines() + sig_pos = cover_lines.index('-- ') base = repo.lookup_reference('refs/heads/base').target - self.assertEqual(f'base-commit: {base}', lines[0]) - self.assertEqual('branch: second', lines[1]) + self.assertEqual(f'base-commit: {base}', + cover_lines[sig_pos - 3]) + self.assertEqual('branch: second', cover_lines[sig_pos - 2]) # Make sure that the base-commit is not present when it is in the # cover letter @@ -435,11 +441,12 @@ Changes in v2: self.assertEqual(2, len(patch_files)) cover = tools.read_file(cover_fname, binary=False) - lines = cover.splitlines()[-2:] + cover_lines = cover.splitlines() + sig_pos = cover_lines.index('-- ') base2 = repo.lookup_reference('refs/heads/second') ref = base2.peel(pygit2.GIT_OBJ_COMMIT).parents[0].parents[0].id - self.assertEqual(f'base-commit: {ref}', lines[0]) - self.assertEqual('branch: second', lines[1]) + self.assertEqual(f'base-commit: {ref}', cover_lines[sig_pos - 3]) + self.assertEqual('branch: second', cover_lines[sig_pos - 2]) finally: os.chdir(orig_dir) diff --git a/tools/patman/patchstream.py b/tools/patman/patchstream.py index 27cd1980e38..f1340a19d67 100644 --- a/tools/patman/patchstream.py +++ b/tools/patman/patchstream.py @@ -687,6 +687,21 @@ class PatchStream: self._write_message_id(outfd) + inserted_base = False + + def _write_base(): + if not self.series.base_commit and not self.series.branch: + return + # '---' separator line before the trailers for readability + # and to match the b4 convention + print('---', file=outfd) + if self.series.base_commit: + print(f'base-commit: {self.series.base_commit.hash}', + file=outfd) + if self.series.branch: + print(f'branch: {self.series.branch}', file=outfd) + print('', file=outfd) + while True: line = infd.readline() if not line: @@ -705,16 +720,18 @@ class PatchStream: if self.blank_count and (line == '-- ' or match): self._add_warn("Found possible blank line(s) at end of file '%s'" % last_fname) + # Write base-commit/branch before git's '-- ' signature + # so patchwork parses them as the series base. + if (self.insert_base_commit and not inserted_base + and line == '-- '): + _write_base() + inserted_base = True outfd.write('+\n' * self.blank_count) outfd.write(line + '\n') self.blank_count = 0 self.finalise() - if self.insert_base_commit: - if self.series.base_commit: - print(f'base-commit: {self.series.base_commit.hash}', - file=outfd) - if self.series.branch: - print(f'branch: {self.series.branch}', file=outfd) + if self.insert_base_commit and not inserted_base: + _write_base() def insert_tags(msg, tags_to_emit): @@ -924,6 +941,21 @@ def insert_cover_letter(fname, series, count, cwd=None): fil = open(fname, 'w') text = series.cover prefix = series.GetPatchPrefix() + inserted_base = False + + def _insert_base(): + """Write base-commit and branch trailers before the signature""" + if not series.base_commit and not series.branch: + return + # '---' separator line before the trailers for readability + # and to match the b4 convention + fil.write('---\n') + if series.base_commit: + fil.write(f'base-commit: {series.base_commit.hash}\n') + if series.branch: + fil.write(f'branch: {series.branch}\n') + fil.write('\n') + for line in lines: if line.startswith('Subject:'): # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc @@ -941,12 +973,14 @@ def insert_cover_letter(fname, series, count, cwd=None): # Now the change list out = series.MakeChangeLog(None) line += '\n' + '\n'.join(out) + elif line.startswith('-- ') and not inserted_base: + # Insert base-commit/branch before git's signature so that + # patchwork parses them as the series base. + _insert_base() + inserted_base = True fil.write(line) - # Insert the base commit and branch - if series.base_commit: - print(f'base-commit: {series.base_commit.hash}', file=fil) - if series.branch: - print(f'branch: {series.branch}', file=fil) + if not inserted_base: + _insert_base() fil.close() From patchwork Fri May 1 11:00:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2266 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633330; bh=/afbTpyi6knU1drB9Fked3uupCwo+IvjATr1G/E0tVg=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=eYZDD7IYfvSNcmEYNzBtr0HndiATiO8//7RTrR4oIktk4CgfIngTiVki7XIS9Uhcs um4XyYPyDBMJa91oqC8gcG1IJxeya1+bEQP6VOtyUD13mGStomDQyBfaL3ibc2tAr6 HHNyzdbf1F3K0Xl6LNv0OwYuKRNmJlqcEVthoQIw= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 15C896A848 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:10 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id rTPIvGINK09L for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:10 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633330; bh=/afbTpyi6knU1drB9Fked3uupCwo+IvjATr1G/E0tVg=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=eYZDD7IYfvSNcmEYNzBtr0HndiATiO8//7RTrR4oIktk4CgfIngTiVki7XIS9Uhcs um4XyYPyDBMJa91oqC8gcG1IJxeya1+bEQP6VOtyUD13mGStomDQyBfaL3ibc2tAr6 HHNyzdbf1F3K0Xl6LNv0OwYuKRNmJlqcEVthoQIw= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 03AC16A833 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:10 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633326; bh=E1YvVl12W+VX/MjBVlqRlDSE+WKg0WEMgBLPKTcnX1g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=L0109zpIvCpna6zef8kOCHUfD2F1aSzxd0eH7eljo7p34rfr84jIf1A3AVubk9/LX LISJoX46MPomECmnbwOes8wkqq0NFHxP7feujN3eyyaN+9/Hn+Yzhq7pWk0I54kPR4 uSkyxUjc2falVNidPJcHMl4qf1ZWLM8wd7KTk4gU= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AD1E16A837; Fri, 1 May 2026 05:02:06 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id ue4VZUbRDEYu; Fri, 1 May 2026 05:02:06 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633319; bh=+qNDXojS4qWuTki6Y8NPQqnkLnpLZJUh70aG5Ai2hZ4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RfRekTynIAMONdaEHjy3P3F2zzRWp4N0UCqACYSzamO+JQfQ1KT3KqjFpn7ZfN8ka bVguR8mT8R0ORzSid3wJFGsiY0iAw+tFI8yhVFIKY8NjJByAK0e7uK54MgE7Cg6c0y i8Uih6HCoX53na6bLYGz+GZjckC2uKxLTC0jn020= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 4F0B66A7AF; Fri, 1 May 2026 05:01:59 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:14 -0600 Message-ID: <20260501110040.1874719-23-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 6ODBS5NP522UNH2GLEBNCJDJMY6CEUID X-Message-ID-Hash: 6ODBS5NP522UNH2GLEBNCJDJMY6CEUID X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 22/29] patman: Stash untracked files before reviewing List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/6ODBS5NP522UNH2GLEBNCJDJMY6CEUID/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> When starting a review, 'git stash' does not include untracked files by default. If the current branch has build artefacts or other untracked files (e.g. rust files present on ci/master), those files remain in the working tree when the review branch is checked out and get picked up by the review process. Pass include_untracked=True to gitutil.stash_save() so untracked files are also stashed, and restored on 'git stash pop' when the review finishes. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/review.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/patman/review.py b/tools/patman/review.py index 056333b5fe3..0508b01e5ec 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -1944,7 +1944,10 @@ def do_review(args, pwork, cser): ctx.repo_path = gitutil.get_top_level() orig_branch = gitutil.current_branch(ctx.repo_path) try: - stash_out = gitutil.stash_save(ctx.repo_path) + # Stash untracked files too, so build artefacts from the + # current branch don't leak into the review branch + stash_out = gitutil.stash_save(ctx.repo_path, + include_untracked=True) if 'No local changes' not in stash_out: had_stash = True except command.CommandExc: From patchwork Fri May 1 11:00:15 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2265 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633328; bh=bfZi/eV3WRLATwRUkjDkgnvUCEVy4n7lTFtAxFCucn0=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=mz7CXDXCoqzaUHwhJ/UMkPQDF0UpqbYk+0At9Dgh8HieHue543FoNr+5vJyzkk51D DfHOMDSNYVy+cQ8/5rv3bnVkZ0ibLAdcBdIkqhngoH8ll+L7wQorFavBOnbxa2R4dD 6AQGg7Aj5qASzaMTkNrHi08hZFTxnRHneCOuB8AY= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6B0006A82E for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:08 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Oj_nGlIxJaT3 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:08 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633328; bh=bfZi/eV3WRLATwRUkjDkgnvUCEVy4n7lTFtAxFCucn0=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=mz7CXDXCoqzaUHwhJ/UMkPQDF0UpqbYk+0At9Dgh8HieHue543FoNr+5vJyzkk51D DfHOMDSNYVy+cQ8/5rv3bnVkZ0ibLAdcBdIkqhngoH8ll+L7wQorFavBOnbxa2R4dD 6AQGg7Aj5qASzaMTkNrHi08hZFTxnRHneCOuB8AY= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 542C66A833 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:08 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633326; bh=BH+OvojW/OMbFSHOrAca/fO+laq7fqZYMljbli3t30U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=l7jlgFImFb9Ys72W5fs4Tb/JnDxoBTk7aWf+bYOYJyhbhykQ4zDb1HFpblnL6c0B9 HOzbKMpPyfkq13dDJJeNdfNQeBIGrLGcHetm+UbHyxlxgieroHph3W0gS9jYlSslZh bAwDBQcJ0rBNteQzmy+shnvUSpRmJviBnqV9O+wY= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AD1D26A82E; Fri, 1 May 2026 05:02:06 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id dSLced-Jw6Nv; Fri, 1 May 2026 05:02:06 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633324; bh=9aNcfs393rxSil1Bkn8x4F1yDpv/gkkMhXSA4vIepaw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Imak5sMMhlAZ1UQXtdVrsa0OBAP9ePi5sfdgFl3L7RSpRPo0u0io6YV9je77HnIfC wG0YXf9y8jTaC4bzhLAB+S48RF98LKfvPzGQI8+nWQDxajtblTaGkJJaTDwG21PXKa 7/gWTpQnPNN0rLi6sODoezn5s/uf+CmpSWRSVtGw= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 40BA06A850; Fri, 1 May 2026 05:02:04 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:15 -0600 Message-ID: <20260501110040.1874719-24-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: ECGGZGMG2U4M43DFBQZWJPW4KH6UPOWQ X-Message-ID-Hash: ECGGZGMG2U4M43DFBQZWJPW4KH6UPOWQ X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 23/29] patman: Force diff headers in every review comment List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/ECGGZGMG2U4M43DFBQZWJPW4KH6UPOWQ/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> The review agent sometimes omits the 'diff --git' and '@@' header lines from each COMMENT block, quoting only the code being commented on. Without the headers the reader cannot tell which file the comment refers to. Strengthen the rule with explicit BAD and GOOD examples, and a note that the headers must be copied verbatim from 'git show'. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/review.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) -- 2.43.0 diff --git a/tools/patman/review.py b/tools/patman/review.py index 0508b01e5ec..c173a9938d5 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -369,11 +369,25 @@ VERDICT: changes_needed Rules: - Always start with GREETING: (first name or empty) -- Each COMMENT: block MUST include the diff header lines (the - 'diff --git' and '@@' lines) before the quoted code, so the file - and line number are clear -- Each COMMENT: block starts with quoted diff lines (using '> ' prefix), - followed by a blank line, then your comment text +- Each COMMENT: block MUST start with the two diff header lines + copied VERBATIM from 'git show {commit_hash}': + > diff --git a/<path> b/<path> + > @@ -<old>,<n> +<new>,<m> @@ <function-context> + then the quoted code lines (with '> ' prefix), then a blank line, + then your comment. This is NOT optional — without the headers, the + reader cannot tell which file or function the comment refers to. + BAD: + COMMENT: + > +#include <foo.h> + + This include is unnecessary. + GOOD: + COMMENT: + > diff --git a/drivers/clk/qcom/clock-ipq5210.c b/drivers/clk/qcom/clock-ipq5210.c + > @@ -0,0 +1,97 @@ + > +#include <foo.h> + + This include is unnecessary. - Quote enough context from the diff to identify the location - Be specific and constructive, but brief — use as few words as possible to make the point. Avoid restating what the code does; From patchwork Fri May 1 11:00:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2267 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633332; bh=nX3Sw7qjuxlF8eIFh2tqSvOSSyW86RH42cJjoDXheJI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=iKclpdXXm+4SCEWBFIqm6i91M3h3eAK7KvCEiH2rOm6hcxT9byMM6Mz1c19a0UnoO 7XlQ0qYp887/rXcLRztpjoCEsazr91Krvp7U++9vYgm85Ll6Z0izDzqcQpDWh1HUS2 3e4XPdjcukpV9MFASdhjGstooXSEKAa0unIw/BwU= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A1FED6A853 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:12 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id I9Hh-Iv9R6Db for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:12 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633330; bh=nX3Sw7qjuxlF8eIFh2tqSvOSSyW86RH42cJjoDXheJI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=ZZ+AVzkGH8ILNJ863ZeqFd5U2T4ScGij710weslMOKys7z65lWktzzst8JRBPd9Pg PCRDRe6engvaV/KeUN7nspISFlSHVEF1MkodQ/SXhEPEsgTuw6duWQHmZJexVgZzDQ LwjzGWD0oi6sqKq2+jNqDV6YYZQZUgOaPWwAYfxY= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9CC196A849 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:10 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633326; bh=RoyRfe/07Sy3jFP6+gG7rgKhLKCL+hp3NN9AIarmhtg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=asH4DfAHH9v/W/Yolh+IUnQPiVn+elKgkDmr8Kj0UtUzxmOybbPh4qsPZ15XfLSLE LEiQsYAc2Ro8XXl8GlRrZMDFJHCtXEnHlT7eTFwyCO2dBLLoZhQ1R1jRDMUEWKG67w fW2dT4braMaLOVxfiRDFpqAkCvUdTMCOQFhQHscw= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BFF6F6A7AF; Fri, 1 May 2026 05:02:06 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id AX_e0sj1RwBQ; Fri, 1 May 2026 05:02:06 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633325; bh=B5l/Q0U6zQ2YzEmo05UKsE23M7p6Ng0JICUmT+swYDQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GYZ6vrHDfYT6w5ijPNT50cERsY4AlEg66B5qkQY3pq6gcULKw8e/DKoIcaWaFpvlU ZpXSI9/u6oFwvaNMuysYNGWjHfABwvGV+HzwEAbt91ZVyCsGQCdham1MzHSMSImLtV 463dpfV3hqaGJSh98QZ4a/NvfNPoIUZ4q0gvlWzY= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 739606A833; Fri, 1 May 2026 05:02:05 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:16 -0600 Message-ID: <20260501110040.1874719-25-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 6XE24RHEHK4HTOTM5MXOXCTG7TJPZRPV X-Message-ID-Hash: 6XE24RHEHK4HTOTM5MXOXCTG7TJPZRPV X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 24/29] patman: Tell the agent never to reconstruct quoted diff lines List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/6XE24RHEHK4HTOTM5MXOXCTG7TJPZRPV/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> The review agent occasionally mixes content between nearby macros or functions when quoting the diff, so the comment ends up pointing at a problem that does not exist in the patch. Add a strong rule that every quoted line must be copied verbatim from 'git show', not reconstructed from memory. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/review.py | 8 ++++++++ 1 file changed, 8 insertions(+) -- 2.43.0 diff --git a/tools/patman/review.py b/tools/patman/review.py index c173a9938d5..29f618641bd 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -389,6 +389,14 @@ Rules: This include is unnecessary. - Quote enough context from the diff to identify the location +- CRITICAL: Every quoted line MUST be copied EXACTLY from the output + of 'git show {commit_hash}'. Do NOT reconstruct, paraphrase, or + combine lines from memory. Do NOT mix content between nearby macros, + functions or files. If you need to check a line before quoting it, + run 'git show {commit_hash}' again and copy the characters exactly, + including whitespace, punctuation, and backslashes. A comment that + points at a problem in a quoted line that does not actually exist + in the patch is worse than no comment at all. - Be specific and constructive, but brief — use as few words as possible to make the point. Avoid restating what the code does; - NEVER use backticks — this is plain-text email, not markdown. From patchwork Fri May 1 11:00:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2268 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633332; bh=wCPvxetNysPaPo2wQprjcLjgnULO3LumQneV6BegbdI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=Zdv973Azol9k45denYWMrEqk32VpkIF/B0m2TQT85UrG2tq6uYm5aAtyRqC0eZy1z bICj68ssFeuiCrJdE0WoXuQGAsl9UIu57Gtw/ommjIQXGaaDjDDuwCVjDUx1faNJiZ 1tcMyrVsakmkOo2emZdrt8FVuefYxzXNk/ca3Z/g= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E9C0E6A848 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:12 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Y_j6Vv2SPvOa for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:12 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633331; bh=wCPvxetNysPaPo2wQprjcLjgnULO3LumQneV6BegbdI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=ooJbqxcunJaVdulpBpffX8TszYETFI79g2EQlqNwMP/cLIHz5ZQuewVNW9Zqpa8aO IiWplcdMs5AKeQH7cgGNuDpMeB1P37Da7Mn3pAb3LzuJxtHy+NsWco4JSJ/STbreru ICKUZQjgDT0EUr7OJ+tFXO221fQRGPao0Y64GwLY= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2225A6A857 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:11 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633326; bh=DbI9PAMZYSKg7BGwZJssaJehSeIvZkubdFJDTjkv1Y0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=r48yZW9MrdHVYsderPVRjPlIwCsxjhBzUoPrFgv6zv4SoDsf4OwcT5iqC5xvM9Fn0 M///FMn3p8a4paUC7gKJJDO8Shu3zYDG5b2pvXqkZYZzAhY3ZvsBFoirbbtt+kVikN 1fIJNl7C+P9OwOrXNIjK6tO4wZdsl2+24O8hRMm0= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DAA9A6A833; Fri, 1 May 2026 05:02:06 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id Ve0DgJS77-HC; Fri, 1 May 2026 05:02:06 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633326; bh=VHOhx/nX2Efen/+iWa0XyBJfv5YzLaz43H7UstTnQiI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OegmJZeMLq9+1HCuFWOaCFoFHvCTtmVjHGFEMse64qkoDlXePKwkyQr54gQzsq2EN cvQ1KEfOVJEB0RAo5XE+npfdghbTyw3ogCzkKK480dRF9DEvQ8SghNC083/jnNXsre rNHpEW9z53o0PbEzN2NPkadhBGDYexktqJFh45Jw= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 733636A834; Fri, 1 May 2026 05:02:06 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:17 -0600 Message-ID: <20260501110040.1874719-26-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 7G4NUAJVQU6NFYSCH5ZB4LDORNFMXBQP X-Message-ID-Hash: 7G4NUAJVQU6NFYSCH5ZB4LDORNFMXBQP X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 25/29] patman: Keep code quotes out of the cover-letter review List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/7G4NUAJVQU6NFYSCH5ZB4LDORNFMXBQP/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> The cover-letter review prompt let the agent insert inline code snippets to illustrate series-level points. The snippets end up indented plain text with no diff header, and sometimes refer to code outside the series (e.g. a previously reverted patch), which the recipient cannot verify from the series being reviewed. Tell the agent to stick to prose in the cover-letter reply; code examples belong in the per-patch reviews where they can be quoted properly from the diff. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/review.py | 3 +++ 1 file changed, 3 insertions(+) -- 2.43.0 diff --git a/tools/patman/review.py b/tools/patman/review.py index 29f618641bd..eb8a3da63b3 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -511,6 +511,9 @@ Rules: quote identifiers that are obviously code (e.g. CONFIG_FOO) - Never put a period directly after a code identifier — rephrase, omit the period, or use an em dash to start the next clause +- Do NOT quote code fragments in the cover-letter reply — code + belongs in the per-patch reviews. Describe series-level issues in + prose only. - Use {ctx.spelling} spelling - Be brief — only raise series-level concerns, not per-patch nits - Do NOT repeat issues that belong on individual patches From patchwork Fri May 1 11:00:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2269 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633337; bh=Z82JtSnFdke3Q+a5W55mpxAYnXfSKwNrBZ6y+lKrHyA=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=iM8rXoOESBjmbLXcsCBX/erViyAUrLaT3bxbgwIXEQrzCl1cM01j2MFbIl1nPOXKl 8KpVgzCexB9butmZr+gLikp6jQ8scBXpM9JEQdaZzqgf7Xe4recBtaNe4Tj+GcoWEX WHw2q++4mXmGaN0SxX0RMULtpRUp6u4UmAeT1hb4= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A0E616A837 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:17 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id aMtx8Z1DdWG8 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:17 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633337; bh=Z82JtSnFdke3Q+a5W55mpxAYnXfSKwNrBZ6y+lKrHyA=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=iM8rXoOESBjmbLXcsCBX/erViyAUrLaT3bxbgwIXEQrzCl1cM01j2MFbIl1nPOXKl 8KpVgzCexB9butmZr+gLikp6jQ8scBXpM9JEQdaZzqgf7Xe4recBtaNe4Tj+GcoWEX WHw2q++4mXmGaN0SxX0RMULtpRUp6u4UmAeT1hb4= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 90C336A82E for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:17 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633335; bh=3BoLaK2E8TEV4rNeap95a0ht2Uu4ZcmbnmCF5RmzDqE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AcZMKoxs6biUFKaFVnJ8FINPQRoXgeTybyclYkZGesAWASe96qbcXcSK4Nuouj0Ol /QI0LexK8WNk3Ot8Ly16IECphdJECnJ68yEZNDFzsI3FQrHt1El1MhF6gjr7gJ0vv6 k7Y/f3ra3nZiovjkaR2bmc65JiJ5GYblpKBahwsQ= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4D6106A82E; Fri, 1 May 2026 05:02:15 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 7wmC3rZuCU5u; Fri, 1 May 2026 05:02:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633327; bh=k7XL7QqwOUcK1c7AxIHN/ol+2L0QpBLChclHO+5Swug=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fx5sHr1BRZDSpma6aNZ95hJ8z3PXfxvPd/3VHYkKlji7Czv7grFUD78vabLg4sBn5 3XhJiVdm2w0WGBtC80G+T9wMnFd11h0XVhqSTWqs+ziBx3Clhi4m6Gzd93wS4GUXZn OCHb7fNILRq2U2mp7KpgBd/zTTGP7NVxiK0pzkrQ= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 665166A7AF; Fri, 1 May 2026 05:02:07 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:18 -0600 Message-ID: <20260501110040.1874719-27-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: TSJEADEE7P7SA6DV6BOKF4PRURUM76QD X-Message-ID-Hash: TSJEADEE7P7SA6DV6BOKF4PRURUM76QD X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 26/29] patman: Use single quotes for all quoted tokens in reviews List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/TSJEADEE7P7SA6DV6BOKF4PRURUM76QD/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> The review prompt told the agent to quote string literals with double quotes, which ends up looking out of place next to the surrounding single-quoted identifiers (e.g. a review would say "handosff" next to 'my_var'). Use single quotes for everything except bare function names (which keep their parentheses). Add a cleanup pass that rewrites short double-quoted tokens to single quotes, leaving longer phrases (which are more likely to be intentional quotations) alone. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/review.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) -- 2.43.0 diff --git a/tools/patman/review.py b/tools/patman/review.py index eb8a3da63b3..054d18c6d80 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -401,9 +401,9 @@ Rules: possible to make the point. Avoid restating what the code does; - NEVER use backticks — this is plain-text email, not markdown. For functions, always use parentheses with no quotes: malloc() not - 'malloc()' or `malloc`. For other identifiers (variables, struct - names, filenames) use single quotes: 'my_var' not "my_var", unless - it is a string literal (use double quotes for strings). Do not + 'malloc()' or `malloc`. For all other quoting (identifiers, + filenames, string literals, misspelled words, etc.) use single + quotes: 'my_var' and 'handoff' not "my_var" or "handoff". Do not quote identifiers that are obviously code (e.g. CONFIG_FOO) - Never put a period directly after a code identifier — rephrase, omit the period, or use an em dash to start the next clause @@ -505,9 +505,9 @@ VERDICT: changes_needed Rules: - NEVER use backticks — this is plain-text email, not markdown. For functions, always use parentheses with no quotes: malloc() not - 'malloc()' or `malloc`. For other identifiers (variables, struct - names, filenames) use single quotes: 'my_var' not "my_var", unless - it is a string literal (use double quotes for strings). Do not + 'malloc()' or `malloc`. For all other quoting (identifiers, + filenames, string literals, misspelled words, etc.) use single + quotes: 'my_var' and 'handoff' not "my_var" or "handoff". Do not quote identifiers that are obviously code (e.g. CONFIG_FOO) - Never put a period directly after a code identifier — rephrase, omit the period, or use an em dash to start the next clause @@ -759,6 +759,14 @@ def cleanup_review_text(text): # Remove double quotes around function references: "func()" -> func() text = re.sub(r'"(\w+\(\))"', r'\1', text) + # Convert double-quoted short tokens to single quotes: + # "handoff" -> 'handoff'. Leave longer quoted text (full sentences + # or phrases) alone, since they may be intentional quotations. + text = re.sub(r'"([^"\n]{1,40})"', + lambda m: f"'{m.group(1)}'" + if ' ' not in m.group(1) else m.group(0), + text) + return text From patchwork Fri May 1 11:00:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2270 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633340; bh=jlWFDyo/omKqnUp7cvFMY3+PFbJBlt6dekpvWsmMLFE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=MkCCfRm9mVyXyLlR7f0CdgArhisDQcMvX6enpin9N2agKde8SSuSyRHMzsk8VG0Zw h5elVSm9qLX+he/0DiB0gLCl36eAgQ/eczkWKF76n1/UW8nH7hcvwhrjQTPKS53+WS +oUPFkrxZqTKNDYAf48kMvwgR8lISOIvbYMV2FzQ= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0BA606A833 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:20 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id eZTQQGNkO-L1 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:19 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633338; bh=jlWFDyo/omKqnUp7cvFMY3+PFbJBlt6dekpvWsmMLFE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=q6UDYSX0K8+xI7gd+cZ7mRv54xbW73ntHrjFJYMyba1XAP7FUfoGjf88E5OqUSdIe uyIORrcFhm3sCnVirn7b98aRoJtL3qu8Aes+11wXKqKpB59dHRJt1qLvTiOfsqV6TV ZfGmOO5olBDah0K35u1zDtXZoOMyHvH4jfd/MTY0= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 82CCF6A837 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:18 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633335; bh=tIdVh/z4bDxLfzcw9AXqnnt7zfhoMdLu0TVP4EyjPXQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VAuZejgQy0yeSOPUVxjZk5tPJd3bDlv3S9bYLj51La9SnRxjPUbrXW72C48ECRs22 yJJ9gZK447Ulqm3y+WpgovjNgKeePwlfpTMSYEgUjnta12Q63bL3SoUviM/h/p0JvQ rw9VJucpQ2TsX9uVdYYaE4VvCalOuz7QsShL//Hc= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 56E496A7AF; Fri, 1 May 2026 05:02:15 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id Yw05uvzqYOqm; Fri, 1 May 2026 05:02:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633332; bh=96fveYdtdD7/wWzD+bSUpc45+mYDD7xT1BbFaajPWWw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TW4nztz9k7AL22KPA+EyDMXpFUwPnvwy0Xeg+eLFmFkL4x4K3qbeKJGBheSfc4EVJ zRq3rJxxjwmN8A6V/uQ/no9fjJ1QfdsZn1g63gtm8+4WzdoACJJz3K2yuFLH1C5Op1 06JS4hQTcn7gtniRFDtsekW4UocLuVJYTwnLC7Ik= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 619946A833; Fri, 1 May 2026 05:02:12 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:19 -0600 Message-ID: <20260501110040.1874719-28-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 5KZCUNLZ3XSUJG43RMR6WQJOSTUSJKPQ X-Message-ID-Hash: 5KZCUNLZ3XSUJG43RMR6WQJOSTUSJKPQ X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 27/29] patman: Allow choosing the review base branch List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/5KZCUNLZ3XSUJG43RMR6WQJOSTUSJKPQ/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> The review command currently picks '<upstream>/next' as its base branch whenever that ref exists, falling back to '<upstream>/master' only if next is missing. That works during a release cycle, but it mishandles the period right after a release: the next branch has just been merged into master and contains no exclusive commits, yet patman still applies onto it. The mirror-image case occurs when a reviewer wants to apply onto master even though next has diverged. Honour a new -b/--base-branch flag so the user can override the choice explicitly. When the flag is unset, decide automatically by asking gitutil.count_revs() for the count of commits in '<upstream>/master..<upstream>/next'. If next is ahead of master, use it; otherwise fall back to master. After a release, master contains everything next had, so count_revs() returns zero and patman picks master until next diverges again at the next cycle's RC1. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/cmdline.py | 5 +++ tools/patman/patman.rst | 12 +++++++ tools/patman/review.py | 24 +++++++++---- tools/patman/test_cseries.py | 69 ++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index dbd0309090d..10723120c4e 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -589,6 +589,11 @@ def add_review_subparser(subparsers): review.add_argument( '-U', '--upstream', type=str, default=None, help='Upstream name (for patchwork URL lookup)') + review.add_argument( + '-b', '--base-branch', type=str, default=None, + help="Base branch to apply review patches onto (e.g. 'us/master', " + "'us/next'). If unset, picks the upstream's '/next' branch " + "when it has commits ahead of '/master', otherwise '/master'.") review.add_argument( '--apply-only', action='store_true', help='Only download and apply patches, skip AI review') diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index 718078cb882..aba6d8905fb 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -1297,6 +1297,18 @@ without calling the Gmail API. Use ``--apply-only`` to download and apply patches without running the AI review -- useful for checking that patches apply cleanly. +By default the review branch is created from ``<upstream>/next`` when +that branch has commits ahead of ``<upstream>/master``, and from +``<upstream>/master`` otherwise. This tracks U-Boot's release cadence: +right after a release ``next`` is merged into ``master`` and stays +empty until the next cycle's RC1, so reviews land on ``master`` during +that window and switch back to ``next`` automatically once it +diverges. Use ``-b`` / ``--base-branch`` to override the choice for a +particular run:: + + patman review -s 497923 -U us -b us/master \ + --reviewer 'Your Name <your@email>' + Use ``-f`` / ``--force`` to re-review a series that has already been reviewed. This deletes the old review records and runs the review again:: diff --git a/tools/patman/review.py b/tools/patman/review.py index 054d18c6d80..524a06df3a0 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -1741,25 +1741,35 @@ def _draft_stored_reviews(args, reviews, series_data, pwork, cser): def _get_upstream_branch(args, cser): - """Determine the upstream branch for applying patches + """Determine the base branch for applying patches - Prefers 'next' over 'master' if it exists. + Honours --base-branch if the user supplied one. Otherwise prefers + '<upstream>/next' when it has commits ahead of '<upstream>/master', + falling back to '<upstream>/master' when next is empty (e.g. just + after a release, when next has been merged into master and has not + yet been reopened for the next cycle). Args: args (Namespace): Command-line arguments cser (Cseries): Open cseries instance Returns: - str: Upstream branch ref, e.g. 'us/next' + str: Base branch ref, e.g. 'us/next' or 'us/master' """ + if args.base_branch: + return args.base_branch ups = args.upstream if not ups: ups = cser.db.upstream_get_default() if ups: - branch = f'{ups}/next' - if not gitutil.ref_exists(branch): - branch = f'{ups}/master' - return branch + next_branch = f'{ups}/next' + master_branch = f'{ups}/master' + if gitutil.ref_exists(next_branch): + ahead = gitutil.count_revs( + None, f'{master_branch}..{next_branch}') + if ahead: + return next_branch + return master_branch return 'origin/master' diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index cb5724631ad..c267b048fc5 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -21,6 +21,7 @@ from u_boot_pylib import tools from patman import cmdline from patman import control from patman import cser_helper +from patman import review from patman import cseries from patman.database import Pcommit from patman import database @@ -5223,3 +5224,71 @@ VERDICT: changes_needed""" self.assertEqual(2, len(notes)) self.assertEqual(1, notes[0][0]) self.assertEqual(3, notes[1][0]) + + +class TestGetUpstreamBranch(unittest.TestCase): + """Tests for review._get_upstream_branch() base-branch selection.""" + + def _cser(self, default_upstream=None): + cser = mock.Mock() + cser.db.upstream_get_default.return_value = default_upstream + return cser + + def test_explicit_base_branch_wins(self): + """An explicit -b/--base-branch overrides every other check.""" + args = Namespace(base_branch='custom/branch', upstream='us') + with mock.patch('patman.review.gitutil') as gu: + self.assertEqual( + 'custom/branch', + review._get_upstream_branch(args, self._cser())) + gu.ref_exists.assert_not_called() + gu.count_revs.assert_not_called() + + def test_next_ahead_of_master_picks_next(self): + """When next has commits ahead of master, next is chosen.""" + args = Namespace(base_branch=None, upstream='us') + with mock.patch('patman.review.gitutil.ref_exists', + return_value=True), \ + mock.patch('patman.review.gitutil.count_revs', + return_value=3): + self.assertEqual( + 'us/next', + review._get_upstream_branch(args, self._cser())) + + def test_next_empty_falls_back_to_master(self): + """When next exists but has no commits ahead, master is chosen.""" + args = Namespace(base_branch=None, upstream='us') + with mock.patch('patman.review.gitutil.ref_exists', + return_value=True), \ + mock.patch('patman.review.gitutil.count_revs', + return_value=0): + self.assertEqual( + 'us/master', + review._get_upstream_branch(args, self._cser())) + + def test_next_missing_falls_back_to_master(self): + """When next does not exist at all, master is chosen.""" + args = Namespace(base_branch=None, upstream='us') + with mock.patch('patman.review.gitutil.ref_exists', + return_value=False): + self.assertEqual( + 'us/master', + review._get_upstream_branch(args, self._cser())) + + def test_default_upstream_used_when_unset(self): + """When args.upstream is unset, the cser default is consulted.""" + args = Namespace(base_branch=None, upstream=None) + cser = self._cser(default_upstream='us') + with mock.patch('patman.review.gitutil.ref_exists', + return_value=True), \ + mock.patch('patman.review.gitutil.count_revs', + return_value=5): + self.assertEqual( + 'us/next', review._get_upstream_branch(args, cser)) + + def test_no_upstream_returns_origin_master(self): + """With no upstream configured anywhere, return 'origin/master'.""" + args = Namespace(base_branch=None, upstream=None) + self.assertEqual( + 'origin/master', + review._get_upstream_branch(args, self._cser())) From patchwork Fri May 1 11:00:20 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2271 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633340; bh=jq3pixFgNP/KtSlwjlLi8y3bbcHe+6xVAHIaD328jO8=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=oPibbILjdIazAnFkpdbnbA2oIebcFy4+COHSqTh1w1rtGkYbb721b1FLhPjjwwlmE K+6cHI6N+kZlpFowxDRrzFqhlonS49GR0atpZzDDelyd7SL9Eyktf07spqvizqiWa7 NWqWdotSbotnu+f6qZ3dyyQf1RyQib+P+WbBViK4= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 709676A857 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:20 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id kwnfS7s1YFQ8 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:20 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633338; bh=jq3pixFgNP/KtSlwjlLi8y3bbcHe+6xVAHIaD328jO8=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=O4NGtBc28c6yXPTan1gwSTqmRGKW2B16q797giBcmu518pXpOC8h1TTL6bf3PA7LS woAcnE3Q0da6gdT3zAFgBXJ+ZLZEW7KpvEK+/W0ho7YcP7QIdhHKHUgdM/Imxozj3W lYBmvLiZPMl0cYHOcjPgAguK5OWkUHbv7a4D43fo= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E3F4D6A858 for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:18 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633337; bh=6rM4TUyBT0IHvdcQ1RABxgeU0JT3jX7ohU0aPFLNFPw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lVZOZ6S42sHISQq4o3Rmcg9ZKDt/6SA0aeARlebkVd1T+O5gdTARi1oWepo1Lx7Jh aotBW7lt3Cv7Itc9m22T+pwyoZhuAHYGjOe/WiGwa4Qi/TvU10fdwh2dJD6Zm7W3U5 TyuPhRyMFl2/AFK6r9b7F6mTnDc9MQPLjojf9DAk= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C0D276A84E; Fri, 1 May 2026 05:02:17 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id H9gylCIja5WA; Fri, 1 May 2026 05:02:17 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633333; bh=00z5vegfOPcaCnlbBNunw/9CmPhcowqwmfbxdyxYu6o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=E/eRQYP50SU5TMX80gjVbUlcPDBcwlNBR9a9uFIU+YdcvYe87hsQSiX/fi1XEU8n4 ZyDwfZ6qX6Y4D6oyVA4kXR4ugX7WrX0DCHZrCznXWczLoVgvk4tyCexL2QiQKPk4UI Lzg7Q88u3tmLOKEbN4IpXNggV+qdAjmFmgJM5dbA= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id AB7286A834; Fri, 1 May 2026 05:02:13 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:20 -0600 Message-ID: <20260501110040.1874719-29-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 3DBMITNCB4T27I6TSIO52I2J5KYLDHZO X-Message-ID-Hash: 3DBMITNCB4T27I6TSIO52I2J5KYLDHZO X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 28/29] patman: Clarify the 'series not in database' error path List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/3DBMITNCB4T27I6TSIO52I2J5KYLDHZO/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> Running 'patman series scan' on a branch that has not been added to the database fails with: patman: ValueError: No matching series for id None version 1 The same shape of confusing error -- a database lookup with idnum None being reported as raw SQL -- is reachable from 'series set-link', 'series get-link', 'series save-notes' and 'series show-notes' too, because each of those subcommands also passes ser.idnum on without checking that the series was actually found. Pull the existing 'if not ser.idnum: raise ValueError(...)' pattern out of the ten places it appears in cseries.py into a helper, _ensure_in_db(), in cser_helper.py. The helper raises a single canonical message that names the series and points at 'patman series add' as the fix: Series 'foo' not found in database; use 'patman series add' first Use the helper in every existing site (so the message is uniform across subcommands) and add it to the four sites that previously let an unset idnum reach the database. Update the two tests that asserted on the old per-site error wording ('No such series') to expect the unified message. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/cser_helper.py | 20 ++++++++++++++++++++ tools/patman/cseries.py | 32 ++++++++++++++------------------ tools/patman/test_cseries.py | 11 ++++++++--- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/tools/patman/cser_helper.py b/tools/patman/cser_helper.py index 032d5218e9e..0ce59e8ecc0 100644 --- a/tools/patman/cser_helper.py +++ b/tools/patman/cser_helper.py @@ -731,6 +731,26 @@ class CseriesHelper: recs = self.get_ser_ver(series_id, version) return recs.idnum, recs.link + def _ensure_in_db(self, ser): + """Verify a series object came from the database. + + Many subcommands look up a series by name, get a Series object back + with idnum unset when no row exists, then pass that idnum on to the + database where it is reported only as 'series_id NULL'. Raise a + clear ValueError up front so the caller knows to register the + series first. + + Args: + ser (Series): Series object whose idnum must be set + + Raises: + ValueError: if ser.idnum is None + """ + if not ser.idnum: + raise ValueError( + f"Series '{ser.name}' not found in database; " + "use 'patman series add' first") + def get_ser_ver(self, series_id, version): """Get the patchwork details for a series version diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index 443fe324c37..fb3f5a6c49a 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -126,8 +126,7 @@ class Cseries(cser_helper.CseriesHelper): dry_run (bool): True to do a dry run """ ser = self._parse_series(series) - if not ser.idnum: - raise ValueError(f"Series '{ser.name}' not found in database") + self._ensure_in_db(ser) max_vers = self._series_max_version(ser.idnum) if max_vers < 2: @@ -170,8 +169,7 @@ class Cseries(cser_helper.CseriesHelper): dry_run (bool): True to do a dry run """ ser = self._parse_series(series_name) - if not ser.idnum: - raise ValueError(f"Series '{ser.name}' not found in database") + self._ensure_in_db(ser) max_vers = self._series_max_version(ser.idnum) @@ -230,6 +228,7 @@ class Cseries(cser_helper.CseriesHelper): link """ ser, version = self._parse_series_and_version(series_name, version) + self._ensure_in_db(ser) self._ensure_version(ser, version) self._set_link(ser.idnum, ser.name, version, link, update_commit) @@ -248,6 +247,7 @@ class Cseries(cser_helper.CseriesHelper): str: Patchwork link as a string, e.g. '12325' """ ser, version = self._parse_series_and_version(series, version) + self._ensure_in_db(ser) self._ensure_version(ser, version) return self.db.ser_ver_get_link(ser.idnum, version) @@ -825,8 +825,7 @@ class Cseries(cser_helper.CseriesHelper): """ ser = self._parse_series(name) name = ser.name - if not ser.idnum: - raise ValueError(f"No such series '{name}'") + self._ensure_in_db(ser) self.db.ser_ver_remove(ser.idnum, None) if not dry_run: @@ -851,8 +850,7 @@ class Cseries(cser_helper.CseriesHelper): dry_run (bool): True to do a dry run """ old_ser, _ = self._parse_series_and_version(series, None) - if not old_ser.idnum: - raise ValueError(f"Series '{old_ser.name}' not found in database") + self._ensure_in_db(old_ser) if old_ser.name != series: raise ValueError(f"Invalid series name '{series}': " 'did you use the branch name?') @@ -922,6 +920,7 @@ class Cseries(cser_helper.CseriesHelper): notes = tools.read_file(notes_file, binary=False).strip() ser, version = self._parse_series_and_version(series, None) + self._ensure_in_db(ser) svid = self.get_series_svid(ser.idnum, version) self.db.ser_ver_set_notes(svid, notes) self.commit() @@ -934,6 +933,7 @@ class Cseries(cser_helper.CseriesHelper): series (str): Series name, or None for current branch """ ser, _ = self._parse_series_and_version(series, None) + self._ensure_in_db(ser) all_notes = self.db.ser_ver_get_all_notes(ser.idnum) if not all_notes: tout.notice(f"No review notes for '{ser.name}'") @@ -954,8 +954,7 @@ class Cseries(cser_helper.CseriesHelper): the listed patch numbers (1-based). """ ser, _ = self._parse_series_and_version(series, None) - if not ser.idnum: - raise ValueError(f"Series '{ser.name}' not found in database") + self._ensure_in_db(ser) with terminal.pager(): self._show_info(ser, show_reviews) @@ -1058,8 +1057,7 @@ class Cseries(cser_helper.CseriesHelper): if not ups: raise ValueError('Please specify the upstream name') ser, _ = self._parse_series_and_version(series, None) - if not ser.idnum: - raise ValueError(f"Series '{ser.name}' not found in database") + self._ensure_in_db(ser) self.db.series_set_upstream(ser.idnum, ups) @@ -1093,6 +1091,7 @@ class Cseries(cser_helper.CseriesHelper): tout.info(f'{oper} {seq:3} {out}') name, ser, version, msg = self.prep_series(branch_name, end) + self._ensure_in_db(ser) svid = self.get_ser_ver(ser.idnum, version).idnum pcdict = self.get_pcommit_dict(svid) @@ -1191,8 +1190,7 @@ class Cseries(cser_helper.CseriesHelper): succeed """ ser, version = self._parse_series_and_version(name, None) - if not ser.idnum: - raise ValueError(f"Series '{ser.name}' not found in database") + self._ensure_in_db(ser) ups = self.get_series_upstream(name) if ups: @@ -1226,8 +1224,7 @@ class Cseries(cser_helper.CseriesHelper): series (str): Name of series to use, or None to use current branch """ ser = self._parse_series(series, include_archived=True) - if not ser.idnum: - raise ValueError(f"Series '{ser.name}' not found in database") + self._ensure_in_db(ser) svlist = self.db.ser_ver_get_for_series(ser.idnum) @@ -1275,8 +1272,7 @@ class Cseries(cser_helper.CseriesHelper): series (str): Name of series to use, or None to use current branch """ ser = self._parse_series(series, include_archived=True) - if not ser.idnum: - raise ValueError(f"Series '{ser.name}' not found in database") + self._ensure_in_db(ser) self.db.series_set_archived(ser.idnum, False) svlist = self.db.ser_ver_get_for_series(ser.idnum) diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index c267b048fc5..7d6f99b3536 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -2384,7 +2384,10 @@ Tested-by: Mary Smith <msmith@wibble.com> # yak with self.stage('remove non-existent series'): with self.assertRaises(ValueError) as exc: cser.remove('first') - self.assertEqual("No such series 'first'", str(exc.exception)) + self.assertEqual( + "Series 'first' not found in database; use " + "'patman series add' first", + str(exc.exception)) with self.stage('add'): with terminal.capture() as (out, _): @@ -2410,8 +2413,10 @@ Tested-by: Mary Smith <msmith@wibble.com> # yak with terminal.capture() as (out, _): self.run_args('series', '-s', 'first', 'rm', expect_ret=1, pwork=True) - self.assertEqual("patman: ValueError: No such series 'first'", - out.getvalue().strip()) + self.assertEqual( + "patman: ValueError: Series 'first' not found in database;" + " use 'patman series add' first", + out.getvalue().strip()) with self.stage('add'): with terminal.capture() as (out, _): From patchwork Fri May 1 11:00:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass <sjg@u-boot.org> X-Patchwork-Id: 2272 Return-Path: <concept-bounces+u-boot-concept=u-boot.org@u-boot.org> X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633344; bh=v8f+33RfcQqK3/S/7OVOLNiTtUj1umIohTO6Gi0M5Xc=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=Vod3sKcUaiU4SAT7DyUUwBZEweik6uQMpSec8wA2Eh4wtacIKdPRmA1aPOrmmxzFJ RgVD6gRBNCErDnGcy1/f7gp1QJZp22UpNiLBn+9SkPSHgMs+YkgS1qX4no8k1owEDd 7F6yEl5zLvDYPN3Do1a3ljD166aok0rf6hgfuWQ8= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5C7A06A7AF for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:24 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id wFpzPmqcWFXn for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:24 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633344; bh=v8f+33RfcQqK3/S/7OVOLNiTtUj1umIohTO6Gi0M5Xc=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=Vod3sKcUaiU4SAT7DyUUwBZEweik6uQMpSec8wA2Eh4wtacIKdPRmA1aPOrmmxzFJ RgVD6gRBNCErDnGcy1/f7gp1QJZp22UpNiLBn+9SkPSHgMs+YkgS1qX4no8k1owEDd 7F6yEl5zLvDYPN3Do1a3ljD166aok0rf6hgfuWQ8= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4CCC16A82E for <u-boot-concept@u-boot.org>; Fri, 1 May 2026 05:02:24 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633342; bh=Z6oFIyCaJSvr7l6+C0igVFvikyiY/bnk7ZeozO+wQXE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qv/N0khG4kWQvEbXKGFdkMZUy5jWmroPddv1jZyUaLDgFygoP8n6P5DTIt09TUuqH e1lNyTq9aYtrRY4yw0XKNkynxDfiWvYSO2DUvv7bngA0g/sF9tWiO3dEBpY5cPOiDy NMhD7lP3vPmVh8xr9GP5plpHPW+cxAcv8rRe+wy8= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D962E6A7AF; Fri, 1 May 2026 05:02:22 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id hCno_usOx3nx; Fri, 1 May 2026 05:02:22 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633338; bh=+XBs/CoCFyg+uFw2i1dHwBnG4iwUIs+6yQxCIFarOis=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TjqvT9IQkRKIT7+D/0FJjBVq2TbvOD7GIOZ7oH5LETNU92msVzUcwhGjqVAVTuCJu 46sp+5KKtjgH5/SkBJyBPDMIlNgAHuyWkyvA6aSkcHt22hU0Q5IGEotE8z2aCM83eN AnSg/QAB1UifMQ9GlcHj1YSK5Gcgq11rxrvvTJ6k= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 482806A834; Fri, 1 May 2026 05:02:18 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Fri, 1 May 2026 05:00:21 -0600 Message-ID: <20260501110040.1874719-30-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: D2RWEJDNWT3DTOXZPQM45XSLOSG74R6I X-Message-ID-Hash: D2RWEJDNWT3DTOXZPQM45XSLOSG74R6I X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass <sjg@chromium.org> X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 29/29] patman: Document review behaviours added in this series List-Id: Discussion and patches related to U-Boot Concept <concept.u-boot.org> Archived-At: <https://lists.u-boot.org/archives/list/concept@u-boot.org/message/D2RWEJDNWT3DTOXZPQM45XSLOSG74R6I/> List-Archive: <https://lists.u-boot.org/archives/list/concept@u-boot.org/> List-Help: <mailto:concept-request@u-boot.org?subject=help> List-Owner: <mailto:concept-owner@u-boot.org> List-Post: <mailto:concept@u-boot.org> List-Subscribe: <mailto:concept-join@u-boot.org> List-Unsubscribe: <mailto:concept-leave@u-boot.org> From: Simon Glass <sjg@chromium.org> Several review-related improvements have landed without corresponding documentation: - 'series info' is now colour-coded by patchwork state, and its '-r' flag accepts an optional list of patch numbers to limit the review text it prints. - 'series gather' (and 'gather-all') print a notice when every patch in the gathered version has reached state 'accepted', confirming upstream landing without a separate query. - The review apply step stashes the working tree -- including untracked files -- before checking out the review branch and restores it on exit, so build artefacts from the current branch don't leak in. - A partial or interrupted apply aborts with a clear message and rolls back the database row, rather than letting the review run against an incomplete series. Add short paragraphs covering each of these in the right place (``series info`` description, ``How the review works`` apply step, and the ``series gather`` walk-through). Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/patman.rst | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index aba6d8905fb..39fc04deec8 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -759,7 +759,10 @@ Here is a short overview of the available subcommands: info Show detailed information about a series, including each version's link, description, patches and any stored reviews. - Use ``-r`` to include review text. + The output is colour-coded by patchwork state (e.g. ``new``, + ``accepted``). Use ``-r`` to include review text; pass a list + of patch numbers (``-r 1 3``) to limit the review text to those + patches. ls Lists the series in the database. Use ``-r`` to show only @@ -895,6 +898,10 @@ To gather tags (Reviewed-by ...) for your series from patchwork:: patman series gather +If every patch in the gathered version has reached state ``accepted``, +patman prints a notice that the series has been applied upstream. The +same check runs for ``patman series gather-all``. + Now you can check your progress:: patman series progress @@ -1339,6 +1346,21 @@ excluded from refinement to preserve their quoted commit messages. A mechanical cleanup step also runs to remove backticks and fix function quoting style (e.g. ``malloc()`` not ```malloc```). +Apply step +~~~~~~~~~~ + +Before checking out the review branch, patman stashes any uncommitted +changes -- including untracked files -- so build artefacts on the +current branch don't leak into the review. The original branch and +stash are restored at the end of the run, including on failure. + +If the apply agent finishes but the resulting branch holds fewer +commits than the series cover letter advertises, patman aborts with a +message of the form ``Only N of M patches applied to <branch>; +aborting. Fix the conflicts manually and retry.`` The database row +for the new version is rolled back so a retry starts from a clean +state. + Patchwork subcommands ---------------------