From patchwork Sat Apr 4 21:28:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2116 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=1775338242; bh=FRbwitre+Dj3ChHjXas/Tj748Myjc9NUjJtb/byhaJQ=; 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=jjBf31N6aY0BON8idwr+wBQqBOsUGjgCScVFiQZRacE/iGM8MLe4G/vTkBAIO5p/L QAN96YaOBK51wb6LLygFqhRk1zpFRyt7tl+y/nnnOz4KDdVXqrUp0pgpN6xCArwslJ AWJSZGTFAAzcQBR3umZ2rhFAsvMcdAADkkW0SFFB7j2fS0JiZmdNLRMhHaV7Duo354 rPPKrYXX/qy3h3omG5rV2hA8lP8SDNWaE/2MuQerd7CPTKAcGrRKiWRFzaFDOSFRqt y4tJhhyVeq6YAZYPyMD60BoO4+aEzxsxZkEU3PBE0QFrFMrrf5/Tcv7xkKQr15iJTU rU6uiDTxvUouw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 378656A376 for ; Sat, 4 Apr 2026 15:30:42 -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 y0-rsLvtG5NZ for ; Sat, 4 Apr 2026 15:30:42 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338242; bh=FRbwitre+Dj3ChHjXas/Tj748Myjc9NUjJtb/byhaJQ=; 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=jjBf31N6aY0BON8idwr+wBQqBOsUGjgCScVFiQZRacE/iGM8MLe4G/vTkBAIO5p/L QAN96YaOBK51wb6LLygFqhRk1zpFRyt7tl+y/nnnOz4KDdVXqrUp0pgpN6xCArwslJ AWJSZGTFAAzcQBR3umZ2rhFAsvMcdAADkkW0SFFB7j2fS0JiZmdNLRMhHaV7Duo354 rPPKrYXX/qy3h3omG5rV2hA8lP8SDNWaE/2MuQerd7CPTKAcGrRKiWRFzaFDOSFRqt y4tJhhyVeq6YAZYPyMD60BoO4+aEzxsxZkEU3PBE0QFrFMrrf5/Tcv7xkKQr15iJTU rU6uiDTxvUouw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 26EAC6A361 for ; Sat, 4 Apr 2026 15:30:42 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338241; bh=v1e8V+MEGLIJSDBxa/EGrUDfMCc6V3ITE54qUrOBW6E=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vgzN2gaASaJQpU29h8BpjPKV5ZZ5qqVzyZeGtwNFaghyk4Bu0jea/zUly7xWZyYwe tuAgkWIe8c+v28vUnctg/v3PZfg/Xg4keLtFD4kEh9cI1xCkUu5g6OMf+uYjj97SC5 eENuyy6hlh6kf3d4WZXA0Pj6UB8qvmXtsotH/m3tQZsNgsCFvetN4Ahy11ryGlH84r GoBIGt3KVX6HEOX4wHFxVPC/r5ACbLr97RBhQgCPacNsCVDECqKy99edN2b6SAdTUG nZT9m8Ss3KugMAbxKg3D2t91nXgNJ9dqH17FxsZmXqztHcwKizdlztIHam7JQxtGVN 6MlJl8edAmDbQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 684926869D; Sat, 4 Apr 2026 15:30:41 -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 MCea1kZxNzXf; Sat, 4 Apr 2026 15:30:41 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338237; bh=vnog4fa5TdWTp3XLLoWz6bvtkNjWgyEbmMOqwP2SN1o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=iMD2RT5VV3fEv2gyo24PLi7WTwFkQffAMx/rfERWdzm5a0fJVHuZa+5Sn93pbSDcT jXQb6eGisrIUE7zYuAT8mHNtti0JWCe8KiZbtLj55edzER5c9bI37QDrwCCFpE5lSr EjIhcrzWbjg+VuN91TaAj8Y+IlMzufTSU/8/JJl7xjab47Vj6xBbhpHC24Kf05rNrG 6RBWIf02PC7K3G+Rw9Pkx2DUvzYnBBwfkoVUVPar+WCA/sP+F5PXZa0rHSUmzw2oFW wb9gBinb1I0KzfKvXDUYhzmJsL6yblCkQZiccZ287mWT9ymyBMN/vDDtz83flHABFF RvgTcXU+vUOqA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 0E93F6A385; Sat, 4 Apr 2026 15:30:37 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:37 -0600 Message-ID: <20260404213020.372253-2-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: AZITUVJZ2OOKELIRVCDUGDC57V2RL7VE X-Message-ID-Hash: AZITUVJZ2OOKELIRVCDUGDC57V2RL7VE 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/37] patman: Send unknown-setting warnings to stderr 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 warning for unknown settings in the user's config file is printed to stdout, which contaminates the help output and causes test_full_help to fail when the config has settings for future features. Send the warning to stderr instead, and allow the test to tolerate WARNING lines on stderr so that environment-specific warnings do not cause test failures. Signed-off-by: Simon Glass --- tools/patman/func_test.py | 4 +++- tools/patman/settings.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/patman/func_test.py b/tools/patman/func_test.py index b1f37577643..6233957becd 100644 --- a/tools/patman/func_test.py +++ b/tools/patman/func_test.py @@ -635,7 +635,9 @@ diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n' gothelp = result.stdout.replace(extra, '') self.assertEqual(len(gothelp), os.path.getsize(help_file)) - self.assertEqual(0, len(result.stderr)) + unexpected = [l for l in result.stderr.splitlines() + if not l.startswith('WARNING:')] + self.assertEqual(0, len(unexpected)) self.assertEqual(0, result.return_code) def test_help(self): diff --git a/tools/patman/settings.py b/tools/patman/settings.py index 17229e0d823..17996b5dd30 100644 --- a/tools/patman/settings.py +++ b/tools/patman/settings.py @@ -307,7 +307,7 @@ def _UpdateDefaults(main_parser, config, argv): val = config.get('settings', name) defaults[name] = val else: - print("WARNING: Unknown setting %s" % name) + print("WARNING: Unknown setting %s" % name, file=sys.stderr) if 'cmd' in defaults: del defaults['cmd'] if 'subcmd' in defaults: From patchwork Sat Apr 4 21:28:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2117 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=1775338249; bh=uvVQSLYAJUMZSOgDvHwfIW2gIm8a8/ldWd4Mtw7XrNk=; 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=kxDvVrETmb81tc1urzSmvxkuJ5sTZU7d3BKFdVzKbj4C2ulmhFOcQtNHdaj6im6G6 QD79O9QLZRaEe0fLFDfifKP2yJXrnD6WsXFg/O0EAbe7TvmoGu4+Q4sArBfUESSxbR rG7/gjs7z4X4sIXhZCh8UkoM1hCoEbzBSlmbaQChhH9t6RlWcKVtsWqIqgGRIcjJY4 0DeaReZJec7nJUmOY06a3+QpVuuProGg4ppxPSxOHeLoBFpykgJLUVI7LrUw5TUzZ3 XKfn8wk91qfhVm4r2Rhjx7XZ51QS/ndndyfhFWpPFM8Jh9DRQ/fZGJ/q9moZLO50FA fn14k5JM7HP0w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 123C86A376 for ; Sat, 4 Apr 2026 15:30:49 -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 YIw-258xupAj for ; Sat, 4 Apr 2026 15:30:49 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338249; bh=uvVQSLYAJUMZSOgDvHwfIW2gIm8a8/ldWd4Mtw7XrNk=; 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=kxDvVrETmb81tc1urzSmvxkuJ5sTZU7d3BKFdVzKbj4C2ulmhFOcQtNHdaj6im6G6 QD79O9QLZRaEe0fLFDfifKP2yJXrnD6WsXFg/O0EAbe7TvmoGu4+Q4sArBfUESSxbR rG7/gjs7z4X4sIXhZCh8UkoM1hCoEbzBSlmbaQChhH9t6RlWcKVtsWqIqgGRIcjJY4 0DeaReZJec7nJUmOY06a3+QpVuuProGg4ppxPSxOHeLoBFpykgJLUVI7LrUw5TUzZ3 XKfn8wk91qfhVm4r2Rhjx7XZ51QS/ndndyfhFWpPFM8Jh9DRQ/fZGJ/q9moZLO50FA fn14k5JM7HP0w== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0044A6A369 for ; Sat, 4 Apr 2026 15:30:48 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338246; bh=oeDW1ZZKk1SRzFF7ijdS5yxqnxz9WTC5ZmTH+nKlSEM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KDKlIpQ8A21YizylPE4J5XN12CQxX7WKuQ2VFY/VBsrlndnBs8S+fb2QgNYb6GVmb tuADMlz51kE35r5VflYGYFcDhuF/Vy/UY6C1X07glrvzfY050omluQQdoA27Xh6KiJ SCoBzb9izGdN+ozNC2D2eqg/c6eIozVSyuNYMJvlBM+aVCs4+QB05zdHTpOT08tQTS QAsOwmvUc70Hg6x3Npg5fCRMHWnuMiwwSYVRH8EAOju0OuORdvdWi5pPEqU6sSXgpW rD4jNqth7V5Oy1TDn5bjg2GmrjV0/WTksZxp7ZI3TyuXVTuz3FFfxYtYZph//Azbxx qv4Dn1JS4Yx+g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DE9ED6869D; Sat, 4 Apr 2026 15:30:46 -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 yjekGwCNth5Z; Sat, 4 Apr 2026 15:30:46 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338242; bh=K7LJEcqslehhP0yg8Wwx0ZkI0aPyTzAnCmnnlhXWbEM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=al6NN7Utsb587BrOZzDgMM75q9M7AovWXTSfAXdoza4A0HoBK2r7GAPwH8XemsVIB 7jfovLxK2rZPYbila/qp4alALPGEUjX7g/D2eQSk/2YsCARU/xF6UMsl706IBqKha1 PTOKnnbUw/YeBXCyrRpjdEW06nCPKD9rQYHpXkFlc7a7MzyPPZ62Y9jtGfeYAnQ/qz J3Nh6FAlWJOZk1gWhw90uHkmNlx8Drq8vh8If244fBnC3aG8+ZaRlgPLT0Ov31OrHC w8K2szp/h2ydNPA3dPDifek/5oqMmOpuN9W/rpUGeUtK1RChKqgdnyg0rQJiaZqM++ +s9v9iTJRUUgA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 95AA66A361; Sat, 4 Apr 2026 15:30:42 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:38 -0600 Message-ID: <20260404213020.372253-3-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: NGS4QXQYSAGACTXSB4SYMMBVVFCZY3VY X-Message-ID-Hash: NGS4QXQYSAGACTXSB4SYMMBVVFCZY3VY 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/37] patman: Capture todo_clear() output in test_workflow_list 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 call to todo_clear() is not wrapped in terminal.capture(), so the "Todo cleared for series 'first'" message leaks to the console during test runs. Signed-off-by: Simon Glass --- tools/patman/test_cseries.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 7a5eede9f82..0b6e6ec9e32 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -4354,7 +4354,8 @@ Date: .* self.assertIn('todo', lines[3]) # Archive the todo - wf.todo_clear(cser, 'first') + with terminal.capture(): + wf.todo_clear(cser, 'first') # Without --all, only SENT is active; no 'A' column with terminal.capture() as (out, _): From patchwork Sat Apr 4 21:28:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2118 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=1775338253; bh=ElgHsycNrsWQAbSpYxcHtpOU55CTdDAgAGyHdH2xMmU=; 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=bS6XXbP/VOyl2RUEKbTc3J26WRCXX8iVkQkCj0iabh/0yrmdrmIeK5fTsS05+zcNg Kpn5NzPu6lu7roRBKR79C69HYDFKxXlZNOkF2GriBnPFJeTG8LzUuoOvncjxL6Nt+M TwtzBrE0eh3PwnvhHIF5JZyUvHx6bzsCUl9Afp5+lUQ50cAUeyKfaLO6TxgCIxUN67 vVKPYVRZiN38qro8+OnnaDTrVtspJGtyb5XdDPWraKX958gHLGfSfYQ0+eVFxCvq49 axKyeNWddNnZZD0tgDQpJ0IRixcTwoKAiFoIqvteHNaZ+dX5s4KxCK8NoRbzOkzQZU XAcd0z5PWMyZQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9D44C6A381 for ; Sat, 4 Apr 2026 15:30:53 -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 1Wv-Y3vQ8BQl for ; Sat, 4 Apr 2026 15:30:53 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338253; bh=ElgHsycNrsWQAbSpYxcHtpOU55CTdDAgAGyHdH2xMmU=; 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=bS6XXbP/VOyl2RUEKbTc3J26WRCXX8iVkQkCj0iabh/0yrmdrmIeK5fTsS05+zcNg Kpn5NzPu6lu7roRBKR79C69HYDFKxXlZNOkF2GriBnPFJeTG8LzUuoOvncjxL6Nt+M TwtzBrE0eh3PwnvhHIF5JZyUvHx6bzsCUl9Afp5+lUQ50cAUeyKfaLO6TxgCIxUN67 vVKPYVRZiN38qro8+OnnaDTrVtspJGtyb5XdDPWraKX958gHLGfSfYQ0+eVFxCvq49 axKyeNWddNnZZD0tgDQpJ0IRixcTwoKAiFoIqvteHNaZ+dX5s4KxCK8NoRbzOkzQZU XAcd0z5PWMyZQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 8CA6E6A37F for ; Sat, 4 Apr 2026 15:30:53 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338252; bh=xz5DmA/41utorEB0sBEkr18etF/kgxnNjJ1LguD+dWs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Iwo/smNbb7IxDajZCTSgZJpVf16asrePSjK1UU/TrezEjEA1gE7c+nMoQDza0J9Hu j7Lbc+fU+3/hUqv0OAv6TnY2In4AVjfKKKhIWVpcRALD5yeFl7j/MLYSZWivu9iM6H Oy2J+DSuNrw3cXcwvI4TA43p4fRd8DhSKF00rx9gsGfVROoxxEE7byrH6RSwIRtuad egANNJZWyK96aUUZHk4hv1ojUAL0WbjEhUeWIjtM0ZxsJnzL+9DJch0grRHzjUMHXY w7W5NVZU0NVf1s6WFi/VJyRUce3/c+MGwPvUSt4H9H3an+MYiQcHY5zE840xLo2IoR K7z4rR/3vEsBA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5A0896A361; Sat, 4 Apr 2026 15:30: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 10026) with ESMTP id HJYOmr_fmmAV; Sat, 4 Apr 2026 15:30:52 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338248; bh=ne/WSrG9mm9Pj7bigM+8zQYHBnr+z4l3ql+JaEkzjQ8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EWMUyUOA2bRUMI0fL0CfQtkUdHTErWE0evD8KrXsdKz0rLMaQMj0ml766V9jpcg3e 1qJiXp7miz/FxoCtmpd7z4TuecAVCtNnFXWXaZd8Bc2fA/FH4oE3SdVcKcq1rKjpQM yAmHRbpexx0Hz4T/bYrQEHRCFT3dCs56FFbh7TvUVt8kXGJ1Kal/x5XkUR82hqKUL1 bp+e7/LWzO8H394WLV8G3EBFQIFaH32OWuTCzlyS/WIIWdTDHx4SsCP5PwHmP1Yu/9 miNV22iVoGgTBvtRLHlnR7dyn16AUXgds1GrjHuBy05xVJ5GopKlB/WMQJ+W2JtAfU RubOgJSPoFFAw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id F2BAC6869D; Sat, 4 Apr 2026 15:30:47 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:39 -0600 Message-ID: <20260404213020.372253-4-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: AGLCUCWYX4HYSXIOR2S4JYS74S473E4G X-Message-ID-Hash: AGLCUCWYX4HYSXIOR2S4JYS74S473E4G 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/37] patman: Fix autolink retry hint argument order 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 error message suggests 'patman series autolink -s -V ' but argparse requires -s and -V before the subcommand. Fix to: 'patman series -s -V autolink' Signed-off-by: Simon Glass --- tools/patman/cseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index 0643d44cc01..e75e55c2764 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -304,7 +304,7 @@ class Cseries(cser_helper.CseriesHelper): raise ValueError( f"Cannot find series '{desc}'{delay}; " 'to try again later:\n' - f" patman series autolink -s {name} -V {version}") + f' patman series -s {name} -V {version} autolink') if options != last_options: tout.clear_progress() From patchwork Sat Apr 4 21:28:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2119 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=1775338259; bh=iWSVr7Hxd2dG/Jzbc3/H9qSB9Pfp2AD2PI4iFk3DAB0=; 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=Ju2vt6wMyLMby4TKpN9OO9tfWJOCKNzONMNvYtr9bMuqpb7XbonX7SKUlzOHMCVMj bGhTmj9xF1+n/PRq1S2zIQOux4R4vyr4GFQge5jSQC61RlN7D+bPD6T3utvq1mtE4m +P4HPNeAO2q3YNxgicEKrT4DZTCVMQf+HqzpHClig1UsH37aPArrV6P4yWF3fcp7W4 de6ZfS6QBylH7iA73FjPbKYlHPm8JULsiD7UEkbu85j8X2igdoenthXhTecM1odc6z YH8ljXXtnAVhYBBX1CR0+wnbStbqZ7YyjGeEXVuX0EacfDqqtZDWvDqTMQVs20cNTA Dlnnofu52FDEw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 26F346A37D for ; Sat, 4 Apr 2026 15:30: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 10024) with ESMTP id Wr-kRj6nQqP2 for ; Sat, 4 Apr 2026 15:30:59 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338259; bh=iWSVr7Hxd2dG/Jzbc3/H9qSB9Pfp2AD2PI4iFk3DAB0=; 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=Ju2vt6wMyLMby4TKpN9OO9tfWJOCKNzONMNvYtr9bMuqpb7XbonX7SKUlzOHMCVMj bGhTmj9xF1+n/PRq1S2zIQOux4R4vyr4GFQge5jSQC61RlN7D+bPD6T3utvq1mtE4m +P4HPNeAO2q3YNxgicEKrT4DZTCVMQf+HqzpHClig1UsH37aPArrV6P4yWF3fcp7W4 de6ZfS6QBylH7iA73FjPbKYlHPm8JULsiD7UEkbu85j8X2igdoenthXhTecM1odc6z YH8ljXXtnAVhYBBX1CR0+wnbStbqZ7YyjGeEXVuX0EacfDqqtZDWvDqTMQVs20cNTA Dlnnofu52FDEw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0B2F16A369 for ; Sat, 4 Apr 2026 15:30:59 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338257; bh=DmdLwcj1Eg38u+cl56LIIQ+kO74/4npoK6gnEBElGMY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tC+XOY9bcSdeHh9L57gkoAbEFhEpkP4PbybbJy9Z60hs6tUxrGGmLEbgFlFJji7vg m0+d6kCFUxDQtDXRrOkM8E17XBMelu84UxUdYEMQXrBbSCXUA1E4QWhCwDRQJK+vIu de7P7KgWvLzoXXtMaYB3aVn81BBko8aF/gnInUNTDi4toxDQR2LZ/l+Q1L0t3Y8Yeq PqfDxBvirrIsHF7yQd+w5JdJZLz7F/yQ/hiVVE9o9m5r8BdfwFGmzWIGnJOJwEanM2 4oXrnusiyL4/QEBOQ4/X35exLt7ITd9ZXnf8rwPa4pzpuo5gEU4dTQCg1/xZq2Ee5J 5TJwjv+dMav4w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B3CC26A255; Sat, 4 Apr 2026 15:30:57 -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 KJyQTp62RQJD; Sat, 4 Apr 2026 15:30:57 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338253; bh=orwgCcbteQhoYHyOvIOj9n+GNS46LSHWGiOdeqhkrAg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OK7z1+toUJRkUihHf22HJUlOBW79xGHpvkhxz5C9vLeVL7X4Jn4cKnmqUbv0DZQe6 f5ceyg2RQmdJ3Kdz3tFcCDgpnyw322R+4g7XC+OfWwqedjoXVzB2GigudiIe9AYHDX +9ixmj7AB5Rj7zPOd1QKaS+BpKQ42Y/VEbe9aU82OblQiB8njbN7xWmDNwH6fhUT5Z NB2N/nIlwlyqIs51nUeyDz8C6r2uikVX/48tY1LHeXAHHrvmIncd2m0z7r5YQJWeiL u1HRHr6QH3cvK8wwZbP7SnrHMUoY3VeOPGvNY3BK2LSbdyKnZMkDDFKHZTt5cH4GrP 0OlmMdl75cJvQ== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 735456869D; Sat, 4 Apr 2026 15:30:53 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:40 -0600 Message-ID: <20260404213020.372253-5-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 632SWT2RS2ZE2Z2NOXEEYGBUSZYA4NLM X-Message-ID-Hash: 632SWT2RS2ZE2Z2NOXEEYGBUSZYA4NLM 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/37] u_boot_pylib: Add test runner entry point 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 u_boot_pylib module has no way to run its tests from the command line. Other tools like patman and buildman have their own entry points that accept a 'test' subcommand. Add a proper __main__.py with argument parsing and a symlink so tests can be run with './tools/u_boot_pylib/u_boot_pylib test'. Signed-off-by: Simon Glass --- tools/u_boot_pylib/__main__.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/tools/u_boot_pylib/__main__.py b/tools/u_boot_pylib/__main__.py index d86b9d7dce0..5f802bd8d86 100755 --- a/tools/u_boot_pylib/__main__.py +++ b/tools/u_boot_pylib/__main__.py @@ -4,19 +4,37 @@ # Copyright 2023 Google LLC # +"""u_boot_pylib test runner""" + import os import sys -if __name__ == "__main__": - # Allow 'from u_boot_pylib import xxx to work' - our_path = os.path.dirname(os.path.realpath(__file__)) - sys.path.append(os.path.join(our_path, '..')) +# Allow 'from u_boot_pylib import xxx' to work +our_path = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(our_path, '..')) + +import argparse + +from u_boot_pylib import test_util - # Run tests - from u_boot_pylib import test_util +def run_tests(): + parser = argparse.ArgumentParser(description='u_boot_pylib test runner') + parser.add_argument('cmd', choices=['test'], help='Command to run') + parser.add_argument('testname', nargs='?', default=None, + help='Specific test to run') + parser.add_argument('-v', '--verbose', action='store_true', + help='Verbose output') + args = parser.parse_args() + + to_run = args.testname if args.testname not in [None, 'test'] else None result = test_util.run_test_suites( - 'u_boot_pylib', False, False, False, False, None, None, None, - ['terminal']) + 'u_boot_pylib', False, args.verbose, False, + False, None, to_run, None, + ['u_boot_pylib.terminal']) sys.exit(0 if result.wasSuccessful() else 1) + + +if __name__ == "__main__": + run_tests() From patchwork Sat Apr 4 21:28:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2120 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=1775338265; bh=ig/zzcY+gUwLBy1AiV7DW6ebILXixAS4f7nN+qFuEU0=; 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=KInSVekKD4GRquyePWtixJS+i4ZUHkEC4Em0jhD3nOrbQdLNtMyrOFRlmTPBRp7hI dQK/LIxki2kOWhO0LiQwrjXlPv1PtwwoD5+sEetNur+OTgfhVVi41EAxfiQFY7Wfut ouvi0QnVBFMUZlLiLqXBcM2Uh2MyubXG3bvZiOfae9qpIBrRoxiojxPgE+z3ypz0P6 5qLIcEUbFilEUM7syXQ48kuOtmNXrvJuejnnVLK1SjQqcaln8NND4PpdNDZ+SLn85P nsu/Fz0tfEVoyDnse8nMO8uhRoCbdpJCoKWdvSBvZXALCY3pPBv/8YE6cPRQrhEji7 I/mrvkRe0PptQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0012E6A37F for ; Sat, 4 Apr 2026 15:31:05 -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 0_u11Xl9mZ00 for ; Sat, 4 Apr 2026 15:31:04 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338264; bh=ig/zzcY+gUwLBy1AiV7DW6ebILXixAS4f7nN+qFuEU0=; 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=sCwLLZZti7ZPwqg6YVv/hAdweHg1yf1YhzVtE/B6N6RQ+MKvSOAVgfKXKeMuvO3qg l134tKLb3ZwVRqjKVoXxcf721X4D5B2d5Ps+yx5+8e+bfW+bwu1rIYM7hbM917E2OV QatUV9kgDeI+T/o8h/BUGNcicElsnGrgrYTztlUVL5Nw4SkRsKHKKPifZZ4IahlU74 bpPN23kLvw+N/mhnUsFhVQtzPSBRcO6Ckdxb+Eko/gUTuBfhVP8YkIebiD2mhkt1R2 BMy3xH0D4NL9nZ1lzHxx0QLcqalKCcMf/2d4R1OFhYuf7B3JvGKZwRRPEUmXwfSelJ Suh1x9u9zbWxg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D951A6869D for ; Sat, 4 Apr 2026 15:31:04 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338263; bh=PBvxsev1kUWNqHNVlnnKPSXXfE096l11ocXKbgM7VkA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Pxa9mqae38kU6oPBN4DL/BaGksYZCmsGnaQ/n1/zKG5fJJlYmQ5/AICksgkXj32He v6Ge7cl+YyRCK2XXQ/2u/npAWh83glvqT2Ar7wgqVfnK4Hla0cxR+E8Tb2MzMTkZho xI2WGCR06nz7f7TuaNk5qpltVnksMU0O33Hx/aA9gTlBx0L5ZxhfeFXnViN5fS9mZR FFTaWfwxfodXCbnTsupqwplxbw1rKhQLlRcNHe+zogWkOTTpmku85WIpYIVRszN1GF fe42HVyEHYhPZoVvqepUab+2ZW37s5GRySpB+0TQu8jDor/jAQKlWOuiigMdeOIFWq K9rOT+YFqJ0vQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E40366A375; Sat, 4 Apr 2026 15:31: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 10026) with ESMTP id mK_UUJH-8l9J; Sat, 4 Apr 2026 15:31:03 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338259; bh=Gt9UOHQFSAnRhulopyg/O2OWM5iRS+Z2yLqblQ5jj3U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MB2tLQZrnFBhywsuGmK8C5c78lrGq5YGjK3EI0SCvW1rQFOlPiWGE6P2O+mD79XN3 Dm7m448D+QnpUMnww+JGB8KVZDfE4M3Pt6yyKjFNLLGeB+1vrrbHDZlXSqxvQDAIml mRsH5Ibz7CdK6szgv5v1oMJxlqmVPwEFWmge6DtxaJMuke1LcUtbqhfcqyTfZGAspz WfP59QUvd99ZzKDID70e/e2w+QlR5udqDrhd7KO+WPwbW6X3jsEylahlvig3Pi7NVM rxfE9FI/S7n55ToKhgLW3+TnsG2vXxr465szTKGyilwzHznfW5/s3dmT/SaA7FZLRU vvNVHoDFyTyfQ== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id D380D6869D; Sat, 4 Apr 2026 15:30:58 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:41 -0600 Message-ID: <20260404213020.372253-6-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: F7NEBNPKIBOWB7ULWBFPU6YPN4PM6YGC X-Message-ID-Hash: F7NEBNPKIBOWB7ULWBFPU6YPN4PM6YGC 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/37] u_boot_pylib: Fix cros_subprocess tests for Python 3 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 cros_subprocess test suite uses str for accumulating output data, but communicate_filter() sends bytes. This causes a TypeError on every test. Change MyOperation to use bytes and update all string comparisons in the tests to use byte literals. Add TestSubprocess to the u_boot_pylib test suite. Signed-off-by: Simon Glass --- tools/u_boot_pylib/__main__.py | 3 ++- tools/u_boot_pylib/cros_subprocess.py | 31 ++++++++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/tools/u_boot_pylib/__main__.py b/tools/u_boot_pylib/__main__.py index 5f802bd8d86..5d3dec2c709 100755 --- a/tools/u_boot_pylib/__main__.py +++ b/tools/u_boot_pylib/__main__.py @@ -15,6 +15,7 @@ sys.path.append(os.path.join(our_path, '..')) import argparse +from u_boot_pylib import cros_subprocess from u_boot_pylib import test_util @@ -31,7 +32,7 @@ def run_tests(): 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.terminal', cros_subprocess.TestSubprocess]) sys.exit(0 if result.wasSuccessful() else 1) diff --git a/tools/u_boot_pylib/cros_subprocess.py b/tools/u_boot_pylib/cros_subprocess.py index cd614f38a64..4ac435b631c 100644 --- a/tools/u_boot_pylib/cros_subprocess.py +++ b/tools/u_boot_pylib/cros_subprocess.py @@ -261,9 +261,9 @@ class TestSubprocess(unittest.TestCase): input_to_send: a text string to send when we first get input. We will add \r\n to the string. """ - self.stdout_data = '' - self.stderr_data = '' - self.combined_data = '' + self.stdout_data = b'' + self.stderr_data = b'' + self.combined_data = b'' self.stdin_pipe = None self._input_to_send = input_to_send if input_to_send: @@ -305,8 +305,8 @@ class TestSubprocess(unittest.TestCase): cmd = 'echo fred >/dev/stderr && false || echo bad' plist = Popen([cmd], shell=True).communicate_filter(oper.output) self._basic_check(plist, oper) - self.assertEqual(plist [0], 'bad\r\n') - self.assertEqual(plist [1], 'fred\r\n') + self.assertEqual(plist [0], b'bad\r\n') + self.assertEqual(plist [1], b'fred\r\n') def test_shell(self): """Check with and without shell works""" @@ -316,7 +316,7 @@ class TestSubprocess(unittest.TestCase): plist = Popen([cmd], shell=True).communicate_filter(oper.output) self._basic_check(plist, oper) self.assertEqual(len(plist [0]), 0) - self.assertEqual(plist [1], 'test\r\n') + self.assertEqual(plist [1], b'test\r\n') def test_list_args(self): """Check with and without shell works using list arguments""" @@ -324,7 +324,7 @@ class TestSubprocess(unittest.TestCase): cmd = ['echo', 'test', '>/dev/stderr'] plist = Popen(cmd, shell=False).communicate_filter(oper.output) self._basic_check(plist, oper) - self.assertEqual(plist [0], ' '.join(cmd[1:]) + '\r\n') + self.assertEqual(plist [0], (' '.join(cmd[1:]) + '\r\n').encode()) self.assertEqual(len(plist [1]), 0) oper = TestSubprocess.MyOperation() @@ -333,7 +333,7 @@ class TestSubprocess(unittest.TestCase): cmd = ['echo', 'test', '>/dev/stderr'] plist = Popen(cmd, shell=True).communicate_filter(oper.output) self._basic_check(plist, oper) - self.assertEqual(plist [0], '\r\n') + self.assertEqual(plist [0], b'\r\n') def test_cwd(self): """Check we can change directory""" @@ -342,7 +342,7 @@ class TestSubprocess(unittest.TestCase): plist = Popen('pwd', shell=shell, cwd='/tmp').communicate_filter( oper.output) self._basic_check(plist, oper) - self.assertEqual(plist [0], '/tmp\r\n') + self.assertEqual(plist [0], b'/tmp\r\n') def test_env(self): """Check we can change environment""" @@ -354,7 +354,7 @@ class TestSubprocess(unittest.TestCase): cmd = 'echo $FRED' plist = Popen(cmd, shell=True, env=env).communicate_filter(oper.output) self._basic_check(plist, oper) - self.assertEqual(plist [0], add and 'fred\r\n' or '\r\n') + self.assertEqual(plist [0], add and b'fred\r\n' or b'\r\n') def test_extra_args(self): """Check we can't add extra arguments""" @@ -374,7 +374,8 @@ class TestSubprocess(unittest.TestCase): shell=True).communicate_filter(oper.output) self._basic_check(plist, oper) self.assertEqual(len(plist [1]), 0) - self.assertEqual(plist [0], prompt + 'Hello Flash\r\r\n') + self.assertEqual(plist [0], + (prompt + 'Hello Flash\r\r\n').encode()) def test_isatty(self): """Check that ptys appear as terminals to the subprocess""" @@ -386,16 +387,16 @@ class TestSubprocess(unittest.TestCase): both_cmds += cmd % (fd, fd, fd, fd, fd) plist = Popen(both_cmds, shell=True).communicate_filter(oper.output) self._basic_check(plist, oper) - self.assertEqual(plist [0], 'terminal 1\r\n') - self.assertEqual(plist [1], 'terminal 2\r\n') + self.assertEqual(plist [0], b'terminal 1\r\n') + self.assertEqual(plist [1], b'terminal 2\r\n') # Now try with PIPE and make sure it is not a terminal oper = TestSubprocess.MyOperation() plist = Popen(both_cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate_filter(oper.output) self._basic_check(plist, oper) - self.assertEqual(plist [0], 'not 1\n') - self.assertEqual(plist [1], 'not 2\n') + self.assertEqual(plist [0], b'not 1\n') + self.assertEqual(plist [1], b'not 2\n') if __name__ == '__main__': unittest.main() From patchwork Sat Apr 4 21:28:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2121 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=1775338270; bh=wi0HBfOdHJZoe5KzQso2eKFq/9N3oPTfTol+yaSJBWg=; 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=w1CdVv3On4xcXP5rVb3PBDQq6f4GoIF+pm4s6gwu0fgJQ92XJiDqOV1CQnSAkYODr 5LEK9wzAuiU4yaC/SZ3KW32YoZFIlgnKj7M+Ae5mq0wrYqZjQIb2layajuIyrRzr0J lX1U7po3TlkEJ9v1mhbzQnW2Z3AuaJE4wLeOIHjdUs7CBpKx7ybnNhsn62wKgr4QEg 5ovQfPMrU64s1MBA9lKvzxDnjWFcNcL1TiVB7BZNoN1pmV+VPYKwgDcqnbv5DfQf+x ZMDj9f4KRXrGqnte25AGA9iMCpL2hFaaIqD+WaR/UgyGI1apEm9uEAOGmin29/k/oJ pzx2PlbsmaRhw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 835756A37F for ; Sat, 4 Apr 2026 15:31: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 fZdNponTpSYs for ; Sat, 4 Apr 2026 15:31:10 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338270; bh=wi0HBfOdHJZoe5KzQso2eKFq/9N3oPTfTol+yaSJBWg=; 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=w1CdVv3On4xcXP5rVb3PBDQq6f4GoIF+pm4s6gwu0fgJQ92XJiDqOV1CQnSAkYODr 5LEK9wzAuiU4yaC/SZ3KW32YoZFIlgnKj7M+Ae5mq0wrYqZjQIb2layajuIyrRzr0J lX1U7po3TlkEJ9v1mhbzQnW2Z3AuaJE4wLeOIHjdUs7CBpKx7ybnNhsn62wKgr4QEg 5ovQfPMrU64s1MBA9lKvzxDnjWFcNcL1TiVB7BZNoN1pmV+VPYKwgDcqnbv5DfQf+x ZMDj9f4KRXrGqnte25AGA9iMCpL2hFaaIqD+WaR/UgyGI1apEm9uEAOGmin29/k/oJ pzx2PlbsmaRhw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 72DED6A375 for ; Sat, 4 Apr 2026 15:31:10 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338268; bh=bAmvydX9tAsG5sWqcPx1pVuPxk2VrJHxabAnKp0M5pY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IfXnO4GRjAr3T5JUyTb14syAEb0In0Z78VlI03QA40/iFNryLDu0NouNCchxLaCJm i1oKCYIsAp2HiMO8KK2kVDMpR0Ui57C0HyCpKMsygTO7BfjSytxNhQpZd7nXqfx92x PFtllA893A01E4ZV5HhsH315gGpHD81LcRghmZ96qOUm2UDUR+RtzI5rEdc1KWxXWq FR+uw0rB6iQDj2wjOaDqJpAjO/T+7EvXEFFyggUvXDXhHXTgjg5UFHCpQMD34IuxBO 6piuRF4tgM8CRIxLH3YBpYo7mdVmplgZQ/zO+o38+X8b6J+FS/MHzcaf0Z9UdTUdBT vP7KOVn7INDgw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 530656869D; Sat, 4 Apr 2026 15:31: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 10026) with ESMTP id nreQJR7hEyVJ; Sat, 4 Apr 2026 15:31:08 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338264; bh=6i2rnP29XitK9UvMd4JasXlrny08Km6D5aHcpf4FmCg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jA35gcAAcYTBDJKiOfJchgoMMAJYzMrAxFGXYPnFr67R3LD9kHVZFak7NhiwRHO8h XQPmmxDnfPF2OvnzctNk4vjSu6b18YCQlEF6ttHk3X4VFfU1kgOO/S46cfeLkbNexb 5BxBfe20ZQxHZGaiQVXZJwNli1XhYEf14jarOn1NPuG1pbOKEBe6fVAc0OYEHawVcf LCTQ019+J1r6nxVKgcy7xkLtHhzTZHqXzK2l5Ls0bt2ggulEoDkHNy2nP5Gjg2MbEh jGzb/YZFuYP5StRkZLoNIVMPw/JKavdauFUYIjKeAqj+j4BDb0c8NjyexB1I4tuEdT wKROHl5CGhSCA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 35F1E6A369; Sat, 4 Apr 2026 15:31:04 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:42 -0600 Message-ID: <20260404213020.372253-7-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: CF4NOJLUKWV5TNPAJ42UWPSP3KRF3JYK X-Message-ID-Hash: CF4NOJLUKWV5TNPAJ42UWPSP3KRF3JYK 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/37] u_boot_pylib: Fix gitutil doctests and add to test suite 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 gitutil doctests have gone stale: build_email_list() now returns tag and email as separate list items instead of a single quoted string, email_patches() has a different parameter order, and the get_top_level() test checks the wrong directory path since the module moved from patman to u_boot_pylib. Update the doctests to match the current behaviour. Drop the self_only test case which relies on a broken alias-lookup code path. Add gitutil doctests to the u_boot_pylib test suite. Signed-off-by: Simon Glass --- tools/u_boot_pylib/__main__.py | 3 ++- tools/u_boot_pylib/gitutil.py | 33 ++++++++++++--------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/tools/u_boot_pylib/__main__.py b/tools/u_boot_pylib/__main__.py index 5d3dec2c709..6b9f4f3d950 100755 --- a/tools/u_boot_pylib/__main__.py +++ b/tools/u_boot_pylib/__main__.py @@ -32,7 +32,8 @@ def run_tests(): result = test_util.run_test_suites( 'u_boot_pylib', False, args.verbose, False, False, None, to_run, None, - ['u_boot_pylib.terminal', cros_subprocess.TestSubprocess]) + ['u_boot_pylib.terminal', 'u_boot_pylib.gitutil', + cros_subprocess.TestSubprocess]) sys.exit(0 if result.wasSuccessful() else 1) diff --git a/tools/u_boot_pylib/gitutil.py b/tools/u_boot_pylib/gitutil.py index 8e45727b47a..202afe745d3 100644 --- a/tools/u_boot_pylib/gitutil.py +++ b/tools/u_boot_pylib/gitutil.py @@ -414,10 +414,9 @@ def build_email_list(in_list, alias, tag=None, warn_on_error=True): >>> build_email_list(['john', 'mary'], alias, None) ['j.bloggs@napier.co.nz', 'Mary Poppins '] >>> build_email_list(['john', 'mary'], alias, '--to') - ['--to "j.bloggs@napier.co.nz"', \ -'--to "Mary Poppins "'] + ['--to', 'j.bloggs@napier.co.nz', '--to', 'Mary Poppins '] >>> build_email_list(['john', 'mary'], alias, 'Cc') - ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins '] + ['Cc', 'j.bloggs@napier.co.nz', 'Cc', 'Mary Poppins '] """ raw = [] for item in in_list: @@ -501,24 +500,16 @@ def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname, >>> series = {} >>> series['to'] = ['fred'] >>> series['cc'] = ['mary'] - >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ - False, alias) - ('git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ -"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2', 0) - >>> email_patches(series, None, ['p1'], True, True, 'cc-fname', False, \ - alias) - ('git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ -"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" p1', 0) + >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', + ... alias) + ('git send-email --annotate --to f.bloggs@napier.co.nz --cc m.poppins@cloud.net --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2', 0) + >>> email_patches(series, None, ['p1'], True, True, 'cc-fname', + ... alias) + ('git send-email --annotate --to f.bloggs@napier.co.nz --cc m.poppins@cloud.net --cc-cmd "./patman send --cc-cmd cc-fname" p1', 0) >>> series['cc'] = ['all'] - >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ - True, alias) - ('git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \ -send --cc-cmd cc-fname" cover p1 p2', 0) - >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ - False, alias) - ('git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ -"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \ -"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2', 0) + >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', + ... alias) + ('git send-email --annotate --to f.bloggs@napier.co.nz --cc f.bloggs@napier.co.nz --cc j.bloggs@napier.co.nz --cc m.poppins@cloud.net --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2', 0) # Restore argv[0] since we clobbered it. >>> sys.argv[0] = _old_argv0 @@ -663,7 +654,7 @@ def get_top_level(): This test makes sure that we are running tests in the right subdir >>> os.path.realpath(os.path.dirname(__file__)) == \ - os.path.join(get_top_level(), 'tools', 'patman') + os.path.join(get_top_level(), 'tools', 'u_boot_pylib') True """ result = command.run_one( From patchwork Sat Apr 4 21:28:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2122 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=1775338275; bh=y6zwcjJ8bYYN4RaHmAp9wutpB/mP6hkOYgOH16AlZ0g=; 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=N6lkD2gLD3x3cXsLDXOJ4tZVGEo7hBrkG/toTnfiASpLUPsdgbBz2zcVrczlsnBoR Url/92w7+QiXi8qYSbjbfvgUrLJAx9NlyjPaP1mbRyQ/Uv0GvW46bjqzl2dUNek2Va eWKtB5JZT4aFyjdCUjYaZ1GGjwn7jNpfBPItT811t6WKFiFfXW7fJKrFb9mwHZstR5 tEpE50WGuiAkZXhC3FxRc7dhROX5x6DpexoMj29pmyVOyAhjel7APeBgme6uh6ng+O CKnY7llWxyCMDRAnlk9YP2Zp56t7WBm8l1dIUGqheEmB5Ax6gwsm2cUtno5fM4n3Ml fF5EABETkpcZA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 64D996A369 for ; Sat, 4 Apr 2026 15:31: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 10024) with ESMTP id 9_ylznV0mnEy for ; Sat, 4 Apr 2026 15:31:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338275; bh=y6zwcjJ8bYYN4RaHmAp9wutpB/mP6hkOYgOH16AlZ0g=; 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=N6lkD2gLD3x3cXsLDXOJ4tZVGEo7hBrkG/toTnfiASpLUPsdgbBz2zcVrczlsnBoR Url/92w7+QiXi8qYSbjbfvgUrLJAx9NlyjPaP1mbRyQ/Uv0GvW46bjqzl2dUNek2Va eWKtB5JZT4aFyjdCUjYaZ1GGjwn7jNpfBPItT811t6WKFiFfXW7fJKrFb9mwHZstR5 tEpE50WGuiAkZXhC3FxRc7dhROX5x6DpexoMj29pmyVOyAhjel7APeBgme6uh6ng+O CKnY7llWxyCMDRAnlk9YP2Zp56t7WBm8l1dIUGqheEmB5Ax6gwsm2cUtno5fM4n3Ml fF5EABETkpcZA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 513D36A375 for ; Sat, 4 Apr 2026 15:31:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338273; bh=719n+gU8FAWmSdzT6g/z5JLrZYW3mLOTR/NhOhSa2ho=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VUisCl6K4qoFa4KGhpiBLUqNctbBSUtVt/Mpgkitlgd8uCzLlQKEzShrUecPewhW9 oTbfm/pwIZLbbuCybuaAmMRrx+jadUMr9jKUkKWN36pEDBP6NJaNYv4PbXJNGfObN1 5hwXocTTBAsD1KDuxvQbGPmHenUsVQTF5FSEzjnX6JwZjXci8PTuGRRtUQgmA6rWd0 CPnQJVywcRTUWFJwOj9kicbNcyT5cZMMcUS8ST6Htp6+g3ZC2tybPW31LETvlOFmco ofcQVWpBSy5Yijo/E/6I9JSbJSlBmUbvKMrQzHda960bAh5gExwmvn1rrdVamJP0nN dDkTWCWbGoNJQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A3A476A369; Sat, 4 Apr 2026 15:31:13 -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 e-Q4QWR3C-Xk; Sat, 4 Apr 2026 15:31:13 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338269; bh=YiPLJRYPs1GrRW5EIa0zbjHH7Iy+BgAABnbjCbUvpwQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gwKFmZnn9a0pJCZRdxQCGj1MhWdgBhwWDAHGp6PzaUfMpfIyfUJnJwp2rc+P0XnHE IknrjtAl9JVuj0E4Og5H9aUfK3FSQabtqXao8jt77Zro3LqjrGX4iOI/GDqRpfDlUO sWK/thHqgs/wlmeBFWba9sYkQ+sXm0alPh2pWnxn3ug6QJIxfzriFlNmVK6zDC4khO eR9JDy0EhIKmdz8d8dFDlo1q0wy6j4rQ41tzUNclHFGApKQSPcR4Hwaco9Di8zFv9H kkMVIuK3ssxcRfMMiZ+hFaaYQQhAgjud3lE9blzHBv0raaptbMKGhoqkfgU5yhFq/k n97MHO3ZgFw8Q== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 6897E6869D; Sat, 4 Apr 2026 15:31:09 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:43 -0600 Message-ID: <20260404213020.372253-8-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: MNA2NNZ2GH3C3USLPTNHD55DVNM2E6OR X-Message-ID-Hash: MNA2NNZ2GH3C3USLPTNHD55DVNM2E6OR 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/37] CI: Run u_boot_pylib tests in GitLab and Azure pipelines 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 u_boot_pylib module now has a test runner entry point but it is not included in the CI pipelines. Add it alongside the existing patman test invocation in both GitLab CI and Azure Pipelines. Signed-off-by: Simon Glass --- .azure-pipelines.yml | 1 + .gitlab-ci.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 9edaa7606e9..09642e3295e 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -150,6 +150,7 @@ stages: ./tools/binman/binman --toolpath ${UBOOT_TRAVIS_BUILD_DIR}/tools test ./tools/buildman/buildman -t ./tools/dtoc/dtoc -t + ./tools/u_boot_pylib/u_boot_pylib test ./tools/patman/patman test make O=${UBOOT_TRAVIS_BUILD_DIR} testconfig EOF diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 73c46ea69ca..4b7996e0a39 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -324,6 +324,7 @@ Run binman, buildman, dtoc, hwids_to_dtsi, Kconfig, patman and pickman suites: ./tools/binman/binman ${TOOLPATH} test -T; ./tools/buildman/buildman -t; ./tools/dtoc/dtoc -t; + ./tools/u_boot_pylib/u_boot_pylib test; ./tools/patman/patman test; ./tools/pickman/pickman test; ./tools/pickman/pickman test -T; From patchwork Sat Apr 4 21:28:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2123 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=1775338277; bh=RnUpxCRMKlZfJ2GSq4V3mkTPPRoFeVBO735DCHFWGjg=; 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=Zxk/LI7HB/fiaMpY7jkxiGqCRXqApOE7ejs8FCZsuMfbDs1I8mJ63ddSxpqGHa8AV yKVR1Q7Mzb9clO82d5exp/+P6/bcYnOEFgT0chRMQRRzQxI8pGzvtSbZkB4zpn/PId uwOzy4sPcx9auuseM5COYXUnNSQ2TOPXpE3ii1HaB1zFM+XdVZR3HFbTMk1axx6CrW rbXkmkx2XvhREjc/1G+Z9+T7vdKIzz0kx8X9sPQLz3DBS8pnHet61hYc0Ialaxsd9O LcBkGMLSBoc0j7Ajv5ZAnAU92V1XAPPKL5XM2t4JuQDGRIvTVvoFkDKKn2nT7+1xcG Kp/ior255s7Gw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DFD356A387 for ; Sat, 4 Apr 2026 15:31: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 9SnYBq4bNxip for ; Sat, 4 Apr 2026 15:31:17 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338276; bh=RnUpxCRMKlZfJ2GSq4V3mkTPPRoFeVBO735DCHFWGjg=; 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=dGjtiA0YJ4KnYzBqqphUUDhyj+EUqpkcj/0VCKuWj0zm1Bi+t+tdhDk7OFGCYtJiF V8h2nam3SzNBKx4DZrJCEl+Oz3o2vZdtMghMe9g996ltNyljR3aUpxtRbC/IRqRSHl rsFup79GjNwsdz9DmL6mHK7ZpP/btRfXeTv9Cxbcri70rmLy2mqwp6EwEuNNiHAsS1 441rKYHryEbd8qsfNSnkVRIdJ8cIFjLxj1CSGybEiwyzCTvUVezA2mglbNeESYlFhn 7cCWym4RzjTWcdgSlBzrtnE2c6NV+TtnHzA0jtXG9FO22fLMAEHmpaSo/BKxC2HAuV 9Wy9wYC9l9pTA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E68736A375 for ; Sat, 4 Apr 2026 15:31:16 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338275; bh=ukQIypP0pPN+PDd7p77z5peg6IJuyd6idpLuOpPfY00=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UyZX+NQVyLlRZiCTsUW8AEG4Ts4pOd60OqTtePckRpANRn6a7RnF8in5laJJSfiTC NcM7rpsfxghpWXo5Lw1l+dfPScsBGTEp5KOnkEXoZY6M6aYhed6Wou9mlh2CjUhozA U43/5PEu0baaxSVCwJQmoFeC2Y8r6tT2bdr38ETBXzC8jcFta/vvL6OMi57KWuPlhV SeCDU2SIS3HQa0PTg8xaL4X7ovZQ2N/tb3LcM+l4QrAamhlDA02pUaYXQV9rvlI9yW 5IGuXww9sDZ8QTOz8IBdDWHuEQ3xg1QsTv5qdtGBhZx28cFO07hY3cjKLf3NllBJsd fStCxahdHGQ8g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6F7526A375; Sat, 4 Apr 2026 15:31: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 rfxKFjXN1QtT; Sat, 4 Apr 2026 15:31:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338275; bh=3tjFYfqWsqFvPjAovmAlKWcvlZigLK4moz7oDcsUls4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=K6uk8Wt0xcSSCv54FOp8cwaUFhc6fhtKjfNq7wPxf4ZPcY5HTtWb7IUsotdvNwspF 2QiWYYjclJxMHb9G9wRkSebbRJJpcyYJyFAzQN4hhqVerjrN66AdZHn5p/05A3y31Y phg/7mqquE3KvX2XIRT/+uLRqLIELRVh9ufXtwPTfmYP/gw0PBnS16cQnU+twZ4iGe SKpklHWlpBgK3HJtUU3Z9UklXyMewKmA/jV12X9iROrGoRdwGCJXdYXn5+3+XTwdXL sTgn8ZeDtdmAaGUhq21+NqcRm55Vtb74Ti0aeZDVNntRbykW2JQqr/3xeB4oC8OAqN nTNLLtJcVgCWg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id C18116869D; Sat, 4 Apr 2026 15:31:14 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:44 -0600 Message-ID: <20260404213020.372253-9-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: W47ZNASXTBBQEPVYHUHMLKIMIIDLZCSQ X-Message-ID-Hash: W47ZNASXTBBQEPVYHUHMLKIMIIDLZCSQ 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/37] patman: Strip trailing slash from patchwork URLs 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 Normalise the patchwork URL by stripping any trailing slash, so that API paths are constructed without a double slash. Guard against None URLs which can occur when patchwork is not configured. Signed-off-by: Simon Glass --- tools/patman/patchwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py index 627af08a723..5192b817968 100644 --- a/tools/patman/patchwork.py +++ b/tools/patman/patchwork.py @@ -174,7 +174,7 @@ class Patchwork: url (str): URL of patchwork server, e.g. 'https://patchwork.ozlabs.org' """ - self.url = url + self.url = url.rstrip('/') if url else url self.fake_request = None self.proj_id = None self.link_name = None From patchwork Sat Apr 4 21:28:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2124 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=1775338282; bh=86zhGhKdigLQKp+HUNpFQ646TluiP2eMcIe7VerXFmM=; 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=Tikj3aEiUdHqKCwbLaVmEVb/Y1fZ05WbPPvi22hDiSUaT4zGMXmPoqVJK8GLu3XIy UKoe5XVCb8BrmBJD2NICQVuifekwpCCNUyhSMO64zPx+r1eZAF3HEJqsKMCg08GZ4c IqnAcW7b65r02qpkSYTSAEMVDSl6IcwL9P2DCFLHQh2cHSYcUaI3vFeFJvQHBab7yL IUPNwskZneLWUhASi6ZHTYOLwy2hWiEU7E/MgbHw7f6e+MoX3tnm2m5ZtAu6otz3EN WTKcFVQAVM+KcMfiCnKGj7/HQn76v/FPdB2vSkRftwWN649KvFvUgWzs8Izv1s7vvF z9k9YM2lIVTiA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 756226A37F for ; Sat, 4 Apr 2026 15:31: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 10024) with ESMTP id eM27vwor_H5p for ; Sat, 4 Apr 2026 15:31:22 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338282; bh=86zhGhKdigLQKp+HUNpFQ646TluiP2eMcIe7VerXFmM=; 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=Tikj3aEiUdHqKCwbLaVmEVb/Y1fZ05WbPPvi22hDiSUaT4zGMXmPoqVJK8GLu3XIy UKoe5XVCb8BrmBJD2NICQVuifekwpCCNUyhSMO64zPx+r1eZAF3HEJqsKMCg08GZ4c IqnAcW7b65r02qpkSYTSAEMVDSl6IcwL9P2DCFLHQh2cHSYcUaI3vFeFJvQHBab7yL IUPNwskZneLWUhASi6ZHTYOLwy2hWiEU7E/MgbHw7f6e+MoX3tnm2m5ZtAu6otz3EN WTKcFVQAVM+KcMfiCnKGj7/HQn76v/FPdB2vSkRftwWN649KvFvUgWzs8Izv1s7vvF z9k9YM2lIVTiA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6507A6A375 for ; Sat, 4 Apr 2026 15:31:22 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338280; bh=trVncWXPRLPuZdVaUcDUjjegqD3hpyUMlP1Bt81faIY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AFQMHVzLkmx/AWtkjN0/AAZeBNbaIz4MW91NpSNNO55RVU4aBIaLC6z3FEeC6LPA9 IktIkgMiFu9n+leLMLNFmcrWVVC0ghP/AKJ0QVAsCjjrKKAeNP3aZH6Sve6wRRgOnP vYOvP2fM2nIDgHWlQ/7j/Mbwz2gLbOwGiAeqQ5buW5jccf5o9PRW0i65rGFMMSEX9E w4NtpOjz6Eap2Kz0Uy3pfAxfi74CIl/fZjFjF/mbGZgHNdQIoMAknIkZ83V66suJsJ of7vu/isHUxpfZvuJCM7s7R7bki+E59eP1QGjPgPVYgsIY41CVrZxHQGfpjcojArSE vFI23qNOevlcw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E06F96A369; Sat, 4 Apr 2026 15:31: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 10026) with ESMTP id mnfRYM9joqlO; Sat, 4 Apr 2026 15:31:20 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338276; bh=DPNoh9Dr6TIyvxg6MF2OVyXbPkYg1XlmOHAktcmXF9c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hkT09jQ7qA74plqrrcYmLfAFRXMwArQZYW80COLdPFRCYvjHLd5ZV0L4ycQGrkdC6 1wccVtjVhAkZODBXgCTvYnizaST7RLKUNaS0K8mzzKhspfjSKao8ZPCfwqXd34LPxj Q8hTYv9hHs0T/pQIETbET3P2iBOdTiH/tgTOxZa0Fol6mVw1b5U7uNj51o0ezQAKr4 quekMe7PPuiD7IfKbrEmy/ytVHeuGHpyHP5850I5+39kW+jSM6PSmYxck0mVgIpaD/ QEby3U2A1PKEHM0/ejUqpEgCAh1kmCMXPfCHaMAXBvJitGVO4zbatByKvZ/u+gmUPJ CfSHBKPjM939A== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 831846869D; Sat, 4 Apr 2026 15:31:16 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:45 -0600 Message-ID: <20260404213020.372253-10-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 4YWGWXRUU2U6IVAYH3ZG27RN6ETNUBAP X-Message-ID-Hash: 4YWGWXRUU2U6IVAYH3ZG27RN6ETNUBAP 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/37] patman: Extract _setup_patchwork() helper from do_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 Factor out the patchwork URL resolution and project lookup into a shared helper function. This reduces duplication and makes the logic available to other commands such as the upcoming review command. Signed-off-by: Simon Glass --- tools/patman/control.py | 61 ++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/tools/patman/control.py b/tools/patman/control.py index 0c3a3097967..53963713a8b 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -111,6 +111,42 @@ def patchwork_status(branch, count, start, end, dest_branch, force, show_comments, False, pwork) +def _setup_patchwork(cser, pwork, ups, pw_url): + """Set up a Patchwork instance from upstream and project settings + + Args: + cser (Cseries): Open cseries instance + pwork (Patchwork or None): Existing instance, or None to create + ups (str or None): Upstream name + pw_url (str or None): Patchwork URL override + + Returns: + Patchwork: Configured instance + + Raises: + ValueError: if the URL or project cannot be resolved + """ + if pwork: + return pwork + if not pw_url and ups: + pw_url = cser.db.upstream_get_patchwork_url(ups) + if not pw_url: + raise ValueError( + 'No patchwork URL found; use -U/--upstream or ' + "configure with 'patman upstream add'") + pwork = Patchwork(pw_url) + proj = cser.project_get(ups) + if not proj: + proj = cser.project_get() + if not proj: + raise ValueError( + "Patchwork project not configured; use " + "'patman patchwork set-project'") + _, proj_id, link_name = proj + pwork.project_set(proj_id, link_name) + return pwork + + def do_series(args, test_db=None, pwork=None, cser=None): """Process a series subcommand @@ -133,28 +169,9 @@ def do_series(args, test_db=None, pwork=None, cser=None): try: cser.open_database() if args.subcmd in needs_patchwork: - if not pwork: - ups = cser.get_series_upstream(args.series) - pw_url = None - if ups: - pw_url = cser.db.upstream_get_patchwork_url(ups) - if not pw_url: - pw_url = args.patchwork_url - if not pw_url: - raise ValueError( - 'No patchwork URL found for upstream ' - f"'{ups}'; use 'patman upstream add' with " - '-p or pass --patchwork-url') - pwork = Patchwork(pw_url) - proj = cser.project_get(ups) - if not proj: - proj = cser.project_get() - if not proj: - raise ValueError( - "Please set project ID with " - "'patman patchwork set-project'") - _, proj_id, link_name = proj - pwork.project_set(proj_id, link_name) + ups = cser.get_series_upstream(args.series) + pwork = _setup_patchwork( + cser, pwork, ups, args.patchwork_url) elif pwork and pwork is not True: raise ValueError( f"Internal error: command '{args.subcmd}' should not have patchwork") From patchwork Sat Apr 4 21:28:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2125 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=1775338289; bh=cIxok7cRAO3IWI3HuEamQ1w6Ri85Jmg1yW2ArQNwJtw=; 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=IxBUnHDPfOO/sUYikZjKAqsewaEB9ESQxF64KFoJj77/Zp8utffaZlBM+us+12I0O jpJyPDAaM8mtm7HB/GKMf3bZCfqEMyy9KK97JW0Y0Kwg0lPn/wZbrV8LLPiAok5rcL 0w1EYws6QPJL5xIK44vipRJfObUPka0vboQZCxreJcWrsXI5jGDyu/HxZ4u2VpZm9J D3pfYx/fOXiQdEv+04FciTR+b1hRbVM0/NXqZR+lx6foAf0y75y+JcI9NqgPm9G93F Bp/Otg35gi2tW50pMbQYFyJQsFbrQHfEekFHvsvyPHmFW2dPxsobhG8spH4Qu7IQ3C 7TVeTKgg/IAHw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 374816A37F for ; Sat, 4 Apr 2026 15:31:29 -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 0nLxjOsnM70o for ; Sat, 4 Apr 2026 15:31:29 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338289; bh=cIxok7cRAO3IWI3HuEamQ1w6Ri85Jmg1yW2ArQNwJtw=; 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=IxBUnHDPfOO/sUYikZjKAqsewaEB9ESQxF64KFoJj77/Zp8utffaZlBM+us+12I0O jpJyPDAaM8mtm7HB/GKMf3bZCfqEMyy9KK97JW0Y0Kwg0lPn/wZbrV8LLPiAok5rcL 0w1EYws6QPJL5xIK44vipRJfObUPka0vboQZCxreJcWrsXI5jGDyu/HxZ4u2VpZm9J D3pfYx/fOXiQdEv+04FciTR+b1hRbVM0/NXqZR+lx6foAf0y75y+JcI9NqgPm9G93F Bp/Otg35gi2tW50pMbQYFyJQsFbrQHfEekFHvsvyPHmFW2dPxsobhG8spH4Qu7IQ3C 7TVeTKgg/IAHw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 258376A375 for ; Sat, 4 Apr 2026 15:31:29 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338286; bh=CmnMBPyuV9EJIbqnchqzagxpjvm9zkfVQkYC45IzBXc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=k8Dhd+TOyX6eRu3J5hIRNbnlUkY93RhR80kYW2A7H+g8aTdOngJtCDMriSVCHFn8O 5rBU5M48xUm27UK37kfIEEHoJR7RILogtOIao758xjM+cRaIXb4Lg65sA+kgd2/pS5 XFIzhQQV3u7YEF9PfwafSbqKeiDjr2ZV59VoCHyMD7YeFJhSrCiM5IGdd32ou6omX0 eJjiTdGrMSc8+PicRGxzLygh0PmUPe7WcpQF7Yb/v7HN+9lNXaNx9WrxdVo3i6Y+rX /cqBtTN7rfvD/xvWMA7EFQRmvm97aZw4cqqyfF5tFeXlVykSZuvMsZS8ia+tOqmCAE g59P/yciRAniQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id CAE756A369; Sat, 4 Apr 2026 15:31: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 mRzwwgW4krYW; Sat, 4 Apr 2026 15:31:26 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338282; bh=PZsftRuf6walNoUIFPbJVRJ89vsIgstoUIKBnaQgLxI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dG2NEIwbUiZsZ6+C2v/NsHdzCR2vDc3ZN85h6fpAT+uI1qiFsY4Dcl/ac0uTKWonE ZmdSouQYGlCtT62TuZ0oZ8bFbe0tcjRg1GqS98e+syqy9Gda60chxErth/cgipZk/t vlFlWggzSh2nxa+JQpR+7EVWsGht5qOy8UgJxXMEVUcXA6WYNoCMK90dGEbmf6VFyl zs4rcJISRswvNAM4XOApH32NHv5Wykq7ADZUeT9L8HRb0rTp2U8nZ1iQSILaKZvt3s EPNKEMck/Jv8UUQmgl0vk9q7Tn+c99xlzB4/4NUlzKOB4ibGNV6wAKKZtPwPoAi4xN GXEzunlJn3h1w== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 084386869D; Sat, 4 Apr 2026 15:31:21 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:46 -0600 Message-ID: <20260404213020.372253-11-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: WYDVSPUKGNXJRTEBXXQ6FTJD4J7EA2UO X-Message-ID-Hash: WYDVSPUKGNXJRTEBXXQ6FTJD4J7EA2UO 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/37] patman: Fix autolink to prefer per-upstream patchwork URL 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 a default patchwork-url is set in ~/.patman, _setup_patchwork() only checks the per-upstream URL when no default is configured. This causes autolink to search the wrong patchwork server when the series has a specific upstream with its own URL. Fix _setup_patchwork() to always check the per-upstream URL and override the config default when one is found. Warn when the upstream URL differs from the configured default. Signed-off-by: Simon Glass --- tools/patman/control.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tools/patman/control.py b/tools/patman/control.py index 53963713a8b..a37893785ad 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -128,16 +128,28 @@ def _setup_patchwork(cser, pwork, ups, pw_url): """ if pwork: return pwork - if not pw_url and ups: - pw_url = cser.db.upstream_get_patchwork_url(ups) + tout.debug(f'_setup_patchwork: ups={ups!r} pw_url={pw_url!r}') + if ups: + ups_url = cser.db.upstream_get_patchwork_url(ups) + if ups_url: + if pw_url and pw_url != ups_url: + tout.info(f' Overriding {pw_url!r} with upstream' + f' {ups!r} URL {ups_url!r}') + pw_url = ups_url + tout.debug(f' URL from upstream {ups!r}: {pw_url!r}') if not pw_url: raise ValueError( 'No patchwork URL found; use -U/--upstream or ' "configure with 'patman upstream add'") pwork = Patchwork(pw_url) proj = cser.project_get(ups) + tout.debug(f' project_get({ups!r}): {proj!r}') if not proj: proj = cser.project_get() + tout.debug(f' project_get(None) fallback: {proj!r}') + if proj: + tout.warning(f"No patchwork project for upstream '{ups}';" + f' using default project {proj[0]} (ID {proj[1]})') if not proj: raise ValueError( "Patchwork project not configured; use " From patchwork Sat Apr 4 21:28:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2126 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=1775338293; bh=JywM0WU/HvD9RSfCF8et7IhVPHrMU4YqyJ5anahO9vo=; 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=JXTVZWkOJb7d+pzd78v9l/TXzD2JOsdrTZwUlKkMlZDzoXx165aXjINhuSmDdFMmU SGbhdbMvslOW+OSxEaCp58SsW4TU81E07NtL40rwlUejo9Ch+8Aeb6frWNudcfeg82 PxKzBDJ4rVJNX/yLQM3CPFaZ9Rx89vExKi5LvqUGOz5EIHxaHTvfVhQXqEUCusnTeI +05GJ+BOGHIGt716+ue9Q2JPOrkPofJtrVjFgLykEclIAh2b7wMFEGyhAT4ur4Q2yh I58tNDZ6VLw4BFS3lFnvEL1SsWi7Xya6TKP4a31W9omaHSZgBSWeoBO4+QwvyDEGOZ akXW/v26ydCkw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C0A416A369 for ; Sat, 4 Apr 2026 15:31: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 rpvl8igqZXT8 for ; Sat, 4 Apr 2026 15:31:33 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338293; bh=JywM0WU/HvD9RSfCF8et7IhVPHrMU4YqyJ5anahO9vo=; 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=JXTVZWkOJb7d+pzd78v9l/TXzD2JOsdrTZwUlKkMlZDzoXx165aXjINhuSmDdFMmU SGbhdbMvslOW+OSxEaCp58SsW4TU81E07NtL40rwlUejo9Ch+8Aeb6frWNudcfeg82 PxKzBDJ4rVJNX/yLQM3CPFaZ9Rx89vExKi5LvqUGOz5EIHxaHTvfVhQXqEUCusnTeI +05GJ+BOGHIGt716+ue9Q2JPOrkPofJtrVjFgLykEclIAh2b7wMFEGyhAT4ur4Q2yh I58tNDZ6VLw4BFS3lFnvEL1SsWi7Xya6TKP4a31W9omaHSZgBSWeoBO4+QwvyDEGOZ akXW/v26ydCkw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AD0DD6A375 for ; Sat, 4 Apr 2026 15:31:33 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338291; bh=ONYaoc/VkFj2RlMDWs9DuZYtaHJOa8dmlKcHVHsYohg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=abvTEa/T+Y7KEjdHGJrzqsFJPGca5Lts3jo2gM0DS+3y5Mtk9km6r4as4ZALCAjIQ ECMZktJ+ERfAtsoQmUturVWTR2WR2XYZ9pw3XIz4PSzTuGOrgJa0xJMQk1sbisZQYm AZ4dtC8U+fnzdkGdKDdZj9cNiOLW2aveZrrnYxrXlpBHvA/Ivr5PutLLAP05HDbv2c 3JtauY35uuuWN8ZTVlYyi2nRrZKnPE3KqmgA/GmijVReq5ib0DyZA80I5ZHwjtjN13 PIqFBnzjlYV+1oPOvokDR5Zk7f8n/lpMIaifkp/WgAHni2NjwnPtGHkrjCZB9n7GRL OGhOF/ZaAsy7A== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AC7096A369; Sat, 4 Apr 2026 15:31:31 -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 lDPr6TU0DpVX; Sat, 4 Apr 2026 15:31:31 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338287; bh=+Hr5s9h1gAxWR4aCmLqNjAtjNfat9NMqfynI76V2Ols=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eV5QWO7V2HgLTynOCPMYkaeoC/oXURIuBEVrkCon2yFuKTRTSxgqWAuF6hRHo1rbk cW56ppYpL21XnoO/CG680hEs+EzlG6wn+QQEpJtcr4DkacYvAgQvT9i/Q0bIuJ46JJ RP7eigH9HNErnS4XOTISR6NZMH+wWQDtPhFNI+//B3tlnglluDiutezlktHFXD9P3s IawhvVvjnPc2IJbfObSM78RqYtrcAG+MAGDnF0IbnStEtEMWriMx2971PDB3FhUdqq HEha5Hcu9KmtVf0iB8oJLq0+GH5Yd24hK6yMQfxxaQw8ne2jDnqWtz6Bbzu/+tSN3u aOxMrhcpS2w2Q== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 2E4B56869D; Sat, 4 Apr 2026 15:31:27 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:47 -0600 Message-ID: <20260404213020.372253-12-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: FGVL6FGNXWKPFGIDLMLJIZGMVH2IDJQZ X-Message-ID-Hash: FGVL6FGNXWKPFGIDLMLJIZGMVH2IDJQZ 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/37] patman: Add ser_ver_set_desc() to update per-version description 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 database method to update the description for a specific series version. This is needed so that autolink can use the correct search term when the patch order changes between versions. Signed-off-by: Simon Glass --- tools/patman/database.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/patman/database.py b/tools/patman/database.py index ec1daa1c073..437a05b3de0 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -657,6 +657,15 @@ class Database: # pylint:disable=R0904 if self.rowcount() != 1: raise ValueError(f'No ser_ver updated (svid {svid})') + def ser_ver_set_desc(self, svid, desc): + """Update the description for a series version + + Args: + svid (int): ser_ver ID num + desc (str): Description text + """ + self.execute('UPDATE ser_ver SET desc = ? WHERE id = ?', (desc, svid)) + def ser_ver_add(self, series_idnum, version, link=None, desc=None): """Add a new ser_ver record From patchwork Sat Apr 4 21:28:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2127 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=1775338297; bh=BxiNrCkv8Xp4SrWLD6vF1dg3UQwDJ1Vv2tPqKBdCsBo=; 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=hT8+Ts4n3uWcTbTmc0csTlCKPxiAAY6m/dmUq3206dF3oNgur7dC9yOlKZrBh9y75 tw5MwmV95/m6bR2zppZ1x1MOW/HY6ZXI41qm8Jk29vpaIEG4K7901ZCW4/G4LRzXNR P7WJ4j5lruvXCMF7Jy9Ci8EuroNOkTVqHdO4yrMY85jYVU3vvIirRhqsoLUG6Uf4z9 FDKu7XTY3Eja9bb0m/zuekuO5AvQTdRBBFH/kv3tnSMy+CfqUfqk7QJ3dQnPnhfat8 6aAeAeaibDDuMlydRr2InsveQvNGsw0AWdq23Lp86rTIJIi3RI7CCBSO56UjfaDXF2 FDKMscPkB6YOA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B9A726A369 for ; Sat, 4 Apr 2026 15:31: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 10024) with ESMTP id cPkrsgipCDro for ; Sat, 4 Apr 2026 15:31:37 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338297; bh=BxiNrCkv8Xp4SrWLD6vF1dg3UQwDJ1Vv2tPqKBdCsBo=; 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=hT8+Ts4n3uWcTbTmc0csTlCKPxiAAY6m/dmUq3206dF3oNgur7dC9yOlKZrBh9y75 tw5MwmV95/m6bR2zppZ1x1MOW/HY6ZXI41qm8Jk29vpaIEG4K7901ZCW4/G4LRzXNR P7WJ4j5lruvXCMF7Jy9Ci8EuroNOkTVqHdO4yrMY85jYVU3vvIirRhqsoLUG6Uf4z9 FDKu7XTY3Eja9bb0m/zuekuO5AvQTdRBBFH/kv3tnSMy+CfqUfqk7QJ3dQnPnhfat8 6aAeAeaibDDuMlydRr2InsveQvNGsw0AWdq23Lp86rTIJIi3RI7CCBSO56UjfaDXF2 FDKMscPkB6YOA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A75296A375 for ; Sat, 4 Apr 2026 15:31:37 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338296; bh=K7waL1Ly8W7ENWHSf6IlqDSI4nVPv5wJY2rMMln4hco=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nq5GGQ3LckTem7vz33BGxJaHDPMYf+B7DM16361HW4WjEV3OD8Nt9P267RTGXe+UO 84/FOYH1dwFsga8wQzSJVCWa0dJzWhFy7F8Lu5WanqbGpCHiEBYiU2DowWIqWNw6vA ZEh6c52/wwdpB8VK9DL7coxwDxgcOEhgMZWVsL3Ju0oVim5xygirXn5BRUvnIYZ4lu NWaZnYitdVh93gcdhItPCDl1y+ZRiJJfoyd695wYfOom4cD1Ht0RqeDj90gVcfbglA /SG9b4akHEzNgA7HDkjDeK2mw6nOU3WhcxBlSFksJB3eGPlJ25gCLfd/joBqsQaA4p le9q39NgE1UBA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5F81B6A375; Sat, 4 Apr 2026 15:31:36 -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 jf73RECQITSk; Sat, 4 Apr 2026 15:31:36 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338292; bh=3Qnqd9Ir0/oVHeybtiqtw+Vo1ts4Z0dJwqlCvSv44d0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=izPzhx3FL12WmxxBlzdTLmbMqI8VbVvILkDku9LHesaFrMR/G17qksQOYEe3H+HW5 V6XZPdOH2equmT/RgFOCuvkmYM0HY/xexGVoIO7CPHIIOtdRIX2sx1EzmSEU1VfCzT msxyLbTxVtG2OO4tul9qmKmbp978TLS6YkIhn4ByVVj/jK0dFXVrYofq8kRjKAqqAt e13T19mcHb/JSYazA5mQiHEpUzV/XOVSpqEvyvVZo9KULR2yQgUFwetY8qlswxYBs9 WaSiqv+L0w6NnHg9rm8tB8KIg/naLjHkHLdclHzIY266UajvtCiS5EHi7JP7UV/bCN kIvVhCgkl/mnw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 607186869D; Sat, 4 Apr 2026 15:31:32 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:48 -0600 Message-ID: <20260404213020.372253-13-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 5SQ7DPICIST2VUM3M277SUJGZ7YOPSPI X-Message-ID-Hash: 5SQ7DPICIST2VUM3M277SUJGZ7YOPSPI 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/37] patman: Use per-version description for autolink search 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 series-level description can become stale when the patch order changes between versions. Use the per-version description (stored in ser_ver) when available, falling back to the series-level description. Also set the per-version description during increment() and scan() so it is available for future autolink searches. Signed-off-by: Simon Glass --- tools/patman/cseries.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index e75e55c2764..5569eb6f4f5 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -191,7 +191,19 @@ class Cseries(cser_helper.CseriesHelper): old_svid = self.get_series_svid(ser.idnum, max_vers) pcd = self.get_pcommit_dict(old_svid) - svid = self.db.ser_ver_add(ser.idnum, vers) + # Set the per-version description from the cover letter or + # first commit subject, so autolink can find the series on + # patchwork even if the patch order changes + meta_name = new_name if not dry_run else branch_name + new_series = patchstream.get_metadata(meta_name, 0, count, + git_dir=self.gitdir) + if new_series.get('cover'): + sv_desc = new_series.cover[0] # pylint: disable=E1136 + elif new_series.commits: + sv_desc = new_series.commits[0].subject + else: + sv_desc = None + svid = self.db.ser_ver_add(ser.idnum, vers, desc=sv_desc) self.db.pcommit_add_list(svid, pcd.values()) if not dry_run: self.commit() @@ -260,13 +272,18 @@ class Cseries(cser_helper.CseriesHelper): str: series description """ _, ser, version, _, _, _, _, _ = self._get_patches(series, version) + svinfo = self.get_ser_ver(ser.idnum, version) - if not ser.desc: + # Use the per-version description if available, since the + # series-level desc may be stale (e.g. patch order changed) + desc = svinfo.desc or ser.desc + if not desc: raise ValueError(f"Series '{ser.name}' has an empty description") + ser.desc = desc pws, options = self.loop.run_until_complete(pwork.find_series( ser, version)) - return pws, options, ser.name, version, ser.desc + return pws, options, ser.name, version, desc def link_auto(self, pwork, series, version, update_commit, wait_s=0): """Automatically find a series link by looking in patchwork @@ -939,6 +956,17 @@ class Cseries(cser_helper.CseriesHelper): self.db.series_set_desc(ser.idnum, branch_desc) tout.notice(f"Updated description to '{branch_desc}'") + # Update per-version description from cover letter or first + # commit, so autolink uses the right search term + if ser.cover: + sv_desc = ser.cover[0] # pylint: disable=E1136 + elif ser.commits: + sv_desc = ser.commits[0].subject + else: + sv_desc = None + if sv_desc: + self.db.ser_ver_set_desc(svid, sv_desc) + if not dry_run: self.commit() seq = len(ser.commits) From patchwork Sat Apr 4 21:28:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2128 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=1775338304; bh=BBvnOvdlOMMORU3UYO8mO6r9LU+j64AoTqSua1vZKuQ=; 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=OPL6+t0NAsKdSvpxuTI9t2j6nuldSm2yIzAx/LWS2US04XxnzwV9ob9QUP9MjvD89 50+3qDuhKYOV6bj804TMnoLwJKPGQPsLYy5KsEDISAGWLj5OzzP2HTC1p0XzPeN4g2 EUmmi+6VVikZd5Iw/INNQlhLF2OqSN7ZU8RBIjeGdzkLZW0ThweXUQgOvthw5tYj65 H4c9vMla0Cu0ciKkb7CO7KEikklr6yzjo0KPvBn6mluQM1H0eaGKVLwxVUAcLpjJhA bJNCEuPIHAduBKwHeN/bV8Yj46dctX7yKMEiaNtLQHEoTPnfUS/OMVkQ+UvuTSIJ1S Mg4BlmyruExGA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5F1B96A382 for ; Sat, 4 Apr 2026 15:31: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 UnarMr17zyUj for ; Sat, 4 Apr 2026 15:31:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338304; bh=BBvnOvdlOMMORU3UYO8mO6r9LU+j64AoTqSua1vZKuQ=; 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=OPL6+t0NAsKdSvpxuTI9t2j6nuldSm2yIzAx/LWS2US04XxnzwV9ob9QUP9MjvD89 50+3qDuhKYOV6bj804TMnoLwJKPGQPsLYy5KsEDISAGWLj5OzzP2HTC1p0XzPeN4g2 EUmmi+6VVikZd5Iw/INNQlhLF2OqSN7ZU8RBIjeGdzkLZW0ThweXUQgOvthw5tYj65 H4c9vMla0Cu0ciKkb7CO7KEikklr6yzjo0KPvBn6mluQM1H0eaGKVLwxVUAcLpjJhA bJNCEuPIHAduBKwHeN/bV8Yj46dctX7yKMEiaNtLQHEoTPnfUS/OMVkQ+UvuTSIJ1S Mg4BlmyruExGA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4B8286A375 for ; Sat, 4 Apr 2026 15:31:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338301; bh=+A7kMRpr/e1dfAIitRKcuV0bnZGuBGRYZizH8fRZDIM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nThro970wugsbaR0nKQqk6VkbRsNuLQWi1ZVZePjgo/3/cx1D256HNvK5x/NTCH6u V4isnRGjCghAZmSsLXSXIipVWXZ02k99OGGaIgTOJIma8tLyBHtXmjOv4WdpHf+cAC U4yGfHTqrm47W19pNbJVn65Im++mm0Fd+6jFa+XOtSO+t8Tx9/LLHbS5SPiUFyHRon 9dJmnNCZQykPc2YADn6xzFRYCNf8R7FNKQMIruFvtimiqOqrl+Hof4SFf+HyQC0EmJ Gw7iuru6GMZYsRm/5dUtkFuDiannF6IV60CPrRUEtqafECoqhOQsX/eu1wIBYdlWbv T7yfXBemgJ4fw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id EA8E26A369; Sat, 4 Apr 2026 15:31:41 -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 B8LMGm0M5AO9; Sat, 4 Apr 2026 15:31:41 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338297; bh=NsydEK3mnjiQetXngqacDBASsFPHrs0UpsCMaNvDxVg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cqKBSMKQcVK5NQi7EnvzrQ57KLnd6MNCgNu6kE7MAlKMQIl1ESwPybePhm336BTPV IfZAfMyz+sIWql0kspgbvu8cOqL0Y2A3mktyIS9dz+5V+Rctago7hggdSnbtiDj94g D+aJg1KcZ8UazdfGLZ+WkK8fhQL2sjR02URdQwLvC/19au9cthc8aQN37KZcK8c368 UQNvPlWq4mGgoSIKfeVwhgVO5OE8sS7GX0O992GEIUL8BqyuqpTBP7zwAFoQkLMZWL bnFO3BS/YTlMsjINaj/tUkHD7GjGaeinXKBIt9UZAx9wQtDaGm5T5CgQYiTiKFXwcS etAT26kEmxfCA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 776C16869D; Sat, 4 Apr 2026 15:31:37 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:49 -0600 Message-ID: <20260404213020.372253-14-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: Q7WKIKU74WCGP62GCY6C5SYINNNTGR4S X-Message-ID-Hash: Q7WKIKU74WCGP62GCY6C5SYINNNTGR4S 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/37] u_boot_pylib: Extract Claude agent utilities from pickman 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 Claude Agent SDK helper functions (check_available(), run_agent_collect(), etc.) are useful beyond just pickman. Move them to a shared u_boot_pylib.claude module so that other tools like patman can also use Claude agents without duplicating the code. Update pickman/agent.py to import from the shared module instead of defining its own copies. Add unit tests for the new module covering availability detection, output collection and error handling. Signed-off-by: Simon Glass --- tools/pickman/agent.py | 58 +++------------- tools/pickman/ftest.py | 43 ++++++------ tools/u_boot_pylib/__init__.py | 4 +- tools/u_boot_pylib/__main__.py | 4 +- tools/u_boot_pylib/claude.py | 64 +++++++++++++++++ tools/u_boot_pylib/test_claude.py | 111 ++++++++++++++++++++++++++++++ 6 files changed, 211 insertions(+), 73 deletions(-) create mode 100644 tools/u_boot_pylib/claude.py create mode 100644 tools/u_boot_pylib/test_claude.py diff --git a/tools/pickman/agent.py b/tools/pickman/agent.py index 7e482a7b8ee..f20e0f2fda5 100644 --- a/tools/pickman/agent.py +++ b/tools/pickman/agent.py @@ -27,8 +27,14 @@ SIGNAL_SUCCESS = 'success' SIGNAL_APPLIED = 'already_applied' SIGNAL_CONFLICT = 'conflict' -# Maximum buffer size for agent responses -MAX_BUFFER_SIZE = 10 * 1024 * 1024 # 10MB +# Import common Claude agent utilities from shared module +from u_boot_pylib.claude import ( + AGENT_AVAILABLE, MAX_BUFFER_SIZE, check_available, run_agent_collect, +) + +ClaudeAgentOptions = None +if AGENT_AVAILABLE: + from u_boot_pylib.claude import ClaudeAgentOptions # pylint: disable=C0412 # Commits that need special handling (regenerate instead of cherry-pick) # These run savedefconfig on all boards and depend on target branch @@ -37,54 +43,6 @@ QCONFIG_SUBJECTS = [ 'configs: Resync with savedefconfig', ] -# Check if claude_agent_sdk is available -try: - from claude_agent_sdk import query, ClaudeAgentOptions - AGENT_AVAILABLE = True -except ImportError: - AGENT_AVAILABLE = False - - -def check_available(): - """Check if the Claude Agent SDK is available - - Returns: - bool: True if available, False otherwise - """ - if not AGENT_AVAILABLE: - tout.error('Claude Agent SDK not available') - tout.error('Install with: pip install claude-agent-sdk') - return False - return True - - -async def run_agent_collect(prompt, options): - """Run a Claude agent and collect its conversation log - - Sends the prompt to a Claude agent, streams output to stdout and - collects all text blocks into a conversation log. - - Args: - prompt (str): The prompt to send to the agent - options (ClaudeAgentOptions): Agent configuration - - Returns: - tuple: (success, conversation_log) where success is bool and - conversation_log is the agent's output text - """ - conversation_log = [] - try: - async for message in query(prompt=prompt, options=options): - if hasattr(message, 'content'): - for block in message.content: - if hasattr(block, 'text'): - print(block.text) - conversation_log.append(block.text) - return True, '\n\n'.join(conversation_log) - except (RuntimeError, ValueError, OSError) as exc: - tout.error(f'Agent failed: {exc}') - return False, '\n\n'.join(conversation_log) - def is_qconfig_commit(subject): """Check if a commit subject indicates a qconfig resync commit diff --git a/tools/pickman/ftest.py b/tools/pickman/ftest.py index 261ca4cd2d5..e5c1ceb1359 100644 --- a/tools/pickman/ftest.py +++ b/tools/pickman/ftest.py @@ -3121,7 +3121,8 @@ class TestRunAgentCollect(unittest.TestCase): async def fake_query(**kwargs): yield msg - with mock.patch.object(agent, 'query', fake_query, create=True): + with mock.patch('u_boot_pylib.claude.query', fake_query, + create=True): with terminal.capture(): opts = mock.MagicMock() success, log = asyncio.run( @@ -3141,7 +3142,8 @@ class TestRunAgentCollect(unittest.TestCase): yield msg raise RuntimeError('agent crashed') - with mock.patch.object(agent, 'query', fake_query, create=True): + with mock.patch('u_boot_pylib.claude.query', fake_query, + create=True): with terminal.capture(): opts = mock.MagicMock() success, log = asyncio.run( @@ -3157,7 +3159,8 @@ class TestRunAgentCollect(unittest.TestCase): async def fake_query(**kwargs): yield msg - with mock.patch.object(agent, 'query', fake_query, create=True): + with mock.patch('u_boot_pylib.claude.query', fake_query, + create=True): with terminal.capture(): opts = mock.MagicMock() success, log = asyncio.run( @@ -6610,14 +6613,14 @@ class TestResolveSubtreeConflicts(unittest.TestCase): """Test successful conflict resolution.""" mock_collect = mock.AsyncMock(return_value=(True, 'resolved')) with terminal.capture(): - with mock.patch.object(agent, 'AGENT_AVAILABLE', True): - with mock.patch.object(agent, 'run_agent_collect', - mock_collect): - with mock.patch.object(agent, 'ClaudeAgentOptions', - create=True): - success, log = agent.resolve_subtree_conflicts( - 'dts', 'v6.15-dts', 'dts/upstream', - '/tmp/test') + with mock.patch('u_boot_pylib.claude.AGENT_AVAILABLE', True), \ + mock.patch.object(agent, 'run_agent_collect', + mock_collect), \ + mock.patch.object(agent, 'ClaudeAgentOptions', + create=True): + success, log = agent.resolve_subtree_conflicts( + 'dts', 'v6.15-dts', 'dts/upstream', + '/tmp/test') self.assertTrue(success) self.assertEqual(log, 'resolved') @@ -6625,20 +6628,20 @@ class TestResolveSubtreeConflicts(unittest.TestCase): """Test failed conflict resolution.""" mock_collect = mock.AsyncMock(return_value=(False, 'failed')) with terminal.capture(): - with mock.patch.object(agent, 'AGENT_AVAILABLE', True): - with mock.patch.object(agent, 'run_agent_collect', - mock_collect): - with mock.patch.object(agent, 'ClaudeAgentOptions', - create=True): - success, log = agent.resolve_subtree_conflicts( - 'dts', 'v6.15-dts', 'dts/upstream', - '/tmp/test') + with mock.patch('u_boot_pylib.claude.AGENT_AVAILABLE', True), \ + mock.patch.object(agent, 'run_agent_collect', + mock_collect), \ + mock.patch.object(agent, 'ClaudeAgentOptions', + create=True): + success, log = agent.resolve_subtree_conflicts( + 'dts', 'v6.15-dts', 'dts/upstream', + '/tmp/test') self.assertFalse(success) def test_sdk_unavailable(self): """Test returns failure when SDK is not available.""" with terminal.capture(): - with mock.patch.object(agent, 'AGENT_AVAILABLE', False): + with mock.patch('u_boot_pylib.claude.AGENT_AVAILABLE', False): success, log = agent.resolve_subtree_conflicts( 'dts', 'v6.15-dts', 'dts/upstream', '/tmp/test') self.assertFalse(success) diff --git a/tools/u_boot_pylib/__init__.py b/tools/u_boot_pylib/__init__.py index 807a62e0743..c176e332a51 100644 --- a/tools/u_boot_pylib/__init__.py +++ b/tools/u_boot_pylib/__init__.py @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0+ -__all__ = ['command', 'cros_subprocess', 'gitutil', 'terminal', 'test_util', - 'tools', 'tout'] +__all__ = ['claude', 'command', 'cros_subprocess', 'gitutil', 'terminal', + 'test_util', 'tools', 'tout'] diff --git a/tools/u_boot_pylib/__main__.py b/tools/u_boot_pylib/__main__.py index 6b9f4f3d950..5687f9b51a5 100755 --- a/tools/u_boot_pylib/__main__.py +++ b/tools/u_boot_pylib/__main__.py @@ -28,12 +28,14 @@ def run_tests(): help='Verbose output') args = parser.parse_args() + from u_boot_pylib import test_claude + 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]) + cros_subprocess.TestSubprocess, test_claude.TestClaude]) sys.exit(0 if result.wasSuccessful() else 1) diff --git a/tools/u_boot_pylib/claude.py b/tools/u_boot_pylib/claude.py new file mode 100644 index 00000000000..29dff1d1d4b --- /dev/null +++ b/tools/u_boot_pylib/claude.py @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2025 Canonical Ltd. +# Written by Simon Glass +# + +"""Common Claude Agent SDK utilities. + +Provides shared functions for running Claude agents across tools that need +AI assistance (e.g. pickman, patman review). +""" + +from u_boot_pylib import tout + +# Maximum buffer size for agent responses +MAX_BUFFER_SIZE = 10 * 1024 * 1024 # 10MB + +# Check if claude_agent_sdk is available +try: + from claude_agent_sdk import query, ClaudeAgentOptions + AGENT_AVAILABLE = True +except ImportError: + AGENT_AVAILABLE = False + + +def check_available(): + """Check if the Claude Agent SDK is available + + Returns: + bool: True if available, False otherwise + """ + if not AGENT_AVAILABLE: + tout.error('Claude Agent SDK not available') + tout.error('Install with: pip install claude-agent-sdk') + return False + return True + + +async def run_agent_collect(prompt, options): + """Run a Claude agent and collect its conversation log + + Sends the prompt to a Claude agent, streams output to stdout and + collects all text blocks into a conversation log. + + Args: + prompt (str): The prompt to send to the agent + options (ClaudeAgentOptions): Agent configuration + + Returns: + tuple: (success, conversation_log) where success is bool and + conversation_log is the agent's output text + """ + conversation_log = [] + try: + async for message in query(prompt=prompt, options=options): + if hasattr(message, 'content'): + for block in message.content: + if hasattr(block, 'text'): + print(block.text) + conversation_log.append(block.text) + return True, '\n\n'.join(conversation_log) + except (RuntimeError, ValueError, OSError) as exc: + tout.error(f'Agent failed: {exc}') + return False, '\n\n'.join(conversation_log) diff --git a/tools/u_boot_pylib/test_claude.py b/tools/u_boot_pylib/test_claude.py new file mode 100644 index 00000000000..c564dddb70e --- /dev/null +++ b/tools/u_boot_pylib/test_claude.py @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2025 Canonical Ltd. +# + +"""Tests for the Claude Agent SDK utilities module.""" + +import asyncio +import unittest +from unittest.mock import MagicMock + +from u_boot_pylib import claude +from u_boot_pylib import terminal + + +class TestClaude(unittest.TestCase): + """Tests for u_boot_pylib.claude""" + + def test_check_available_when_sdk_missing(self): + """check_available() returns False when SDK is not installed""" + if not claude.AGENT_AVAILABLE: + with terminal.capture(): + self.assertFalse(claude.check_available()) + + def test_check_available_when_sdk_present(self): + """check_available() returns True when SDK is installed""" + old = claude.AGENT_AVAILABLE + try: + claude.AGENT_AVAILABLE = True + self.assertTrue(claude.check_available()) + finally: + claude.AGENT_AVAILABLE = old + + def test_max_buffer_size(self): + """MAX_BUFFER_SIZE is defined and reasonable""" + self.assertEqual(claude.MAX_BUFFER_SIZE, 10 * 1024 * 1024) + + def _setup_claude_with_mock_query(self, mock_query): + """Inject a mock query function into the claude module""" + claude.query = mock_query + + def test_run_agent_collect_success(self): + """run_agent_collect() collects text from agent messages""" + block1 = MagicMock() + block1.text = 'Hello' + msg1 = MagicMock() + msg1.content = [block1] + + block2 = MagicMock() + block2.text = 'World' + msg2 = MagicMock() + msg2.content = [block2] + + # pylint: disable=W0613 + async def mock_query(**kwargs): + for msg in [msg1, msg2]: + yield msg + + self._setup_claude_with_mock_query(mock_query) + loop = asyncio.new_event_loop() + with terminal.capture(): + success, log = loop.run_until_complete( + claude.run_agent_collect('test prompt', MagicMock())) + loop.close() + + self.assertTrue(success) + self.assertIn('Hello', log) + self.assertIn('World', log) + + def test_run_agent_collect_handles_error(self): + """run_agent_collect() returns False on agent failure""" + # pylint: disable=W0613 + async def mock_query(**kwargs): + raise RuntimeError('Agent crashed') + yield # pylint: disable=W0101 + + self._setup_claude_with_mock_query(mock_query) + loop = asyncio.new_event_loop() + with terminal.capture(): + success, _ = loop.run_until_complete( + claude.run_agent_collect('test prompt', MagicMock())) + loop.close() + + self.assertFalse(success) + + def test_run_agent_collect_skips_non_text_blocks(self): + """run_agent_collect() ignores blocks without text attribute""" + text_block = MagicMock() + text_block.text = 'Real text' + tool_block = MagicMock(spec=[]) # No text attribute + + msg = MagicMock() + msg.content = [tool_block, text_block] + + # pylint: disable=W0613 + async def mock_query(**kwargs): + yield msg + + self._setup_claude_with_mock_query(mock_query) + loop = asyncio.new_event_loop() + with terminal.capture(): + success, log = loop.run_until_complete( + claude.run_agent_collect('test prompt', MagicMock())) + loop.close() + + self.assertTrue(success) + self.assertIn('Real text', log) + + +if __name__ == '__main__': + unittest.main() From patchwork Sat Apr 4 21:28:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2129 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=1775338308; bh=K8IXDjNtrxI3VZlPWddoRm3yVPEmIFGoqzz7IRO4EcI=; 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=vP8KHMJwU79ktj++na1evL/Hi2WCWEIKv+uEyI+S9CM1g4nbXbJ8IxYmNnQBCz144 XjAv1pBrnwAyLETRTaQMa2K4Tugm0IHZBgdmd+S2Cu5ADJBElv0md2zNCfXHEjt096 UpOyaQJ52tQrfKimgsu+HUhrehnwZRBF1dyNhG5q1pt9+4fxVcQvpVmabTrVoMnSJP zm3EjF4sugJEtdVUKuJ96CDf3PSG6u8Ep2rrZkTcf5JdrhQnTRtgvQIag6OFlQcG8p /xSwJJAm8R7MRKhGRCmyIU7X8rq5zpHReeDR+uVMHfjy44b8mqIlPe8Hq0XGd9Bmrl S1AMgultXWdKg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id EDC9F6A382 for ; Sat, 4 Apr 2026 15:31: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 ILuF9ubx-ZCx for ; Sat, 4 Apr 2026 15:31:48 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338308; bh=K8IXDjNtrxI3VZlPWddoRm3yVPEmIFGoqzz7IRO4EcI=; 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=vP8KHMJwU79ktj++na1evL/Hi2WCWEIKv+uEyI+S9CM1g4nbXbJ8IxYmNnQBCz144 XjAv1pBrnwAyLETRTaQMa2K4Tugm0IHZBgdmd+S2Cu5ADJBElv0md2zNCfXHEjt096 UpOyaQJ52tQrfKimgsu+HUhrehnwZRBF1dyNhG5q1pt9+4fxVcQvpVmabTrVoMnSJP zm3EjF4sugJEtdVUKuJ96CDf3PSG6u8Ep2rrZkTcf5JdrhQnTRtgvQIag6OFlQcG8p /xSwJJAm8R7MRKhGRCmyIU7X8rq5zpHReeDR+uVMHfjy44b8mqIlPe8Hq0XGd9Bmrl S1AMgultXWdKg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D432B6A375 for ; Sat, 4 Apr 2026 15:31:48 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338307; bh=h3KoLF2kXqRZFkqGOFpi8Sl78kFPKjvA9Kb1Wnufacg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=w1zBmxDbnxc2r7cX2ZWmVmy4UXlMMbsXJPEeCyx3A9q7fYGxUrgYjbXAa1BzfssTi wgSSlfrety3kjMTN/174c+npOtb5x+U1CDLLlJA5oJ0bL/TRBU0w/mGlfgN5GUy1ft KHxBa4ZPpgEtB9hRDEtPIt1be0C2KL9cdpzL0ky8UNi2dClmOUc6kfFF508upeZy1c sA1mv6a2ume9GOBy+JmdJf2jx9U0VaRut6Ij7l9slSozUqgtZ0uUzlE+F7KJgnjPeo o0CUi4vQK4iyYCpDqHht0fe5ynnhtL7yY1xcYxn+EtiPDCJHsfPVZIIfkYagsHdN+x wR2GePvt971UQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6C4356A369; Sat, 4 Apr 2026 15:31:47 -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 zhctGYpu-l9R; Sat, 4 Apr 2026 15:31:47 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338303; bh=xhaX62+oC3mAGjaIUyclFMnqhl8AKlWCS4/1IJAKqOc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wQBGSbTaEXn25ChCpC0kNAaxx6/xsf8qWhffsuKjhSHn4ZvbRW9w1YiW2ZMn89RIS TSFu2xDoUAnGAwFOrrECvYfPzwyVRvg1u5iqGBapfUCJ9cyQwPlpOVyE7oIaIEd9bA nTq2mc3R2LkWOo7JgLRuN/jHYGtmyDl2h6hfvx5A7/IUhDwnrsvOAPUymCICTuOoO0 l1yHVSM58IyoneLKoTG9Lo8tZ5yQRLBZBINrtN5cTVMnifjk5XZfsMkgugx2vjsV3i ZULG4IwAOANC8uuegCJOEn2RYGrLuKJhEnpmScgiif9W52whLVbGlmuLVvIzktCX5J SbkPxgayhsu/w== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 10BC16869D; Sat, 4 Apr 2026 15:31:43 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:50 -0600 Message-ID: <20260404213020.372253-15-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: HNJEGIQOQRZ6X2H46YHOKUGHHGTUBJLK X-Message-ID-Hash: HNJEGIQOQRZ6X2H46YHOKUGHHGTUBJLK 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/37] patman: Make patchwork query_series() and get_patch_comments() public 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 Rename _query_series() to query_series() and _get_patch_comments() to get_patch_comments() so that review.py can call them without accessing protected members. Update all internal callers. Signed-off-by: Simon Glass --- tools/patman/patchwork.py | 10 +++++----- tools/patman/status.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py index 5192b817968..de676161eee 100644 --- a/tools/patman/patchwork.py +++ b/tools/patman/patchwork.py @@ -270,7 +270,7 @@ class Patchwork: async with aiohttp.ClientSession() as client: return await self._request(client, 'projects/') - async def _query_series(self, client, desc): + async def query_series(self, client, desc): """Query series by name Args: @@ -306,7 +306,7 @@ class Patchwork: name_found = [] # Do a series query on the description - res = await self._query_series(client, desc) + res = await self.query_series(client, desc) for pws in res: if pws['name'] == desc: if int(pws['version']) == version: @@ -317,7 +317,7 @@ class Patchwork: # series name cmt = ser.commits[0] - res = await self._query_series(client, cmt.subject) + res = await self.query_series(client, cmt.subject) for pws in res: patch = Patch(0) patch.parse_subject(pws['name']) @@ -523,7 +523,7 @@ class Patchwork: """ return await self._request(client, f'patches/{patch_id}/') - async def _get_patch_comments(self, client, patch_id): + async def get_patch_comments(self, client, patch_id): """Read comments about a patch Args: @@ -783,7 +783,7 @@ On Tue, 4 Mar 2025 at 06:09, Simon Glass wrote: """ data = await self.get_patch(client, patch_id) state = data['state'] - comment_data = await self._get_patch_comments(client, patch_id) + comment_data = await self.get_patch_comments(client, patch_id) return Patch(patch_id, state, data, comment_data) diff --git a/tools/patman/status.py b/tools/patman/status.py index 967fef3ad6e..2828e4a3bfc 100644 --- a/tools/patman/status.py +++ b/tools/patman/status.py @@ -28,7 +28,7 @@ def process_reviews(content, comment_data, base_rtags): Args: content (str): Content text of the patch itself - see pwork.get_patch() comment_data (list of dict): Comments for the patch - see - pwork._get_patch_comments() + pwork.get_patch_comments() base_rtags (dict): base review tags (before any comments) key: Response tag (e.g. 'Reviewed-by') value: Set of people who gave that response, each a name/email From patchwork Sat Apr 4 21:28:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2130 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=1775338315; bh=qed2PkWEvCgNTZ9DsYhoPtQ0N+ZKXTuw3Hc0APcQOfE=; 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=KQye4mGFxm4hB/Ff+ngyNEVKU0bSIM3uOL2hkWEoK/Hv67zP9/1K63K4CA6dq9kvW YNhKKZcOwXgUIFpuaBrwnM+uSpSob7FJrCK8Ycw5HjgV7JdEJkpgEzeII34/QR2bax ibkrBqyf0tVUvvmn2v1iX0xO8yoANuy8Pn/5oGnm/dGtyg3Pj0VGQ/KRidI/A6eXGf JDd5WWKDz7wXo38rH9ZgfYHd4m7us2SNQ/r66aEvNi2l4VESTcC6erlZHc49XnGuAN /LJEMr7avfCC3n9hZp5umX8rMPW+nAp8rKhbjsmRag4ryE9X3E7b5kEOofqvrjRPJq L1p2Ze2EsMT9A== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C21506A382 for ; Sat, 4 Apr 2026 15:31:55 -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 dI2dig_k38G4 for ; Sat, 4 Apr 2026 15:31:55 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338315; bh=qed2PkWEvCgNTZ9DsYhoPtQ0N+ZKXTuw3Hc0APcQOfE=; 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=KQye4mGFxm4hB/Ff+ngyNEVKU0bSIM3uOL2hkWEoK/Hv67zP9/1K63K4CA6dq9kvW YNhKKZcOwXgUIFpuaBrwnM+uSpSob7FJrCK8Ycw5HjgV7JdEJkpgEzeII34/QR2bax ibkrBqyf0tVUvvmn2v1iX0xO8yoANuy8Pn/5oGnm/dGtyg3Pj0VGQ/KRidI/A6eXGf JDd5WWKDz7wXo38rH9ZgfYHd4m7us2SNQ/r66aEvNi2l4VESTcC6erlZHc49XnGuAN /LJEMr7avfCC3n9hZp5umX8rMPW+nAp8rKhbjsmRag4ryE9X3E7b5kEOofqvrjRPJq L1p2Ze2EsMT9A== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AC1886A375 for ; Sat, 4 Apr 2026 15:31:55 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338312; bh=ITgI3b57TaPLdPHaE68IKnPW3g3Rk1laIJWPWLj3V3o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TqmMCke4BWOpCenoB4sFp0Cs/ZnmE8Gf7SPHk53biDJltyn+QRsbGl28WipOIEkfq zXBkRG5YKnFt/gpyvKP+jBxY214KshEwL3DTdMuYIs8ksOdztttJxZfTm0JlJzasfx jtrR3ovedSbC7jhkUnQOm/O30RehCpLVwyizyy1wAM65Krxc0r08NvuYMSlAeAbnGI 3Xb+uSwsJ2EnIM1y4l1yexV4t1DBLzSRQriTT2tnQHuCErVYTKES4HtyziSm/oaxJM is3JD8B5uoeTgvmZFzQfTUabMjofFyuG42pIwN8NgJ8O9HSItkXUal+IIUbpXOXIKT HhDcS4nzHxK7g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E603A6A369; Sat, 4 Apr 2026 15:31: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 10026) with ESMTP id zgjfZvB9aohB; Sat, 4 Apr 2026 15:31:52 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338308; bh=EFuaZ9/wNPJeXmieHwjfMG8GrDn+GSjVAWVMOb65z6g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mS+fsYyrImz1wrOGGKz9dwRUc/GI5BIy4/1j0tIWdIussORGkkhZWo+wjcttPEMuO CrXvNGGeZOT/xghF3QlZzEJ7ZIHa46CQl7OpCk5sqs3N+pKU9e01wbShZwsBPN0Wct qNd02w4vnO1EFMPan/JTJlF/ONW+o2BYOnRz8ue72S0Ito9T/z8+ua4ILgMg4Dw3e4 UY8P0PcO4sIEcbDxCIY7/+M3GYSg5CqtUqDTGty3ygxb+jhJqqGt9BMpsmtnx4it62 fcpgxAEfzwwLg6SfuAHlHle3KOnYbcoqSPaFV4YYHIMxLpIuU/dYoKuLbRy631Qzws IE/iyu8qT1z5Q== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 8B7016869D; Sat, 4 Apr 2026 15:31:48 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:51 -0600 Message-ID: <20260404213020.372253-16-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 6KNJODKUF64AAIXRMIDYQFMKIQH46JFP X-Message-ID-Hash: 6KNJODKUF64AAIXRMIDYQFMKIQH46JFP 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/37] patman: Add debug logging to autolink and patchwork queries 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 tout.debug() calls throughout the autolink pipeline so failures can be diagnosed with -v. Log the patchwork URL resolution, project lookup, series queries and autolink progress. Signed-off-by: Simon Glass --- tools/patman/control.py | 1 + tools/patman/cseries.py | 8 ++++++++ tools/patman/patchwork.py | 5 +++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/patman/control.py b/tools/patman/control.py index a37893785ad..61379cc108e 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -182,6 +182,7 @@ def do_series(args, test_db=None, pwork=None, cser=None): cser.open_database() if args.subcmd in needs_patchwork: ups = cser.get_series_upstream(args.series) + tout.debug(f'Series upstream: {ups!r}') pwork = _setup_patchwork( cser, pwork, ups, args.patchwork_url) elif pwork and pwork is not True: diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index 5569eb6f4f5..1dd1550f367 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -302,11 +302,19 @@ class Cseries(cser_helper.CseriesHelper): stop = start + wait_s sleep_time = 5 last_options = None + first = True while True: pws, options, name, version, desc = self.link_search( pwork, series, version) + if first: + tout.debug(f"Autolinking series '{name}' v{version}" + f" (timeout {wait_s}s)") + first = False + tout.debug(f"Searching {pwork.url} project {pwork.proj_id}" + f" for '{desc}'") if pws: tout.clear_progress() + tout.debug(f'Found link: {pws}') if wait_s: tout.notice('Link completed after ' f'{self.get_time() - start} seconds') diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py index de676161eee..3e8f7c6c62c 100644 --- a/tools/patman/patchwork.py +++ b/tools/patman/patchwork.py @@ -281,8 +281,9 @@ class Patchwork: list of series matches, each a dict, see get_series() """ query = desc.replace(' ', '+') - return await self._request( - client, f'series/?project={self.proj_id}&q={query}') + subpath = f'series/?project={self.proj_id}&q={query}' + tout.debug(f' GET {self.url}/api/1.2/{subpath}') + return await self._request(client, subpath) async def _find_series(self, client, svid, ser_id, version, ser): """Find a series on the server From patchwork Sat Apr 4 21:28:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2131 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=1775338320; bh=72v8ozrKeQBGCp15J/I2XoEb/9PRdMfr4ohXEDMRPzI=; 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=YXRqqn6SyAyhRAGqVK8C4E3gtNKpi0k3o02qTcxN9fEpQmd/GsUykms5pByVzx/+3 6kRhuJQaTxDe/ez/nL/HH5Svn+0e0qlw6ERuIydelb7cxoPAYxQxaPsl2hWFAS1FGr G/vBg6seQ1iCw94lrWlxb+OPTBi4Wlom+1D8SGM0cq3VSgbqW8m+mIIwFr/BArPHzQ y8qyjvwhyGMLIb1/VACBx5sPLkyZxHSifupQCuXmTrheC8P7YqTFvXMgK/Y1HzBaZX lspr+P9aR9pKJdER+Wl7sFyxxK0RmOtCGa9cVEDms0Iq9VO19ir7xT6FwtiRP4413R 28eLfk9HQITgw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4B1E96A382 for ; Sat, 4 Apr 2026 15:32: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 10024) with ESMTP id P9DN97pZCldW for ; Sat, 4 Apr 2026 15:32:00 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338320; bh=72v8ozrKeQBGCp15J/I2XoEb/9PRdMfr4ohXEDMRPzI=; 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=YXRqqn6SyAyhRAGqVK8C4E3gtNKpi0k3o02qTcxN9fEpQmd/GsUykms5pByVzx/+3 6kRhuJQaTxDe/ez/nL/HH5Svn+0e0qlw6ERuIydelb7cxoPAYxQxaPsl2hWFAS1FGr G/vBg6seQ1iCw94lrWlxb+OPTBi4Wlom+1D8SGM0cq3VSgbqW8m+mIIwFr/BArPHzQ y8qyjvwhyGMLIb1/VACBx5sPLkyZxHSifupQCuXmTrheC8P7YqTFvXMgK/Y1HzBaZX lspr+P9aR9pKJdER+Wl7sFyxxK0RmOtCGa9cVEDms0Iq9VO19ir7xT6FwtiRP4413R 28eLfk9HQITgw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 39C766A375 for ; Sat, 4 Apr 2026 15:32:00 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338318; bh=kC9azy48FzeQxHtKOMHR/155ingUG/VbevbC01SUFNs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Uv2/Hqqo1xKG7fymr258fNLoQ21myZ6pQjd/owW/asMJmjIlVgq1AGlOyw51Ykl1d RxlHWz9ldgXrTPlVulRF5P+M1bC/hwCBqszIsf3jMcD2/t8IhvnwptGn6IcgY6rI3M RPaQLTOoXRS0by6fvzE+w+ZPVe7BUvrIEHxUEt+0+JayIKmAFpIvLcSz4xt58IZPsZ Sn7zdzKdX8AEDWPQ/Oc8mE8W3bj/yxsqaGjjj2DVFwyt5c8NwXAv0C1d6PR9g/DC1V /Zjy0YpYKHw++R1yQecgHPDenDaTZDskrNgs8F7MfJPVOl22MK5PTosLSQXeIQCotp ccPwJDV640Bzg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3AFB06A369; Sat, 4 Apr 2026 15:31: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 BG5ut5yZM13x; Sat, 4 Apr 2026 15:31:58 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338314; bh=Rno+Cy1qzqg/RLx7F/7iH8vKfRyEUhowJLCjZbe8V1A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KJ77d5NsN19nwxu6mAxaMYUOttaA/+MtHcVGM/tZEBOMoQbkZDAUZnwgMf/Vj6jzW aftlJa8GwstFaW09GCegTQpVWt5M/dSHzLjESC3MdQCw8SmbF2YErKq/rSaoB9FPkO 3piat4cWZICdT3oxSNk2tjWo3247YpnmaWR44mdTI7pHou7MtD6dpzbisRs5+mQjSl urV/B46rSD83nOl3WJMzkJUXI40w2guVKayXksXUtOIC0Zj23alhHDWhOzOSNRWlqA pnChDBk964B5w7ZFJOoKXqg5yJAbKIA/7Tg87Q2s4dxEeoaPcZ3Uch3bl2WlLHHT/4 6LkWTJ3jcyP5g== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id F34A86869D; Sat, 4 Apr 2026 15:31:53 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:52 -0600 Message-ID: <20260404213020.372253-17-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: S2DIIN5JDJDMHNX44EXBAY2FSFB3TQGW X-Message-ID-Hash: S2DIIN5JDJDMHNX44EXBAY2FSFB3TQGW 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/37] u_boot_pylib: Catch API errors from the Claude Agent SDK 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 Claude Agent SDK raises a bare Exception on API errors (e.g. 500 Internal Server Error), but run_agent_collect() only catches RuntimeError, ValueError and OSError. This causes the entire review to crash when a single agent call fails, even though the review loop already handles the failure gracefully with a placeholder message. Catch bare Exceptions whose message indicates an API or process error (containing 'API Error' or 'exit code') while re-raising unexpected exceptions that indicate real bugs. Signed-off-by: Simon Glass --- tools/u_boot_pylib/claude.py | 5 +++++ tools/u_boot_pylib/test_claude.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/tools/u_boot_pylib/claude.py b/tools/u_boot_pylib/claude.py index 29dff1d1d4b..6ba980b6b2e 100644 --- a/tools/u_boot_pylib/claude.py +++ b/tools/u_boot_pylib/claude.py @@ -62,3 +62,8 @@ async def run_agent_collect(prompt, options): except (RuntimeError, ValueError, OSError) as exc: tout.error(f'Agent failed: {exc}') return False, '\n\n'.join(conversation_log) + except Exception as exc: + if 'API Error' in str(exc) or 'exit code' in str(exc): + tout.error(f'Agent failed: {exc}') + return False, '\n\n'.join(conversation_log) + raise diff --git a/tools/u_boot_pylib/test_claude.py b/tools/u_boot_pylib/test_claude.py index c564dddb70e..2a666e6c396 100644 --- a/tools/u_boot_pylib/test_claude.py +++ b/tools/u_boot_pylib/test_claude.py @@ -83,6 +83,37 @@ class TestClaude(unittest.TestCase): self.assertFalse(success) + def test_run_agent_collect_handles_api_error(self): + """run_agent_collect() catches SDK API errors""" + # pylint: disable=W0613,W0719 + async def mock_query(**kwargs): + raise Exception( + 'Command failed with exit code 1 (exit code: 1)') + yield # pylint: disable=W0101 + + self._setup_claude_with_mock_query(mock_query) + loop = asyncio.new_event_loop() + with terminal.capture(): + success, _ = loop.run_until_complete( + claude.run_agent_collect('test prompt', MagicMock())) + loop.close() + + self.assertFalse(success) + + def test_run_agent_collect_reraises_unknown(self): + """run_agent_collect() re-raises unexpected exceptions""" + # pylint: disable=W0613 + async def mock_query(**kwargs): + raise TypeError('unexpected bug') + yield # pylint: disable=W0101 + + self._setup_claude_with_mock_query(mock_query) + loop = asyncio.new_event_loop() + with self.assertRaises(TypeError): + loop.run_until_complete( + claude.run_agent_collect('test prompt', MagicMock())) + loop.close() + def test_run_agent_collect_skips_non_text_blocks(self): """run_agent_collect() ignores blocks without text attribute""" text_block = MagicMock() From patchwork Sat Apr 4 21:28: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: 2132 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=1775338326; bh=FYfZNwBGhC8uApGs8HSOY0rZ0kcIKd+oC0OZSITL1gQ=; 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=Mvt/TX43wO1n7+BnNpVe7jRNlz+/mayss5zvGMIAKrlVnJXhmSLz/y34pNwcXpIiH IETzd2Qg4p8juoEG7uBCcDz4OeXh1sdUtfCMr/A4RZS21mYofxqmoctZM/fxPMDNIm ZVyuvDunUNjQ65VFD8xodieYGTWgT9M+PS40gT0Sn9SvAyJlW9AK0LpO/VrGli9hX5 W9blhy9+3BFFMSKYSU/g71AckgF/KJHUZ9Hf/dDNRG5Z0E0oCsMlcdUUlZocxXB2ul n+/DBgOF8cTYe84178vZZNmJwpCVHc5FeMw2hN2BuDvU2RwqQDmh9t4nTt7PSwmiel qLYnplESbx2xw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2FACD6A386 for ; Sat, 4 Apr 2026 15:32: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 10024) with ESMTP id L-IpRfJF0oGd for ; Sat, 4 Apr 2026 15:32:06 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338326; bh=FYfZNwBGhC8uApGs8HSOY0rZ0kcIKd+oC0OZSITL1gQ=; 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=Mvt/TX43wO1n7+BnNpVe7jRNlz+/mayss5zvGMIAKrlVnJXhmSLz/y34pNwcXpIiH IETzd2Qg4p8juoEG7uBCcDz4OeXh1sdUtfCMr/A4RZS21mYofxqmoctZM/fxPMDNIm ZVyuvDunUNjQ65VFD8xodieYGTWgT9M+PS40gT0Sn9SvAyJlW9AK0LpO/VrGli9hX5 W9blhy9+3BFFMSKYSU/g71AckgF/KJHUZ9Hf/dDNRG5Z0E0oCsMlcdUUlZocxXB2ul n+/DBgOF8cTYe84178vZZNmJwpCVHc5FeMw2hN2BuDvU2RwqQDmh9t4nTt7PSwmiel qLYnplESbx2xw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1A0516A375 for ; Sat, 4 Apr 2026 15:32:06 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338323; bh=Z+4BreHgcnAt94pL7cxKFQVHnhokDDJLz38Sn29f8kc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tEIyzXKSvf83jEwc+0xLiCNI87fUvlw45WlQUQNxWSx2nufB3afD9tJTj+v08imiH BmBKc1IwZsy5W8TlDS7uSiPSexMf9+IwVFnd6vbgWQLdxy1ZEP7hs4kkdsxyVYkFfT Dv2aGS2qpkXlkWCdRUlGq4IIwmedr1BjEwpy+wJ5VCySbyNTde9uN+smH4GSL/Iy5Z UVtuwBodAhcxjrXhzW+b5Ci4VZEFVkxK2e6LZYV9CHD1PzOtY/V3gAFW3H1Bc8H0hR lvgDoK4PZMDmVUiYUOgDJWQH/onhGC7LMPbHus/RIrT7iwvxAy35yA+vVx42kfVBey Nnrtv6dZ7nOxA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 978BE6A375; Sat, 4 Apr 2026 15:32: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 10026) with ESMTP id uQ4V6_B1WuAR; Sat, 4 Apr 2026 15:32:03 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338319; bh=vxEExp8fMHPWUI371ffdOqS6p86ix+XrcyUQGHoL2ig=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jFTkcyDqweG6UHuF6YTZUAe6Ad0bYDkCmXvEJeFuMuByWk7TZnGgTc5Aoopwz3ErF NhUpnzMaa0Am5llgIgvYCPKRlWJJkcQb9TR7NFfmmGTTAB3chzfa2e4XAGcbzixjqZ 4+2hfz305D0zHs3crwyOLitHF9kuRp8OjJJgT5KwB0RaV49Vt85QnOpHvthPMfaDNR th/JH/SXfWmAOQTh0XVwQBTDFqH0jCEqAKqS+MT0sI54se+kfJlrl7tsj+rO6k32iz cZSwf30mGejxkfMq36OVkYw96g7xtA4Z7kpw8Uwt3Ihj1vYJ1+toFeiIGrcllaxaaE engSdiTN1teKg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 363ED6869D; Sat, 4 Apr 2026 15:31:59 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:53 -0600 Message-ID: <20260404213020.372253-18-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: U3RGFQPX3FVJC7FVCS34F6KEPXHOPMMK X-Message-ID-Hash: U3RGFQPX3FVJC7FVCS34F6KEPXHOPMMK 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/37] patman: Add 'patchwork rm' subcommand to delete a project 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 There is no way to remove a patchwork project configuration once it is added. Add a 'patchwork rm' subcommand that deletes the project entry for a given upstream, or the default entry if no upstream is specified. Signed-off-by: Simon Glass --- tools/patman/cmdline.py | 4 ++++ tools/patman/control.py | 5 +++++ tools/patman/database.py | 20 ++++++++++++++++++++ tools/patman/test_cseries.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index 26626f5b441..3843811729e 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -173,6 +173,10 @@ def add_patchwork_subparser(subparsers): uset.add_argument( 'remote', nargs='?', help='Remote to associate with this project') + pdel = patchwork_subparsers.add_parser('rm') + pdel.add_argument( + 'remote', nargs='?', + help='Remote to delete the project for, or omit for the default') patchwork_subparsers.add_parser('ls', aliases=['list']) return patchwork diff --git a/tools/patman/control.py b/tools/patman/control.py index 61379cc108e..f1e52d7944a 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -367,6 +367,11 @@ def patchwork(args, test_db=None, pwork=None): if ups: msg += f" remote '{ups}'" print(msg) + elif args.subcmd == 'rm': + cser.db.patchwork_delete(args.remote) + cser.commit() + ups_str = f" for upstream '{args.remote}'" if args.remote else '' + tout.info(f'Deleted patchwork project{ups_str}') elif args.subcmd == 'ls': cser.project_list() else: diff --git a/tools/patman/database.py b/tools/patman/database.py index 437a05b3de0..725d13253d5 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -1041,6 +1041,26 @@ class Database: # pylint:disable=R0904 'INSERT INTO patchwork (name, proj_id, link_name, upstream) ' 'VALUES (?, ?, ?, ?)', (name, proj_id, link_name, ups)) + def patchwork_delete(self, ups): + """Delete a patchwork project configuration + + Args: + ups (str or None): Upstream name to delete, or None for the + entry with no upstream + + Raises: + ValueError: if no matching entry exists + """ + if ups is not None: + self.execute( + 'DELETE FROM patchwork WHERE upstream = ?', (ups,)) + else: + self.execute( + 'DELETE FROM patchwork WHERE upstream IS NULL') + if not self.rowcount(): + raise ValueError( + f"No patchwork project found for upstream '{ups}'") + def patchwork_get_list(self): """Get all patchwork project configurations diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 0b6e6ec9e32..96c1d62486c 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -2667,6 +2667,38 @@ Tested-by: Mary Smith # yak self.assertEqual(('Linux', 10, 'linux'), cser.db.patchwork_get('ci')) self.assertEqual(('U-Boot', 6, 'uboot'), cser.db.patchwork_get('us')) + def test_patchwork_rm(self): + """Test deleting a patchwork project configuration""" + cser = self.get_cser() + + cser.db.upstream_add('us', 'https://us.example.com') + cser.db.upstream_add('ci', 'https://ci.example.com') + cser.db.patchwork_update('U-Boot', 6, 'uboot', 'us') + cser.db.patchwork_update('Linux', 10, 'linux', 'ci') + cser.db.commit() + + # Delete by upstream name + cser.db.patchwork_delete('us') + cser.db.commit() + self.assertIsNone(cser.db.patchwork_get('us')) + self.assertEqual(('Linux', 10, 'linux'), cser.db.patchwork_get('ci')) + + # Delete non-existent raises ValueError + with self.assertRaises(ValueError): + cser.db.patchwork_delete('us') + + def test_patchwork_rm_default(self): + """Test deleting the default (no upstream) patchwork project""" + cser = self.get_cser() + + cser.db.patchwork_update('U-Boot', 6, 'uboot') + cser.db.commit() + self.assertIsNotNone(cser.db.patchwork_get()) + + cser.db.patchwork_delete(None) + cser.db.commit() + self.assertIsNone(cser.db.patchwork_get()) + def test_migrate_patchwork_upstream(self): """Test that migrating to v5 renames settings to patchwork""" db = database.Database(f'{self.tmpdir}/.patman3.db') From patchwork Sat Apr 4 21:28: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: 2133 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=1775338330; bh=GgPy3G59tRhNTe5Wta+u2/Ewl2Bl4bVWk7izWeb0j3Q=; 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=tY+LKJPQ88rlz+21zqjzPJO62RXjnUA3e4KUbDGYNtr13ywQVsNJmwWduyjhB8VnC h7L8Bra2utw+PV9ulytCsvaXla7nkOONzlj074r0vdDEwlUqWjfPtIPsXmqmrWDUmc LLwAGsxqg+ym+mSm+MfSDp4yEiRpIu28+Hltu5N0yGiIv7jMaEd7uBIecooJd9OWn9 IsmrnvJNpXV14/VHNhkbz1IZDMFT7YeTvNzztQfPehnUZbB4+5wPncIStjQGQo1Rkt rawGId6lfljWhwV8EjP51K/BZLvaGqJsicmadb74Zk/wCrNX/OBRkE33kV7hkHUAnW RP07wcx59+GzQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C0F195FC8F for ; Sat, 4 Apr 2026 15:32: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 wJq6hDnd-IiE for ; Sat, 4 Apr 2026 15:32:10 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338330; bh=GgPy3G59tRhNTe5Wta+u2/Ewl2Bl4bVWk7izWeb0j3Q=; 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=tY+LKJPQ88rlz+21zqjzPJO62RXjnUA3e4KUbDGYNtr13ywQVsNJmwWduyjhB8VnC h7L8Bra2utw+PV9ulytCsvaXla7nkOONzlj074r0vdDEwlUqWjfPtIPsXmqmrWDUmc LLwAGsxqg+ym+mSm+MfSDp4yEiRpIu28+Hltu5N0yGiIv7jMaEd7uBIecooJd9OWn9 IsmrnvJNpXV14/VHNhkbz1IZDMFT7YeTvNzztQfPehnUZbB4+5wPncIStjQGQo1Rkt rawGId6lfljWhwV8EjP51K/BZLvaGqJsicmadb74Zk/wCrNX/OBRkE33kV7hkHUAnW RP07wcx59+GzQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AE33F6833B for ; Sat, 4 Apr 2026 15:32:10 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338329; bh=9YfZSLj1WTbIhbXMrPoo3Bh0U5RWd9fuoakQ3nkmiFo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kMOoY5f4jPceGopmQI4xaEF3j5oR5ICTusRO5dJX0ebCAbcSX4rxlHXymetnXjyeH 1/mBPnX/cEFivp45NRFBmNjl3LM2ToFumFZHkrQjTpMSV7emfhgN7hAZDwlZfuweBd lhJ3ehPFarxkFEfHjRNe03SvzN9AYqTy++HQEm+Q3xtsUx4TC+YvsyuhuZTYObzEaV 29PAORxsjXEbBnos4eiIehqLy9DRw2hYk3jsJE7pFzwp5Hu115YulQjqI/Q/N56bKL gDg5CONX2GWfVX8YVhbVIMgWaA1/ckt7YrK1kSoYepvScCD56AhzVMn39TEGpYChm+ 2NsV0g7uNtdjw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0799C5FC8F; Sat, 4 Apr 2026 15:32:09 -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 eo082Puz5VQH; Sat, 4 Apr 2026 15:32:08 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338324; bh=u0gOUIRxLaO33wwXM6FOMENHDE+GNj+MuOsqaZiRZkM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BkG9eBy/IqcSYvPivwYymF9JY2pPbnx7JOqbOmz7I0KynOn4a/NMR9eU8NzrI/pOT qgUyPhI/x7tyKlslmYEG9Cclou9mvEdsNwiQRdL8UdZD46/C7EYFAklSzQuJoYxgB7 GFY1pcuvFSqtkka0DfcrAIikwn/6mRtt4WGIob2WKLh1GHck8aivOBO6U8evKoDgtx zZvJ11mASVhbm+H09SY+AQwWaMTdQie//s5RVj3bAaGxWima040Ye/7D0o5+QjEMPq 1PYx3Dhnv4xjY9uT83NNIVk1Kp6P4sAWZQo32NK6maCt8e1uZTSGjPQa3eHExwGvij 7KyEp2Ig4HhZA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id AAB1D6869D; Sat, 4 Apr 2026 15:32:04 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:54 -0600 Message-ID: <20260404213020.372253-19-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: MZTHZKCOQR3EP6IPIX6AGINE4FSQTQSO X-Message-ID-Hash: MZTHZKCOQR3EP6IPIX6AGINE4FSQTQSO 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/37] patman: Add schema v8 with review tracking support 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 database support for tracking AI-assisted reviews of other people's patch series: - Add 'source' column to the series table to distinguish review series (source='review') from the user's own series (source=NULL) - Add 'review' table to store AI review results per patch, including the review body text, approval status and timestamp - Add helper methods for storing and retrieving reviews, including review_get_previous() for loading context from prior versions - Add series_find_by_link() and series_find_review_by_name() to detect when re-reviewing the same series or a new version of a previously reviewed series Signed-off-by: Simon Glass --- tools/patman/database.py | 121 ++++++++++++++++++++++++++++++++++- tools/patman/test_cseries.py | 2 +- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/tools/patman/database.py b/tools/patman/database.py index 725d13253d5..7f33137d0b7 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -2,6 +2,7 @@ # # Copyright 2025 Simon Glass # +# pylint: disable=C0302 """Handles the patman database This uses sqlite3 with a local file. @@ -19,7 +20,12 @@ from u_boot_pylib import tout from patman.series import Series # Schema version (version 0 means there is no database yet) -LATEST = 7 +LATEST = 8 + +# Information about a review record +Review = namedtuple( + 'REVIEW', + 'idnum,svid,seq,body,approved,timestamp') # Information about a series/version record SerVer = namedtuple( @@ -223,6 +229,20 @@ class Database: # pylint:disable=R0904 self.cur.execute( 'ALTER TABLE workflow ADD COLUMN ser_ver_id INTEGER') + def _migrate_to_v8(self): + """Add review tracking and series source type + + - Add source column to series table (NULL for user's own series, + 'review' for series being reviewed) + - Add review table for storing AI review results per patch + """ + self.cur.execute('ALTER TABLE series ADD COLUMN source') + self.cur.execute( + 'CREATE TABLE review (id INTEGER PRIMARY KEY AUTOINCREMENT,' + 'svid INTEGER, seq INTEGER, body TEXT, approved BIT, ' + 'timestamp TEXT, ' + 'FOREIGN KEY (svid) REFERENCES ser_ver (id))') + def migrate_to(self, dest_version): """Migrate the database to the selected version @@ -255,6 +275,8 @@ class Database: # pylint:disable=R0904 self._migrate_to_v6() elif version == 7: self._migrate_to_v7() + elif version == 8: + self._migrate_to_v8() # Save the new version if we have a schema_version table if version > 1: @@ -842,6 +864,7 @@ class Database: # pylint:disable=R0904 # upstream functions + # pylint: disable=R0913 def upstream_add(self, name, url, patchwork_url=None, identity=None, series_to=None, no_maintainers=False, no_tags=False): """Add a new upstream record @@ -1200,3 +1223,99 @@ class Database: # pylint:disable=R0904 query += ' ORDER BY w.timestamp' res = self.execute(query) return res.fetchall() + + # pylint: disable=R0913 + def review_add(self, svid, seq, body, approved, timestamp): + """Add a review record + + Args: + svid (int): ser_ver ID num + seq (int): Patch sequence (0 for cover, 1..N for patches) + body (str): Review email body text + approved (bool): True if Reviewed-by was given + timestamp (str): ISO datetime string + + Return: + int: ID num of the new review record + """ + self.execute( + 'INSERT INTO review (svid, seq, body, approved, timestamp) ' + 'VALUES (?, ?, ?, ?, ?)', + (svid, seq, body, 1 if approved else 0, timestamp)) + return self.lastrowid() + + def review_get_for_version(self, svid): + """Get review records for a given series version + + Args: + svid (int): ser_ver ID num + + Return: + list of Review: Review records ordered by sequence + """ + res = self.execute( + 'SELECT id, svid, seq, body, approved, timestamp ' + 'FROM review WHERE svid = ? ORDER BY seq', (svid,)) + return [Review(*row) for row in res.fetchall()] + + def review_get_previous(self, series_id, version): + """Get reviews from the previous version of a series + + Looks up the ser_ver for version-1 and returns its reviews, so + they can be provided as context when reviewing a new version. + + Args: + series_id (int): Series ID + version (int): Current version being reviewed + + Return: + list of Review: Reviews from version-1, or empty list + """ + prev_version = version - 1 + if prev_version < 1: + return [] + res = self.execute( + 'SELECT sv.id FROM ser_ver sv ' + 'WHERE sv.series_id = ? AND sv.version = ?', + (series_id, prev_version)) + row = res.fetchone() + if not row: + return [] + return self.review_get_for_version(row[0]) + + def series_find_by_link(self, link): + """Find a series by its patchwork link + + Args: + link (str): Patchwork series link/ID + + Return: + tuple or None: (series_id, name, version, svid) if found + """ + res = self.execute( + 'SELECT s.id, s.name, sv.version, sv.id ' + 'FROM ser_ver sv ' + 'JOIN series s ON sv.series_id = s.id ' + 'WHERE sv.link = ?', (str(link),)) + return res.fetchone() + + def series_find_review_by_name(self, name): + """Find a review series by its name + + 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. + + Args: + name (str): Series name to search for + + Return: + tuple or None: (series_id, name, max_version) if found + """ + res = self.execute( + '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 = ? " + 'GROUP BY s.id', (name,)) + return res.fetchone() diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 96c1d62486c..5661e13e2e9 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -3589,7 +3589,7 @@ Date: .* self.assertEqual(f'Update database to v{version}', out.getvalue().strip()) self.assertEqual(version, db.get_schema_version()) - self.assertEqual(7, database.LATEST) + self.assertEqual(8, database.LATEST) def test_migrate_future_version(self): """Test that a database newer than patman is rejected""" From patchwork Sat Apr 4 21:28: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: 2134 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=1775338335; bh=pJ2CMaFj5N83dfxhRnrP1834TjdyghVkCph3g3utCwo=; 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=k8i0c874Vn6G5H6Q43n2k3WZ/LnZgqKpNQw+Q4/Dg+M+FC+qSMdru+eW2UYq3RB24 WYga3VWQttEuCCT8roSUaIf2keNRaUDs3oWqJPc8QyKCfz67XcxoCOIUbrxa4Nf5GN 87Oasg1ZwNUbTv28EBAJqGskTyhHHxuhFM71jhEyGXQjHBFSdcWW7NtvQJEY1K7oAy aX7hQTNslmIA+CzX3K/yyKifeOZsS9opnpRBHxqsGrbE99wTw2Tvbtnde3COqTs3TJ 44pHWJvYIhbpulNXUz5kd8B3Cz3EooghajKjkUBbmlsfzDoj++AOUNrTZ/180b05YQ 51J1GQv8P8S+g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 675365E7B4 for ; Sat, 4 Apr 2026 15:32: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 10024) with ESMTP id v_fndTQjv15P for ; Sat, 4 Apr 2026 15:32:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338335; bh=pJ2CMaFj5N83dfxhRnrP1834TjdyghVkCph3g3utCwo=; 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=k8i0c874Vn6G5H6Q43n2k3WZ/LnZgqKpNQw+Q4/Dg+M+FC+qSMdru+eW2UYq3RB24 WYga3VWQttEuCCT8roSUaIf2keNRaUDs3oWqJPc8QyKCfz67XcxoCOIUbrxa4Nf5GN 87Oasg1ZwNUbTv28EBAJqGskTyhHHxuhFM71jhEyGXQjHBFSdcWW7NtvQJEY1K7oAy aX7hQTNslmIA+CzX3K/yyKifeOZsS9opnpRBHxqsGrbE99wTw2Tvbtnde3COqTs3TJ 44pHWJvYIhbpulNXUz5kd8B3Cz3EooghajKjkUBbmlsfzDoj++AOUNrTZ/180b05YQ 51J1GQv8P8S+g== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4F96C5FC8F for ; Sat, 4 Apr 2026 15:32:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338334; bh=wBCwqxssqXjTo1GhE1UZAhvvczHYs0pvvAS9hBhmKyk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CqLHibtghRd2i5HoLIXP3UxMajUivCu4izM0qC9Cy9OLVxejyhwYd0mStPgBrQxcs QvALZR1tDmH9+IBoAV5DvUhvgKt8hwJij3V8PhIsQwnWVNy6eYaDrWuLvGvHrgzcWh 3kumtVBcI/1m5YbJRrEJUV8usu+Kslv/T3XxturxCunk30kf77Y0l1NMJ9erUmNSvx RIqNTWbCtPGpyqxpmfq/nWJ2DPd4R+qO54dqLUty117ReQHLe8N5Egy6VMoQw5pjbM O0peDi+PKBiMybxUNroU5Apw6TN3jtpUCLnkGiIvYukp+vWZZ2d0aufgQ6QVR5jzcr KeL1Vg7EgbASA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3E98D5FC8F; Sat, 4 Apr 2026 15:32: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 10026) with ESMTP id 1h29St047wso; Sat, 4 Apr 2026 15:32:14 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338330; bh=/7x814i92P3LJIC1PPPZapJYivgNWIPmUHdLXD3n7eM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eiWIQvAcPTr16Ey5NULbZLETg4+1qx3skHOrOjlMxRauuJsqWdaFmEXIsanDefyNL Iz5EwymYC0ycESpGF0dEhJbO65ljM5ASo3pJqwPhAs5b+l1uNF8bVksT/Mpbr4Y3ZK IOmdrrzxk6TWNsEe/I+hR0KrMrNX06h7dzQKmO6rJ3G8P2OKgYXPXcDcZPokVScBAp /uxIssIEqXRKu+zd9Md3SzwOyuacFxDrpm967y3iHYFaWgLZgmPKEP4bD1IOJ30LtZ IOkQ9aqgK1VHpuO3s+Fqj0LIl98qUJPVYXl1BoVukibinNEZb9uNTnwqT6B+8dlaD4 GSGth4YSa609A== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 220915E7B4; Sat, 4 Apr 2026 15:32:10 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:55 -0600 Message-ID: <20260404213020.372253-20-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: IHG22EYVEDLUP2KP3ZDKTVXY25BLXUNT X-Message-ID-Hash: IHG22EYVEDLUP2KP3ZDKTVXY25BLXUNT 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/37] patman: Fix pylint warnings in control.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 Move local imports to the top level, group patman imports together, convert a %-format string to f-string, fix a bare 'print' statement that has no effect, and suppress unavoidable warnings for functions with many branches/arguments. Signed-off-by: Simon Glass --- tools/patman/control.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/tools/patman/control.py b/tools/patman/control.py index f1e52d7944a..87598622eb7 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -17,14 +17,17 @@ except ImportError: # for Python 3.6 import importlib_resources as resources +from patman import cseries +from patman import patchstream +from patman import send +from patman import settings +from patman import status +from patman import workflow +from patman.patchwork import Patchwork from u_boot_pylib import gitutil from u_boot_pylib import terminal from u_boot_pylib import tools from u_boot_pylib import tout -from patman import patchstream -from patman.patchwork import Patchwork -from patman import send -from patman import settings def setup(): @@ -45,6 +48,7 @@ def do_send(args): send.send(args) +# pylint: disable=R0913 def patchwork_status(branch, count, start, end, dest_branch, force, show_comments, url, single_thread=False): """Check the status of patches in patchwork @@ -81,11 +85,11 @@ def patchwork_status(branch, count, start, end, dest_branch, force, warnings = 0 for cmt in series.commits: if cmt.warn: - print('%d warnings for %s:' % (len(cmt.warn), cmt.hash)) + print(f'{len(cmt.warn)} warnings for {cmt.hash}:') for warn in cmt.warn: print('\t', warn) warnings += 1 - print + print() if warnings: raise ValueError('Please fix warnings before running status') links = series.get('links') @@ -103,9 +107,6 @@ def patchwork_status(branch, count, start, end, dest_branch, force, url = series.patchwork_url pwork = Patchwork(url, single_thread=single_thread) - # Import this here to avoid failing on other commands if the dependencies - # are not present - from patman import status pwork = Patchwork(url) status.check_and_show_status(series, link, branch, dest_branch, force, show_comments, False, pwork) @@ -159,6 +160,7 @@ def _setup_patchwork(cser, pwork, ups, pw_url): return pwork +# pylint: disable=R0912,R0915 def do_series(args, test_db=None, pwork=None, cser=None): """Process a series subcommand @@ -170,8 +172,6 @@ def do_series(args, test_db=None, pwork=None, cser=None): needed cser (Cseries): Cseries object to use, None to create one """ - from patman import cseries - if not cser: cser = cseries.Cseries(test_db) needs_patchwork = [ @@ -266,6 +266,7 @@ def do_series(args, test_db=None, pwork=None, cser=None): cser.close_database() +# pylint: disable=R0912 def upstream(args, test_db=None): """Process an 'upstream' subcommand @@ -274,8 +275,6 @@ def upstream(args, test_db=None): test_db (str or None): Directory containing the test database, None to use the normal one """ - from patman import cseries - cser = cseries.Cseries(test_db) try: cser.open_database() @@ -324,6 +323,7 @@ def upstream(args, test_db=None): cser.close_database() +# pylint: disable=R0912 def patchwork(args, test_db=None, pwork=None): """Process a 'patchwork' subcommand Args: @@ -332,8 +332,6 @@ def patchwork(args, test_db=None, pwork=None): use the normal one pwork (Patchwork): Patchwork object to use """ - from patman import cseries - cser = cseries.Cseries(test_db) try: cser.open_database() @@ -387,9 +385,6 @@ def do_workflow(args, test_db=None): test_db (str or None): Directory containing the test database, None to use the normal one """ - from patman import cseries - from patman import workflow - cser = cseries.Cseries(test_db) try: cser.open_database() @@ -408,6 +403,7 @@ def do_workflow(args, test_db=None): cser.close_database() +# pylint: disable=R0912 def do_patman(args, test_db=None, pwork=None, cser=None): """Process a patman command @@ -458,7 +454,7 @@ def do_patman(args, test_db=None, pwork=None, cser=None): patchwork(args, test_db, pwork) elif args.cmd == 'workflow': do_workflow(args, test_db) - except Exception as exc: + except Exception as exc: # pylint: disable=W0718 terminal.tprint(f'patman: {type(exc).__name__}: {exc}', colour=terminal.Color.RED) if args.debug: From patchwork Sat Apr 4 21:28: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: 2135 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=1775338341; bh=V1DOrbx33rsziM3GcrxzPI6za9gGVPNg3uXilQFxyi0=; 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=BjZWVZN0bEJ1ZAg/V5t4xUsj6nk8KwWX7M86pPrthzcpf5h1KoivH7gppx9eRf53R sCSlu26Rst8uOOxW7CDLiUkWfvgC8wTUT0VN6nlFIUQ9AkA7jBrIicYr4aYOsM0v7+ R2C6GsZF2iO0JPy6Dfgzr6d90FH5nT/sfHTbQ8hY2Dlxdje2L5sB+q1RXSvvzAPGEx CTeDGBxstJ4efMEV6LeS19UUu+HlgNNQcMK0syR8FEzNXUxfFRRUiX+TbSiT1DQeO7 2ExxYdJesQvK4bytSKLbomSLQZOIRcLYEK2LiKR756NbqqK7UJsaO8TxEflNVrkyGc ZL+Y81lLSVgeg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 33FDD5FC8F for ; Sat, 4 Apr 2026 15:32: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 10024) with ESMTP id 7HZ99GZyLytK for ; Sat, 4 Apr 2026 15:32:21 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338341; bh=V1DOrbx33rsziM3GcrxzPI6za9gGVPNg3uXilQFxyi0=; 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=BjZWVZN0bEJ1ZAg/V5t4xUsj6nk8KwWX7M86pPrthzcpf5h1KoivH7gppx9eRf53R sCSlu26Rst8uOOxW7CDLiUkWfvgC8wTUT0VN6nlFIUQ9AkA7jBrIicYr4aYOsM0v7+ R2C6GsZF2iO0JPy6Dfgzr6d90FH5nT/sfHTbQ8hY2Dlxdje2L5sB+q1RXSvvzAPGEx CTeDGBxstJ4efMEV6LeS19UUu+HlgNNQcMK0syR8FEzNXUxfFRRUiX+TbSiT1DQeO7 2ExxYdJesQvK4bytSKLbomSLQZOIRcLYEK2LiKR756NbqqK7UJsaO8TxEflNVrkyGc ZL+Y81lLSVgeg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 218E36833B for ; Sat, 4 Apr 2026 15:32:21 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338339; bh=Tn33w8C+9ufzXyqjnNvXdDnD/23OUHVQTnuT/TU5o4Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oOt2p3l52sf9AlJZTwIVpCz+IOZKsj2379nulhc1YR46nJl2gs1xJpaffJCyMEg8o wT62OIzohbSYDHuzxWAgXRo0wLy3JNJVUZTFA78WaIshIvaxZvgwbgUdLISbnkybrn kzxQuo1QhxEnTu20+9mmdjrAIJY0HO2YmiiPaLdu85vf3VvJwwtfJHUDGni4ftOSF+ B8NJO9UeejOW+tOAO19yHQKC2mI1Oa+OqSAHD3Xo1m6FnGPk5/1jYXNciueXpabmkI 6CTm8MGwvBeAkHuKYHrmSwATQh48IgC83h3+Wx4B3MNoEw9wvmi+EPmqu9zBwJe/cy HFq2x2uV4PQ1A== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B0ED65E7B4; Sat, 4 Apr 2026 15:32: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 10026) with ESMTP id r90ShjJB5wpO; Sat, 4 Apr 2026 15:32:19 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338335; bh=liX7xR7mc4/0e6iHbYIc65eJbaA5Bo+hEtIU3OlRjF8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CP9ul5J6ltkHvylu4WA6hNVYEzddhrWxWdarxEASuR+mJ3AEZv5DcAW9LWZN48twF 1pjg/3nRL6kfzkrme/MJipharUVq3/KaMNSp800FeW/r62IBIBVa2R5TEz3IRS4oAX X9VibCUTMdWbXjZuvTQYflWPXR8Dt0qV5k8YEL1kfkUTGiAsinb1Cjmuj2RSJbSAgG oLuneAU905v6JdNQCAPvqupbUZ1iqsC3ExJiqkgh6WYyno1SOFXYfR81SAspVCVLUw iRSrchgYGRU9NmzK+ozBO3ktP/LW4y41FinrLPdXPntPH4cIdXyFqKndZ2h/yaQKr2 KeJ8AOrRu0+mA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 54B3D6833B; Sat, 4 Apr 2026 15:32:15 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:56 -0600 Message-ID: <20260404213020.372253-21-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: UVMG4APUSKIZUKPOEVQOJ36ZTC2VXK7E X-Message-ID-Hash: UVMG4APUSKIZUKPOEVQOJ36ZTC2VXK7E 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 20/37] patman: Move link resolution from control.py into status.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 The patchwork_status() function in control.py handles both series metadata parsing and patchwork link resolution. The latter belongs in status.py alongside the other status-checking logic. Extract the link resolution, URL override and Patchwork construction into a new find_link_and_show_status() function in status.py. This also fixes a bug where Patchwork() is constructed twice, with the second call dropping the single_thread parameter. Signed-off-by: Simon Glass --- tools/patman/control.py | 21 +++++---------------- tools/patman/status.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/tools/patman/control.py b/tools/patman/control.py index 87598622eb7..1c1b998ddd6 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -54,14 +54,14 @@ def patchwork_status(branch, count, start, end, dest_branch, force, """Check the status of patches in patchwork This finds the series in patchwork using the Series-link tag, checks for new - comments and review tags, displays then and creates a new branch with the + comments and review tags, displays them and creates a new branch with the review tags. Args: branch (str): Branch to create patches from (None = current) count (int): Number of patches to produce, or -1 to produce patches for the current branch back to the upstream commit - start (int): Start partch to use (0=first / top of branch) + start (int): Start patch to use (0=first / top of branch) end (int): End patch to use (0=last one in series, 1=one before that, etc.) dest_branch (str): Name of new branch to create with the updated tags @@ -96,20 +96,9 @@ def patchwork_status(branch, count, start, end, dest_branch, force, if not links: raise ValueError("Branch has no Series-links value") - _, version = patchstream.split_name_version(branch) - link = series.get_link_for_version(version, links) - if not link: - raise ValueError(f'Series-links has no link for v{version}') - tout.debug(f"Link '{link}") - - # Allow the series to override the URL - if 'patchwork_url' in series: - url = series.patchwork_url - pwork = Patchwork(url, single_thread=single_thread) - - pwork = Patchwork(url) - status.check_and_show_status(series, link, branch, dest_branch, force, - show_comments, False, pwork) + status.find_link_and_show_status( + series, branch, url, dest_branch, force, show_comments, False, + single_thread) def _setup_patchwork(cser, pwork, ups, pw_url): diff --git a/tools/patman/status.py b/tools/patman/status.py index 2828e4a3bfc..8673eeb697e 100644 --- a/tools/patman/status.py +++ b/tools/patman/status.py @@ -381,6 +381,44 @@ async def check_status(link, pwork, read_comments=False, read_cover_comments) +def find_link_and_show_status(series, branch, url, dest_branch, force, + show_comments, show_cover_comments, + single_thread=False): + """Find the patchwork link for a series and show its status + + Resolves the patchwork link from the series metadata, then checks + and displays the review status. + + Args: + series (Series): Series object for the existing branch + branch (str): Branch name (used to determine the version) + url (str): Patchwork server URL. Overridden by Series-patchwork-url + if present in the series. + dest_branch (str): Name of new branch to create, or None + force (bool): True to force overwriting dest_branch if it exists + show_comments (bool): True to show comments on each patch + show_cover_comments (bool): True to show cover letter comments + single_thread (bool): True to use a single thread for patchwork + """ + from patman import patchstream + from patman.patchwork import Patchwork + from u_boot_pylib import tout + + _, version = patchstream.split_name_version(branch) + links = series.get('links') + link = series.get_link_for_version(version, links) + if not link: + raise ValueError(f'Series-links has no link for v{version}') + tout.debug(f"Link '{link}") + + if 'patchwork_url' in series: + url = series.patchwork_url + pwork = Patchwork(url, single_thread=single_thread) + + check_and_show_status(series, link, branch, dest_branch, force, + show_comments, show_cover_comments, pwork) + + def check_and_show_status(series, link, branch, dest_branch, force, show_comments, show_cover_comments, pwork, test_repo=None): From patchwork Sat Apr 4 21:28: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: 2136 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=1775338346; bh=tsY7cG4DR76lg3pWdxC9k9ipGo92S3/3PwBQI9paYLg=; 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=Eb3lijb1zrjBkxYgUOjjguaPY0BuKvnG1As60ua9V/V5zvMWa+Z2h1UvUBTIC3sgU He39aJTJPkOgfd8B398VvALjh9Jxu70DFEZC5gYiRwmixC8v3SXEYWg6L7gX/n+ZDA 3GKQ7E+sZvwSxx7BpWSSnftVCtVJsaNgQ3De5sywu/lgLwQmjaoEbpIoByg+XEdFJU jOJAjR+phs71I6w3Fcpk2h0eT+cliyoly/tfDq30ha4mZ5owAWFaXMGEhmfAQYFlK+ orOaDt9KBz0o0r4Uw8P5ENE2+BIFl5QwK8Mwh7KnyfI+s+aVu9E2Zs+hw+KPBClR6W w/WMCL7V2kf9g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B542E6878B for ; Sat, 4 Apr 2026 15:32: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 10024) with ESMTP id WujLiiHlWNRA for ; Sat, 4 Apr 2026 15:32:26 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338346; bh=tsY7cG4DR76lg3pWdxC9k9ipGo92S3/3PwBQI9paYLg=; 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=Eb3lijb1zrjBkxYgUOjjguaPY0BuKvnG1As60ua9V/V5zvMWa+Z2h1UvUBTIC3sgU He39aJTJPkOgfd8B398VvALjh9Jxu70DFEZC5gYiRwmixC8v3SXEYWg6L7gX/n+ZDA 3GKQ7E+sZvwSxx7BpWSSnftVCtVJsaNgQ3De5sywu/lgLwQmjaoEbpIoByg+XEdFJU jOJAjR+phs71I6w3Fcpk2h0eT+cliyoly/tfDq30ha4mZ5owAWFaXMGEhmfAQYFlK+ orOaDt9KBz0o0r4Uw8P5ENE2+BIFl5QwK8Mwh7KnyfI+s+aVu9E2Zs+hw+KPBClR6W w/WMCL7V2kf9g== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A454E6833B for ; Sat, 4 Apr 2026 15:32:26 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338345; bh=xNr9JcFOFf1FxdaEIZlKZ2UCmSXgrJKNxdUqLgRF9oA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jIbTp83GnjRFH7z5+2GOTn1RzVjCHtmZ6OVQj9a1nbXwHNA/OaxrrXLxh0udiZWIF XQn8StU9SBxAUu6oV5XyPtxHPbZNBdKKYcyXPLEYw6+7TSxYsoR4c+53Bkfhdvo5Dr 8y+mm/bS8Kv1L2x8/JaCihKdM5PVDA2h6ghvpuBHxuqDlGfLkR2QZgkbNHk03ut8ap b+XG2hdejIWt75boFfV7m2vHH2IdY9ALf6g/cssAXU9wSzPqZZ5OM/U4haTPcdx82q 9ioZ+bsQCXtzuHKdEzixMr0IsHFasljC2vuN3lieu2bN6ZQBdwb75BlF7ICVoLSbHV x1G6vlTs5VxVw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2BF305FC8F; Sat, 4 Apr 2026 15:32:25 -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 4yU2GQc_nSdk; Sat, 4 Apr 2026 15:32:25 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338341; bh=UBXdU9aglhD3+0PPzYnrTANk/LMKZ1jU0kAmQMsJTCQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UOtVyGHA+O60DwXoMtXMet0WT+HY99g20RpbKd2ZNhpc0hCaNDfLPUl0WgDapqOpI wENDfsMs2Hgxgi1xnbO4e5kcO1hWWlqVPWBm1m1GRg0oNLVcQ7XQ2v+FerH6r2vgDQ ElTsmetw19lifxghdwxEeYq/PEhnSXQKmSGLplQESeugpgHhoUaLKtkJiWTU1Yi3+c +Gj8aZSEs0qxj0Rw6Lb93yI7TUHkoENbH/sPpkPZE0xe5dd4wBQB3ucvA/Ni9KKZIR /BRqiOoFyCsuyxG19t3L7WkUCplgNHk1QAvYuOCsTQ7Y+O4TEspvtOz8LL4RxoHEE5 hD3Lav4sgeTtg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id C4F015E7B4; Sat, 4 Apr 2026 15:32:20 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:57 -0600 Message-ID: <20260404213020.372253-22-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: LMXNQHTZC56HLJN43PIXOV3XNHM7GDZO X-Message-ID-Hash: LMXNQHTZC56HLJN43PIXOV3XNHM7GDZO 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 21/37] patman: Fall back to database for patchwork link in status 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 'patman status' cannot find a link for the current version in the commit's Series-links tag, look it up in the database. This handles the case where autolink runs without updating the commit, or the commit has not yet been rewritten. Signed-off-by: Simon Glass --- tools/patman/status.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tools/patman/status.py b/tools/patman/status.py index 8673eeb697e..205314aef3e 100644 --- a/tools/patman/status.py +++ b/tools/patman/status.py @@ -408,7 +408,26 @@ def find_link_and_show_status(series, branch, url, dest_branch, force, links = series.get('links') link = series.get_link_for_version(version, links) if not link: - raise ValueError(f'Series-links has no link for v{version}') + # Fall back to the database if the commit metadata does not + # have a link for this version (e.g. autolink ran without -u) + from patman import cseries + + cser = cseries.Cseries() + try: + cser.open_database() + name, _ = patchstream.split_name_version(branch) + ser = cser.get_series_by_name(name) + if ser: + svinfo = cser.get_ser_ver(ser.idnum, version) + if svinfo: + link = svinfo.link + except (ValueError, AttributeError): + pass + finally: + cser.close_database() + if not link: + raise ValueError(f'No patchwork link for v{version}; ' + 'try: patman series autolink') tout.debug(f"Link '{link}") if 'patchwork_url' in series: From patchwork Sat Apr 4 21:28: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: 2137 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=1775338352; bh=bPK/i5DHDRrkoSpCQLMi+1yaEfv8+EVjDvYbEHtnit4=; 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=lrvheMBBE/V6wciJVZ3QDiFX5zurockrxvjfpsIDFVVrcB7Ma9r4eMZC6z4+oPyGG haKAiweaMqKWOBM/O6j6Gf7HhMMaa9sVP3k4w7Cewi1yQCPWk6r0+ALy1QUXy3GwgX 9CtiwkKOzUYHDjx3tnATWTkffrHENocenZMsUKLAnOwXXajdY6cHnyPMyI8XgwUcUh 0MO3GShhXvLCK19Es2JOh2FGPBP2aHy+jenkIXCUwyk+9rC8WEMkM5AzpoqH9f19vV 3lgYqKIYpVQkNTfmaxwYn5cdQlSBRlCwZJdf51WrfzZQBbYx8PJrtfjIWLjqBzNaNX HTU4Pk35rD0yg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 741725FC8F for ; Sat, 4 Apr 2026 15:32:32 -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 9CsP5vHcbMBv for ; Sat, 4 Apr 2026 15:32:32 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338352; bh=bPK/i5DHDRrkoSpCQLMi+1yaEfv8+EVjDvYbEHtnit4=; 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=lrvheMBBE/V6wciJVZ3QDiFX5zurockrxvjfpsIDFVVrcB7Ma9r4eMZC6z4+oPyGG haKAiweaMqKWOBM/O6j6Gf7HhMMaa9sVP3k4w7Cewi1yQCPWk6r0+ALy1QUXy3GwgX 9CtiwkKOzUYHDjx3tnATWTkffrHENocenZMsUKLAnOwXXajdY6cHnyPMyI8XgwUcUh 0MO3GShhXvLCK19Es2JOh2FGPBP2aHy+jenkIXCUwyk+9rC8WEMkM5AzpoqH9f19vV 3lgYqKIYpVQkNTfmaxwYn5cdQlSBRlCwZJdf51WrfzZQBbYx8PJrtfjIWLjqBzNaNX HTU4Pk35rD0yg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 61EE36833B for ; Sat, 4 Apr 2026 15:32:32 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338350; bh=rPLSwFHWlHYr6YIpIy4M4BXAQu+pD5wmFzJp5ENZnBA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ppRcWs2yLcur3+9bh5qWZOo/jHwjR8x61iOYnZ0YMmLJjw31tWN7AyMysFSXLCPLt HpLIevHgWlBYRiUqyXAuAD3vlWElQK5P0NXHKTF4flGSd+6pZUbuJc1cHwA12at/h1 r8BMu8ZwlP3fk7sjM2gQz96iKeYVqmGGYVYrc5s3NGBhht22vF4PlniYpDZLcXlNO5 VTkycodWkNLOFlRL0hOjbFN/xC9JHyuJYMSbaVRxXNabxuVCUR5V6TWY7k7fb6H9cW sJ7x+Zf/zc/+c0vI1GvYL2xMfwyWuFYCTmbIZrMIVkyEcnCgVAPD1ds5vnBBUydho8 78LdH6mW6DpIg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A1B565FC8F; Sat, 4 Apr 2026 15:32: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 10026) with ESMTP id vBUJ9xHwDYJ6; Sat, 4 Apr 2026 15:32:30 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338346; bh=bEpJToIk9mSTcnio3S8XbaXO1EwA2ejolpEM3G4j2rA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=G6JuWfZ1Cd+3UGZoR3Nik3bPf5WJdtu0isw4KKnx2h1XTTckawlrj3iq/zubj9kvx knBjLqzEWVBAD0pwBaF8jFeQFH70W4dYexee3h11FSmK2WYJMj/SS9yb+pXzbQjhWS NOzIcn8zWYFvBU/PXgABFVYl21gOV55BxKpFO/PLet/woinYgh29zxyQlwvyI++qLE Vl5RarHawbvk1VQXXfyMfzHGlKrHRSPjrA51HhyFEGv9+X1BbuRkMPJ1ZzcMX+61B+ IQaGtqp8tHJLQr9zLEqqhXBmjx7uwKdrMvKQkYuGXga1OeyDqoTiRUCrYwNC4Pns1M HTbLL8pq0M3nA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 3EA4B5E7B4; Sat, 4 Apr 2026 15:32:26 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:58 -0600 Message-ID: <20260404213020.372253-23-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 6ZCE764IZOHOMV3XWVCP4PBDCPKZQBIL X-Message-ID-Hash: 6ZCE764IZOHOMV3XWVCP4PBDCPKZQBIL 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 22/37] patman: Default autolink to updating the branch commit 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 Autolink stores the patchwork link in the database but does not update the Series-links tag in the commit unless -u is passed. This means 'patman status' cannot find the link since it reads from commit metadata. Change autolink and autolink-all to default to updating the commit, with --no-update to opt out. Update tests to match the new default. Signed-off-by: Simon Glass --- tools/patman/cmdline.py | 10 ++++++++-- tools/patman/test_cseries.py | 15 ++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index 3843811729e..3c12200949f 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -260,7 +260,10 @@ def add_series_subparser(subparsers): auto = series_subparsers.add_parser('autolink', aliases=ALIASES['autolink']) - _add_update(auto) + auto.add_argument('-u', '--update', action='store_true', default=True, + help='Update the branch commit (default)') + auto.add_argument('--no-update', action='store_false', dest='update', + help='Do not update the branch commit') _add_wait(auto, 0) aall = series_subparsers.add_parser('autolink-all') @@ -268,7 +271,10 @@ def add_series_subparser(subparsers): help='Link all series versions, not just the latest') aall.add_argument('-r', '--replace-existing', action='store_true', help='Replace existing links') - _add_update(aall) + aall.add_argument('-u', '--update', action='store_true', default=True, + help='Update the branch commits (default)') + aall.add_argument('--no-update', action='store_false', dest='update', + help='Do not update the branch commits') series_subparsers.add_parser('dec') diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 5661e13e2e9..3b78399d907 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -1402,7 +1402,7 @@ Tested-by: Mary Smith # yak with (mock.patch.object(cseries.Cseries, 'link_auto_all', return_value=None) as method): self.run_args('series', 'autolink-all', pwork=True) - method.assert_called_once_with(True, update_commit=False, + method.assert_called_once_with(True, update_commit=True, link_all_versions=False, replace_existing=False, dry_run=False, show_summary=True) @@ -1410,7 +1410,7 @@ Tested-by: Mary Smith # yak with (mock.patch.object(cseries.Cseries, 'link_auto_all', return_value=None) as method): self.run_args('series', 'autolink-all', '-a', pwork=True) - method.assert_called_once_with(True, update_commit=False, + method.assert_called_once_with(True, update_commit=True, link_all_versions=True, replace_existing=False, dry_run=False, show_summary=True) @@ -1418,7 +1418,7 @@ Tested-by: Mary Smith # yak with (mock.patch.object(cseries.Cseries, 'link_auto_all', return_value=None) as method): self.run_args('series', 'autolink-all', '-a', '-r', pwork=True) - method.assert_called_once_with(True, update_commit=False, + method.assert_called_once_with(True, update_commit=True, link_all_versions=True, replace_existing=True, dry_run=False, show_summary=True) @@ -1426,22 +1426,23 @@ Tested-by: Mary Smith # yak with (mock.patch.object(cseries.Cseries, 'link_auto_all', return_value=None) as method): self.run_args('series', '-n', 'autolink-all', '-r', pwork=True) - method.assert_called_once_with(True, update_commit=False, + method.assert_called_once_with(True, update_commit=True, link_all_versions=False, replace_existing=True, dry_run=True, show_summary=True) with (mock.patch.object(cseries.Cseries, 'link_auto_all', return_value=None) as method): - self.run_args('series', 'autolink-all', '-u', pwork=True) - method.assert_called_once_with(True, update_commit=True, + self.run_args('series', 'autolink-all', '--no-update', pwork=True) + method.assert_called_once_with(True, update_commit=False, link_all_versions=False, replace_existing=False, dry_run=False, show_summary=True) # Now do a real one to check the patchwork handling and output with terminal.capture() as (out, _): - self.run_args('series', 'autolink-all', '-a', pwork=pwork) + self.run_args('series', 'autolink-all', '-a', '--no-update', + pwork=pwork) itr = iter(out.getvalue().splitlines()) self.assertEqual( '1 series linked, 1 already linked, 1 not found (3 requests)', From patchwork Sat Apr 4 21:28: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: 2138 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=1775338358; bh=38zaXjuHr/z6qG/ZR5GM6BCDfSjVG+j+D4lqvU6PD3k=; 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=Xzjt/kmhY3I0hF5DVsH1nGA8i/pmJLWrGZ2rTqjLM3nD7sLsJDCTOIZ56YJdxVX2h OM3CRtk6XQB/ms04EfnQhnsc0k6YBKJOQNNk0nKaJE3Xyr5vATKIPdtTN/ClxBa4D5 ri2AmpvQC06X6p1m4W4+lTT2fUABtNSgX2NeH6/7KPPIJQDuEqtDY5cSpKmz7ManPq 6cF0/orFJD7tmvy5JgdOgoZAjga7w20zZ4SAUEQcmBCTjV2Cqb9ELTRG63UaDK4EEu l1lDcL8xyEjCLi1Q4CLLhyhEwrzpPW5tS9l4iStfHUMxJJ2ejiiN+z8q5t0/HA+jjv Gh/ajaxNaCOvA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 032BA68F7F for ; Sat, 4 Apr 2026 15:32: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 olPx5PQ2oroh for ; Sat, 4 Apr 2026 15:32:37 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338357; bh=38zaXjuHr/z6qG/ZR5GM6BCDfSjVG+j+D4lqvU6PD3k=; 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=RXKH+jZ6oauposm2AeUZEBkyvnnyN7XKkfbMvWwdW5VQ8C/mM365gX/0h6NziSxcZ BInKSLh4UZhtBlXSz6cFCVghCA4EV+05dP98oRT0aXgSsJAP0h4aE2wXKnlEsyuNVY YORWQi9nJwgaE3fCZx71hkwYlnbGj4aHMRjuJNHVTCZzxutX5BiXxd8ruLI4PGg/cJ GCGpn9Np/IYIb4LY3jUoGjM+hVg2mftKIOzbx3qG2AaP/FScsDbsmqNUtcE5dmQmvK hRco+/jRTBgf10kgGLOL7u2IqGiBPFI+XONVnbkUjPgjD2l2VExlTqC2SwU0uFn/CZ nH3FSBfvzGCcg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E67C06833B for ; Sat, 4 Apr 2026 15:32:37 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338356; bh=i6gHcorQFWk3ePANhzenV+q6IMcq0CdljNU95AKnqhA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tZPKUGqWJg+uWXJ9ND2yXBBldmGH5KgvKBVXCLfZiYC3CZyoIi4J8QWxZVahG9FNC fl4QHs7PpGazhOG2mAT58sodomxkAQsnizeSBYINdDQiKUtTFKRyrevVKa+srKvgzB MVb/Fl9LzJGlP+ubd8nhY2hOqZYlh0njjJTj8eUQdCTURaTxO2BYfChG7rHX+Zynyw AIIlOr0inQ7PLoNNpfSOZk6JYNvoG07K3X8dhzE3vNPKMLaQrf1ctDpQz3ZMOoBurm ki7m4W8NIUIB7t4kf58y++uPsBvs7Zs+9WhSYA/+ORi3oh5Xwlks82Y9jGBZ7H6lmC BeNqx5HsDhdtQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1D1F75FC8F; Sat, 4 Apr 2026 15:32:36 -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 JpBMchREtjde; Sat, 4 Apr 2026 15:32:36 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338351; bh=K9FFsB0fOQDeNaUASpr63jKh3WpsshDXLwXFR3/S95I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wnk7GDlaVYpt3nuZWRkKj42yloKopeL8brZHl3yu15eWX4dnZ3dda1tgHcdZDT/Xi nnp+SYEMWpd0S6ZRYnaVX67RakoA5jEJoNe4kNSIZNhfNDw9pX1zveTMq+JBBNgEIX TfqRKWwXQUaGzVEdpEqtx2K1wJ/vfsZIXn2n1c4dg5JsgatUQrcTIcnTJGCsvM5VXT Mi/DHvX7smrbPLQVuUj1mhmw/VKNrduLxD6aZBidrMlFKFKlBSFAj2WHyMqH20WJHg +gruUkXxlVepzcdjRKjv+KShDfTIaefiTIOHOFIKDWvwSvZ14LTVuQBxmeI0pq1F3s 9Z+jUqQPWKZWA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id B564C5E7B4; Sat, 4 Apr 2026 15:32:31 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:28:59 -0600 Message-ID: <20260404213020.372253-24-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: S3U4P627XWR2I3ZV44TEMK7C44VK7LTS X-Message-ID-Hash: S3U4P627XWR2I3ZV44TEMK7C44VK7LTS 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 23/37] patman: Fix remaining pylint warnings in control.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 Add missing single_thread parameter to patchwork_status() docstring, wrap a long error message to fit 80 columns, and suppress R0917 (too-many-positional-arguments) introduced in pylint 3.3.4. Signed-off-by: Simon Glass --- tools/patman/control.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tools/patman/control.py b/tools/patman/control.py index 1c1b998ddd6..d82b80ce807 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -48,7 +48,7 @@ def do_send(args): send.send(args) -# pylint: disable=R0913 +# pylint: disable=R0913,R0917 def patchwork_status(branch, count, start, end, dest_branch, force, show_comments, url, single_thread=False): """Check the status of patches in patchwork @@ -69,8 +69,11 @@ def patchwork_status(branch, count, start, end, dest_branch, force, force (bool): With dest_branch, force overwriting an existing branch show_comments (bool): True to display snippets from the comments provided by reviewers - url (str): URL of patchwork server, e.g. 'https://patchwork.ozlabs.org'. - This is ignored if the series provides a Series-patchwork-url tag. + url (str): URL of patchwork server, e.g. + 'https://patchwork.ozlabs.org'. Ignored if the series + provides a Series-patchwork-url tag. + single_thread (bool): True to use a single thread for + patchwork access Raises: ValueError: if the branch has no Series-link value @@ -94,7 +97,7 @@ def patchwork_status(branch, count, start, end, dest_branch, force, raise ValueError('Please fix warnings before running status') links = series.get('links') if not links: - raise ValueError("Branch has no Series-links value") + raise ValueError('Branch has no Series-links value') status.find_link_and_show_status( series, branch, url, dest_branch, force, show_comments, False, @@ -176,7 +179,8 @@ def do_series(args, test_db=None, pwork=None, cser=None): cser, pwork, ups, args.patchwork_url) elif pwork and pwork is not True: raise ValueError( - f"Internal error: command '{args.subcmd}' should not have patchwork") + "Internal error: command " + f"'{args.subcmd}' should not have patchwork") if args.subcmd == 'add': cser.add(args.series, args.desc, mark=args.mark, allow_unmarked=args.allow_unmarked, end=args.upstream, From patchwork Sat Apr 4 21:29: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: 2139 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=1775338363; bh=r4MTBAYDliBwfvSWA0oyTY2BHKPZ5EyzD7i/dyuoWoY=; 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=cWiucJm4xKnVVNxGEMd+FBK2+nFOylly5IOegzByk/UQNptBHqILO6HoL8ANsPZwl 41rCZvAgPbkP6W/5FrtoHUFsiTQtsFLCnPBGtwgR431ehh/zO/6QOEIabocbS+FoDW tagi9juft4Zvkxd3F4cV3qWbFoQ+Wu0qX3gDO9Ww4b5X9lgYgSx3uH5YLLMXgPcwto hdQrKjsy8yt/kwQSBWbkr8p9Y1Ykfqw4MtVsCd6qnG2sBN/zXqGy05IhO1ERXq7HAj 81pm74JR3X8RyDBr9w6fn9rxGNX3QCZyN/XrqaBVvnEE/ectD+cotT7UDjcFIV8rLh QtcPqwNpe5y7g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 80B1A68F53 for ; Sat, 4 Apr 2026 15:32: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 10024) with ESMTP id vel_zPQIyc8c for ; Sat, 4 Apr 2026 15:32:43 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338363; bh=r4MTBAYDliBwfvSWA0oyTY2BHKPZ5EyzD7i/dyuoWoY=; 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=cWiucJm4xKnVVNxGEMd+FBK2+nFOylly5IOegzByk/UQNptBHqILO6HoL8ANsPZwl 41rCZvAgPbkP6W/5FrtoHUFsiTQtsFLCnPBGtwgR431ehh/zO/6QOEIabocbS+FoDW tagi9juft4Zvkxd3F4cV3qWbFoQ+Wu0qX3gDO9Ww4b5X9lgYgSx3uH5YLLMXgPcwto hdQrKjsy8yt/kwQSBWbkr8p9Y1Ykfqw4MtVsCd6qnG2sBN/zXqGy05IhO1ERXq7HAj 81pm74JR3X8RyDBr9w6fn9rxGNX3QCZyN/XrqaBVvnEE/ectD+cotT7UDjcFIV8rLh QtcPqwNpe5y7g== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 70D266833B for ; Sat, 4 Apr 2026 15:32:43 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338361; bh=XBi64aflibyE9PadyeJS+JEQXlSm0R8dSX8RzGR7YZk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vOJPAD9eIp79ny2pS/6nO3kys7dKZ3nEdduxPe8J/uJNOchSdIqXFppBjLPHxl+yN PpwgeURSWBy9cqIKtWsoOrOkNeVul5LVAatD9LxD1rcOQrnm55si+bb0mPI/nB/aQv t7vB1FqC/xnHRBwp07kKghfCyug3+2uaV/AmumersLK5pqqxDJM4b8cypYI3C5sZlo 6PO9d/+yhIWRCo2l9OKQAJsnaVzgYq0/CmOQRsG3DtixFj09q7aR2rMv8oTmXx9AO0 uFnhU9Yl6OHtZaWcdbHRrqgPBfChsausKQvGq3XsdtahVIIFW7znfRvN9IzZn4tZ4y 0asdfok9F//5w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 228695FC8F; Sat, 4 Apr 2026 15:32:41 -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 qzK1K0x507wt; Sat, 4 Apr 2026 15:32:41 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338357; bh=+HeiXPh3IfLY7+4ZkXlQUt2/V1U3sNp4UP92/n/Ukgs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vfr2Qk978ofOCaK7/qo0c6V79CJL3zH8AB6hTUaAZwdeyyBflbgza5akDVzu6au/r fz+aI32wV+e1GABmjIVA+OxWuksNb/tprqJ+CODOu+kqFanlPsRanJ71wNqzGf2Vfk gtrneAQWlskYzA2PJZW0JJWt8thk1XAaSljaewcDLUK6dQkapCgC3n8tym14gV+xk4 xfOQxkPmnP+TOQ6d3VM1YiMoZ4iPMYoK9WKAO2XL+mxkU/Zb+4YpCGtD2HrqfA7zs6 j9x3pjl5yAdXAKE9ef+0h0Y+Y7ozgla1eXR7eOI1K3oGUKcoEBoDFQ3SbvcnDZcKFJ Hky4fNtIxiMvg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 28F515E7B4; Sat, 4 Apr 2026 15:32:37 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sat, 4 Apr 2026 15:29:00 -0600 Message-ID: <20260404213020.372253-25-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 543MSE3HYZCI6QPAQA4FZDJ2A3JC4MZQ X-Message-ID-Hash: 543MSE3HYZCI6QPAQA4FZDJ2A3JC4MZQ 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 24/37] patman: Add 'review' command to download and apply patches 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 new top-level 'patman review' command (aliases: 'r', 'rev') for AI-assisted review of other people's patches from patchwork. This is the first stage which handles downloading and applying the patches to a local branch. The command accepts a patchwork series link (-l) or a title search (-t) and: - Fetches series metadata from the patchwork API - Auto-creates database records (series with source='review', ser_ver, pcommit entries) to track the review - Detects previously reviewed series by link or by name, so new versions are added under the same series record - Downloads the series mbox from patchwork - Uses a Claude agent to apply the patches via 'git am', handling any conflicts that arise - Creates a branch named 'review' where N is the series ID Later stages will add AI-assisted review of each patch and Gmail draft creation. Signed-off-by: Simon Glass --- tools/patman/cmdline.py | 43 +++++ tools/patman/control.py | 34 ++++ tools/patman/review.py | 372 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 449 insertions(+) create mode 100644 tools/patman/review.py diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index 3c12200949f..edefa778446 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -25,6 +25,7 @@ ALIASES = { 'series': ['s', 'ser'], 'status': ['st'], 'patchwork': ['pw'], + 'review': ['r', 'rev'], 'upstream': ['us'], 'workflow': ['wf'], @@ -521,6 +522,46 @@ def add_workflow_subparser(subparsers): return workflow +def add_review_subparser(subparsers): + """Add the 'review' subparser + + Args: + subparsers (argparse action): Subparser parent + + Return: + ArgumentParser: review subparser + """ + review = subparsers.add_parser( + 'review', aliases=ALIASES['review'], + help='AI-powered review of a patchwork series') + review.add_argument( + '-l', '--link', 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') + review.add_argument( + '-n', '--dry-run', action='store_true', dest='dry_run', + default=False, + help='Show what would be done without creating drafts') + review.add_argument( + '--create-drafts', action='store_true', + help='Create Gmail draft emails for each review') + review.add_argument( + '--no-cover', action='store_true', + help='Skip reviewing the cover letter') + review.add_argument( + '--reviewer', type=str, default=None, + help="Override reviewer identity (format: 'Name ')") + review.add_argument( + '-U', '--upstream', type=str, default=None, + help='Upstream name (for patchwork URL lookup)') + review.add_argument( + '--apply-only', action='store_true', + help='Only download and apply patches, skip AI review') + return review + + def setup_parser(): """Set up command-line parser @@ -560,6 +601,7 @@ def setup_parser(): subparsers = parser.add_subparsers(dest='cmd') add_send_subparser(subparsers) patchwork = add_patchwork_subparser(subparsers) + review = add_review_subparser(subparsers) series = add_series_subparser(subparsers) add_status_subparser(subparsers) upstream = add_upstream_subparser(subparsers) @@ -573,6 +615,7 @@ def setup_parser(): parsers = { 'main': parser, + 'review': review, 'series': series, 'patchwork': patchwork, 'upstream': upstream, diff --git a/tools/patman/control.py b/tools/patman/control.py index d82b80ce807..3ce9736d6ba 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -19,6 +19,7 @@ except ImportError: from patman import cseries from patman import patchstream +from patman import review as review_mod from patman import send from patman import settings from patman import status @@ -396,6 +397,37 @@ def do_workflow(args, test_db=None): cser.close_database() +def do_review(args, test_db=None, pwork=None, cser=None): + """Process the 'review' command + + Sets up patchwork and cseries, then delegates to + review.do_review(). + + Args: + args (Namespace): Arguments to process + test_db (str or None): Directory containing the test + database, None to use the normal one + pwork (Patchwork): Patchwork object to use, or None to + create one + cser (Cseries): Cseries object to use, or None to create + one + """ + if not cser: + cser = cseries.Cseries(test_db) + try: + cser.open_database() + + ups = args.upstream + if not ups: + ups = cser.db.upstream_get_default() + pwork = _setup_patchwork( + cser, pwork, ups, args.patchwork_url) + + return review_mod.do_review(args, pwork, cser) + finally: + cser.close_database() + + # pylint: disable=R0912 def do_patman(args, test_db=None, pwork=None, cser=None): """Process a patman command @@ -445,6 +477,8 @@ def do_patman(args, test_db=None, pwork=None, cser=None): upstream(args, test_db) elif args.cmd == 'patchwork': patchwork(args, test_db, pwork) + elif args.cmd == 'review': + do_review(args, test_db, pwork, cser) elif args.cmd == 'workflow': do_workflow(args, test_db) except Exception as exc: # pylint: disable=W0718 diff --git a/tools/patman/review.py b/tools/patman/review.py new file mode 100644 index 00000000000..d3eac86f8a1 --- /dev/null +++ b/tools/patman/review.py @@ -0,0 +1,372 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2025 Canonical Ltd. +# Written by Simon Glass +# + +"""AI-powered patch review using Claude. + +Fetches patches from patchwork, applies them to a local branch using +a Claude agent, and (in later stages) reviews each patch and creates +Gmail draft replies. +""" + +import asyncio +import os +import tempfile + +import aiohttp + +from patman.database import Pcommit +from u_boot_pylib import gitutil +from u_boot_pylib import tout + + +async def fetch_mbox(pwork_url, link): + """Download the series mbox file from patchwork + + Args: + pwork_url (str): Patchwork server URL, e.g. + 'https://patchwork.ozlabs.org' + link (str): Patchwork series link/ID + + Returns: + str: Path to the downloaded mbox file + + Raises: + ValueError: if the download fails + """ + url = f'{pwork_url}/series/{link}/mbox/' + tout.info(f'Downloading mbox from {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 from {url}: ' + f'HTTP {response.status}') + content = await response.read() + if not content: + raise ValueError(f'Empty mbox downloaded from {url}') + + with open(mbox_path, 'wb') as fh: + fh.write(content) + tout.info(f'Downloaded {len(content)} bytes to {mbox_path}') + return mbox_path + + +def _build_apply_prompt(mbox_path, branch_name, upstream_branch): + """Build the Claude agent prompt for applying patches + + The agent will create a branch and apply the mbox using git am, + handling any conflicts that arise. + + Args: + mbox_path (str): Path to the downloaded mbox file + branch_name (str): Name for the new branch + upstream_branch (str): Branch to start from (e.g. 'origin/master') + + Returns: + str: The prompt text for the agent + """ + return f"""Apply a patch series from a patchwork mbox file to a new \ +git branch. + +TASK: +1. Create a new branch called '{branch_name}' from '{upstream_branch}': + git checkout -b {branch_name} {upstream_branch} + +2. Apply the patches from the mbox file: + git am {mbox_path} + +3. If git am fails with conflicts: + - Inspect the conflict with 'git diff' and 'git status' + - Try to resolve the conflicts sensibly + - Run 'git add' on resolved files and 'git am --continue' + - If a patch simply cannot be applied (e.g. already applied or + completely incompatible), use 'git am --skip' and note which + patch was skipped + +4. After all patches are applied (or skipped), run: + git log --oneline {upstream_branch}..HEAD + +5. Report the result: + - How many patches were applied successfully + - Which patches (if any) were skipped and why + - The branch name: {branch_name} + +IMPORTANT: +- Do NOT modify the patches before applying +- Do NOT use 'git am --abort' unless all attempts to resolve fail +- If you skip a patch, continue with the remaining patches +- The mbox file is at: {mbox_path} +""" + + +async def apply_series(pwork_url, link, branch_name, upstream_branch, + repo_path): + """Download and apply a patch series to a new local branch + + Uses the Claude agent to handle the git am process, including + conflict resolution. + + Args: + pwork_url (str): Patchwork server URL + link (str): Patchwork series link/ID + branch_name (str): Name for the new branch + upstream_branch (str): Branch to base from + repo_path (str): Path to the git repository + + Returns: + tuple: (success, branch_name) where success is bool and + branch_name is the name of the created branch + """ + # pylint: disable=C0415,E0611 + from u_boot_pylib.claude import (check_available, run_agent_collect, + MAX_BUFFER_SIZE) + + if not check_available(): + return False, None + + from u_boot_pylib.claude import ClaudeAgentOptions + + # Download the mbox + mbox_path = await fetch_mbox(pwork_url, link) + + # 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=MAX_BUFFER_SIZE, + ) + + tout.info(f'Applying series to branch {branch_name}...') + success, _ = await run_agent_collect(prompt, options) + + # Clean up the mbox file + try: + os.unlink(mbox_path) + except OSError: + pass + + return success, branch_name + + +def apply_series_sync(pwork_url, link, branch_name, upstream_branch, + repo_path): + """Synchronous wrapper for apply_series() + + Args: + pwork_url (str): Patchwork server URL + link (str): Patchwork series link/ID + branch_name (str): Name for the new branch + upstream_branch (str): Branch to base from + repo_path (str): Path to the git repository + + Returns: + tuple: (success, branch_name) + """ + return asyncio.run( + apply_series(pwork_url, link, branch_name, upstream_branch, + repo_path)) + + +def search_series(pwork, title): + """Search patchwork for a series by cover-letter title + + Queries the patchwork API and returns the link for the best match. + If multiple matches are found, shows them and picks the most recent. + + Args: + pwork (Patchwork): Configured patchwork instance + title (str): Title text to search for + + Returns: + str: Patchwork series link/ID + + Raises: + ValueError: if no matching series is found + """ + async def _query(): + if not pwork.proj_id: + raise ValueError( + 'Patchwork project not configured; use ' + "'patman patchwork set-project' or provide -l ") + async with aiohttp.ClientSession() as client: + # pylint: disable=W0212 + return await pwork._query_series(client, title) + + loop = asyncio.get_event_loop() + results = loop.run_until_complete(_query()) + + if not results: + raise ValueError(f"No series found matching '{title}'") + + if len(results) == 1: + match = results[0] + tout.notice(f"Found: {match['name']} (v{match['version']}, " + f"link {match['id']})") + return str(match['id']) + + tout.notice(f"Found {len(results)} matching series:") + for i, match in enumerate(results): + tout.notice(f" {i + 1}. [{match['id']}] {match['name']} " + f"(v{match['version']}, {match['date']})") + + best = max(results, + key=lambda r: (r.get('version', 0), r.get('date', ''))) + tout.notice(f"Using most recent: {best['name']} (link {best['id']})") + return str(best['id']) + + +def _clean_series_name(name): + """Strip the [U-Boot,v2,0/4] prefix from a series name + + Args: + name (str): Raw series name from patchwork + + Returns: + str: Cleaned name + """ + if name.startswith('['): + bracket_end = name.find(']') + if bracket_end != -1: + return name[bracket_end + 1:].strip() + return name + + +# pylint: disable=R0914 +def _register_series(cser, clean_name, version, link, series_data): + """Register a series in the database for review tracking + + Creates or finds the series record, adds a ser_ver entry and + pcommit records for each patch. + + Args: + cser (Cseries): Open cseries instance + clean_name (str): Cleaned series name + version (int): Series version number + link (str): Patchwork series link/ID + series_data (dict): Series data from patchwork + + Returns: + tuple: (series_id, svid) or None if already reviewed + """ + existing = cser.db.series_find_by_link(link) + if existing: + series_id, db_name, db_version, svid = existing + tout.notice( + f"Already reviewed: '{db_name}' v{db_version}") + return None + + prev = cser.db.series_find_review_by_name(clean_name) + if prev: + series_id, db_name, prev_version = prev + tout.notice(f"Previously reviewed '{db_name}' " + f"v{prev_version}; adding v{version}") + else: + desc = series_data.get('cover_letter', {}) + desc = desc.get('name', '') if desc else '' + series_id = cser.db.series_add(clean_name, desc) + cser.db.series_set_source(series_id, 'review') + + svid = cser.db.ser_ver_add(series_id, version, + link=str(link)) + + patches = series_data.get('patches', []) + pcommits = [] + for i, patch in enumerate(patches): + pcommits.append(Pcommit( + idnum=None, seq=i, + subject=patch.get('name', ''), + svid=svid, change_id=None, state=None, + patch_id=patch.get('id'), num_comments=0)) + if pcommits: + cser.db.pcommit_add_list(svid, pcommits) + + cser.commit() + tout.notice( + f"Added series '{clean_name}' v{version} to database") + return series_id, svid + + +# pylint: disable=R0914 +def do_review(args, pwork, cser): + """Run the review command + + Downloads patches from patchwork, applies them to a local + branch using a Claude agent, and (in later stages) reviews + each patch. + + Args: + args (Namespace): Command-line arguments + pwork (Patchwork): Configured patchwork instance + cser (Cseries): Open cseries instance + """ + if not args.pw_link and not args.title: + raise ValueError( + "Please provide -l or -t to " + "identify the series") + + link = args.pw_link + if not link: + link = search_series(pwork, args.title) + + # Fetch series metadata from patchwork + async def _fetch(): + async with aiohttp.ClientSession() as client: + return await pwork.get_series(client, link) + + loop = asyncio.get_event_loop() + series_data = loop.run_until_complete(_fetch()) + + series_name = series_data.get('name', f'series-{link}') + version = series_data.get('version', 1) + patch_count = series_data.get('received_total', 0) + clean_name = _clean_series_name(series_name) + + tout.notice(f"Series: {clean_name}") + tout.notice( + f"Version: {version}, Patches: {patch_count}") + + result = _register_series( + cser, clean_name, version, link, series_data) + if result is None: + return 0 + series_id, _ = result + + # Determine the upstream branch for applying patches + ups = args.upstream + if not ups: + ups = cser.db.upstream_get_default() + if ups: + upstream_branch = f'{ups}/master' + else: + upstream_branch = 'origin/master' + + # Download and apply the patches + branch_name = f'review{series_id}' + repo_path = gitutil.get_top_level() + success, branch_name = apply_series_sync( + pwork.url, link, branch_name, upstream_branch, + repo_path) + + if not success: + tout.error('Failed to apply patches to branch') + return 1 + tout.notice(f'Patches applied to branch: {branch_name}') + + if args.apply_only: + tout.notice('Apply-only mode; skipping review') + return 0 + + # TODO: Stage 2 - AI review of each patch + # TODO: Stage 3 - Gmail draft creation + tout.notice( + 'Patch application complete. ' + 'AI review not yet implemented.') + + return 0 From patchwork Sat Apr 4 21:29:01 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: 2140 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=1775338368; bh=Ts7By+ELA5PoE1KwmI1vgWeNhH2xDeTL5VOHfbU/il4=; 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=fXbufhPpN9xzYF7O/GTml5hl7vOLDrnRjItmAet+fS25nU8dUNDswOhRRy5USJScV /3nPKT+PKrkzmVSjJVMZnjgA3pysT9PL2X2XZdr4YoU1DFd+98oZNGWb+z1adFHSwV K2q9b8dp4Ypccjq6xB+rv114x61pT4EdrJ5iH058/jw+BcKZRCFkwc5AOcgzWjor0H c+//hULMaSPXkFEtCiDTgKB76J/MBIUGosgEC6v9a8LfHMjh8e9o76DIKxShMEIewk 00rGCB3WMSFsS2kenFP0TYR3bVczeoy5yWlA+z/f4+nQdv6rdepr1hqCZVSJwydcIo yVM7Tm29E0how== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 25C2E68F53 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32: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 iLb40f5QHuPp for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32:48 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338368; bh=Ts7By+ELA5PoE1KwmI1vgWeNhH2xDeTL5VOHfbU/il4=; 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=fXbufhPpN9xzYF7O/GTml5hl7vOLDrnRjItmAet+fS25nU8dUNDswOhRRy5USJScV /3nPKT+PKrkzmVSjJVMZnjgA3pysT9PL2X2XZdr4YoU1DFd+98oZNGWb+z1adFHSwV K2q9b8dp4Ypccjq6xB+rv114x61pT4EdrJ5iH058/jw+BcKZRCFkwc5AOcgzWjor0H c+//hULMaSPXkFEtCiDTgKB76J/MBIUGosgEC6v9a8LfHMjh8e9o76DIKxShMEIewk 00rGCB3WMSFsS2kenFP0TYR3bVczeoy5yWlA+z/f4+nQdv6rdepr1hqCZVSJwydcIo yVM7Tm29E0how== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 14DED5E7B4 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32:48 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338367; bh=PkopsUiW7t/GU0pfIW6cTGVBCnEm+Xzp0bAhT6PhXfQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BZdUsv22HWvj49VvFDQYcPIV/kr83UMIWu/Dhu5ygoiXjcgZlgFDcrr9WbmDFW3G7 EraW/AzGnU+q4GSvGhosPu61YP1WTghV1fe/PPpD68ue8TAfrE/+bcbnUGhuw9EAip Yl0Pxq1DFqvSIUuJU1Uo7yS3gESuMTcd6uHvwt1uX5I1OJwD+vL7pkdpq6Nh6KGWiA GaNQBoyumRMwQk3vcTeJOoiG5pyZQCPbBq0w4t8J79zXsMEVQXQrLTXCQc8mB/0NzZ LBpIfZrIMfVJMRa65a4yMVzkYucfKWPOxasLtvgmDp6ed8BRv8gqBo0+sQ4T6fi+9f e2ffXSPc2tC4w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0A15A5FC8F; Sat, 4 Apr 2026 15:32:47 -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 dRVfT2ni7-dk; Sat, 4 Apr 2026 15:32:46 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338362; bh=mmgx3Aa/6gDh0SzatMylG7YGr192zPXWetfsFNXfyoc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fgTNNNxewer9Ur/8oLB/8NpxjLHPNylBbzhTSC6SSLNuWHUXALL5uoVsJqi8h7XEX pL8zM1JCItN+f8/Q4Zju+cgUcuCD+Y4NumuEJcEiMzVYKIxsTfTdzGWztP4W2u1cJl 2WNJb1tzPgO2O7uIQqz/aW7opaCsNEuqPlxg/h7HEK94zyvCO5LqqpLLQ/dJdD4j9Z kCdz4pH4cxF+uFMOO8ZvWqMf1R6SLrX8KMFyKVHncuTfeX4TUlaUUXYhyoBW+JkVJY aqeTo20kxWV4rcnTclUaYzSSohQHVUU4/5I9BpewXsCFtToCY8H5BzcjCQIqC8bdpi jh9nG/nTgjeXg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 315E15E7B4; Sat, 4 Apr 2026 15:32:42 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:01 -0600 Message-ID: <20260404213020.372253-26-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: QV27QM77DSYVTA7AMB7G6GKBCSLP2PJK X-Message-ID-Hash: QV27QM77DSYVTA7AMB7G6GKBCSLP2PJK 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/37] patman: Add Gmail draft creation for patch 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/QV27QM77DSYVTA7AMB7G6GKBCSLP2PJK/> 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 Gmail API integration module that creates draft reply emails for patch reviews. The module handles: - OAuth2 authentication with token caching in ~/.config/patman.d/ - Creating draft emails with proper In-Reply-To and References headers for threading - Finding existing Gmail threads by Message-ID so drafts appear as replies - Building CC lists from the original patch headers - Setting the From header when the reviewer identity differs from the Gmail account - Syncing draft status (detecting sent and deleted drafts) - Fetching thread replies for follow-up response generation The Subject header is taken from the original email headers to preserve the [PATCH] prefix that patchwork strips from its 'name' field. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/gmail.py | 591 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 591 insertions(+) create mode 100644 tools/patman/gmail.py -- 2.43.0 diff --git a/tools/patman/gmail.py b/tools/patman/gmail.py new file mode 100644 index 00000000000..78bedc99d16 --- /dev/null +++ b/tools/patman/gmail.py @@ -0,0 +1,591 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2025 Canonical Ltd. +# Written by Simon Glass <simon.glass@canonical.com> +# +# pylint: disable=C0415,R0913,R0914,R0917,W0718 + +"""Gmail API integration for creating draft review emails. + +Handles OAuth2 authentication and creation of draft emails via the +Gmail API. Tokens are stored in ~/.config/patman/ for reuse across +sessions. +""" + +import base64 +from collections import namedtuple +import os +from email.mime.text import MIMEText + +from u_boot_pylib import tout + +# Common parameters for draft creation +DraftParams = namedtuple('DraftParams', + 'service fallback_addr list_email ' + 'dry_run sender cover_msgid') + +# Config paths - use patman.d to avoid conflict with existing ~/.config/patman +CONFIG_DIR = os.path.expanduser('~/.config/patman.d') +CLIENT_SECRET_PATH = os.path.join(CONFIG_DIR, 'client_secret.json') + +# Gmail API scopes - compose drafts and read messages (for thread lookup) +SCOPES = ['https://www.googleapis.com/auth/gmail.compose', + 'https://www.googleapis.com/auth/gmail.readonly'] + + +def _token_path(account=None): + """Get the token path for a given account + + Args: + account (str or None): Gmail account email, or None for default + + Returns: + str: Path to the token file + """ + if account: + safe = account.replace('@', '_at_').replace('.', '_') + return os.path.join(CONFIG_DIR, f'gmail_token_{safe}.json') + return os.path.join(CONFIG_DIR, 'gmail_token.json') + + +def check_available(): + """Check if Gmail API dependencies are installed + + Returns: + bool: True if google-api-python-client and google-auth-oauthlib + are available + """ + try: + from googleapiclient import discovery # pylint: disable=W0611 + from google_auth_oauthlib import flow # pylint: disable=W0611 + from google.auth.transport import requests # pylint: disable=W0611 + except ImportError: + tout.error('Gmail API dependencies not available') + tout.error('Install with: pip install google-api-python-client ' + 'google-auth-httplib2 google-auth-oauthlib') + return False + + if not os.path.exists(CLIENT_SECRET_PATH): + tout.error(f'Gmail client secret not found at {CLIENT_SECRET_PATH}') + tout.error('Download OAuth client credentials from Google Cloud ' + 'Console and save them there') + return False + return True + + +def _get_credentials(account=None): + """Get or refresh OAuth2 credentials + + Loads existing token if available and valid. If expired, refreshes + using the refresh token. If no valid token exists, initiates the + OAuth2 consent flow. + + Args: + account (str or None): Gmail account email (e.g. + 'user@gmail.com'), or None for default token + + Returns: + google.oauth2.credentials.Credentials: Valid credentials + + Raises: + FileNotFoundError: if client_secret.json is missing + """ + from google.auth.transport.requests import Request + from google.oauth2.credentials import Credentials + from google_auth_oauthlib.flow import InstalledAppFlow + + token_path = _token_path(account) + creds = None + + # Load existing token + if os.path.exists(token_path): + creds = Credentials.from_authorized_user_file(token_path, SCOPES) + + # Refresh or get new credentials + if not creds or not creds.valid: + if creds and creds.refresh_token: + creds.refresh(Request()) + else: + if not os.path.exists(CLIENT_SECRET_PATH): + raise FileNotFoundError( + f'Gmail client secret not found at {CLIENT_SECRET_PATH}') + app_flow = InstalledAppFlow.from_client_secrets_file( + CLIENT_SECRET_PATH, SCOPES) + if account: + tout.notice(f'Please authenticate as {account}') + creds = app_flow.run_local_server(port=0) + + # Save the token for next time + os.makedirs(CONFIG_DIR, exist_ok=True) + with open(token_path, 'w', encoding='utf-8') as fh: + import json + token_data = { + 'token': creds.token, + 'refresh_token': creds.refresh_token, + 'token_uri': creds.token_uri, + 'client_id': creds.client_id, + 'client_secret': creds.client_secret, + 'scopes': list(creds.scopes or []), + } + json.dump(token_data, fh) + + return creds + + +def get_service(account=None): + """Get an authenticated Gmail API service object + + Args: + account (str or None): Gmail account email, or None for default + + Returns: + googleapiclient.discovery.Resource: Gmail API service + """ + from googleapiclient.discovery import build + + creds = _get_credentials(account) + return build('gmail', 'v1', credentials=creds) + + +def fetch_sent_reviews(service, list_email, user_email, + max_results=20): + """Fetch sent emails that are reviews of other people's patches + + Paginates through Gmail search results to collect review emails. + Filters out replies to the user's own patches by checking whether + the first message in the thread was sent by someone else. + + Args: + service (googleapiclient.discovery.Resource): Gmail API service + list_email (str): Mailing list address to filter by + user_email (str): The reviewer's own email, used to skip + threads initiated by the reviewer + max_results (int): Maximum number of emails to collect + + Returns: + list of str: Email body texts + """ + query = f'from:me to:{list_email} subject:(Re: PATCH)' + bodies = [] + page_token = None + # Cache thread originator lookups + thread_from_cache = {} + + while len(bodies) < max_results: + kwargs = {'userId': 'me', 'q': query, 'maxResults': 100} + if page_token: + kwargs['pageToken'] = page_token + results = service.users().messages().list(**kwargs).execute() + messages = results.get('messages', []) + if not messages: + break + + for msg_info in messages: + if len(bodies) >= max_results: + break + tout.progress( + f'Fetched {len(bodies)}/{max_results} review emails') + + msg = service.users().messages().get( + userId='me', id=msg_info['id'], format='full').execute() + + # Check if the thread was started by someone else + thread_id = msg.get('threadId') + if thread_id not in thread_from_cache: + thread = service.users().threads().get( + userId='me', id=thread_id, + format='metadata', + metadataHeaders=['From']).execute() + first_msg = thread.get('messages', [{}])[0] + hdrs = first_msg.get('payload', {}).get('headers', []) + from_val = '' + for h in hdrs: + if h['name'] == 'From': + from_val = h['value'] + break + thread_from_cache[thread_id] = from_val + + # Skip if this thread was started by us + if user_email in thread_from_cache.get(thread_id, ''): + continue + + payload = msg.get('payload', {}) + body = _extract_body(payload) + if body and '>' in body: + bodies.append(body) + + page_token = results.get('nextPageToken') + if not page_token: + break + + tout.clear_progress() + return bodies + + +def _extract_body(payload): + """Extract plain text body from a Gmail message payload + + Args: + payload (dict): Gmail message payload + + Returns: + str or None: Decoded body text + """ + if payload.get('mimeType') == 'text/plain': + data = payload.get('body', {}).get('data', '') + if data: + return base64.urlsafe_b64decode(data).decode('utf-8', 'replace') + + for part in payload.get('parts', []): + result = _extract_body(part) + if result: + return result + return None + + +def _find_thread(service, msgid): + """Find the Gmail thread containing a message with the given Message-ID + + Args: + service (googleapiclient.discovery.Resource): Gmail API service + msgid (str): Message-ID header value (e.g. '<abc@example.com>') + + Returns: + str or None: Gmail thread ID if found + """ + # Strip angle brackets — Gmail search expects bare message ID + bare_id = msgid.strip('<>') + try: + results = service.users().messages().list( + userId='me', q=f'rfc822msgid:{bare_id}').execute() + messages = results.get('messages', []) + if messages: + return messages[0].get('threadId') + except Exception as exc: + tout.warning(f'Failed to search Gmail for thread: {exc}') + return None + + +def create_draft(service, to, subject, body, + in_reply_to=None, references=None, cc=None, + sender=None): + """Create a Gmail draft email + + If in_reply_to is set, searches for the original message in Gmail + and attaches the draft to that thread so it appears as a reply. + + If sender is provided and differs from the Gmail account's address, + a From header is set so the email is sent on behalf of the right + identity. + + Args: + service (googleapiclient.discovery.Resource): Gmail API service + to (str): Recipient email address + subject (str): Email subject line + body (str): Plain text email body + in_reply_to (str or None): Message-ID to reply to + references (str or None): References header value + cc (str or None): CC addresses + sender (str or None): Sender identity, e.g. 'Name <email>'. + If set, added as the From header. + + Returns: + dict: Gmail API response with draft ID and message info + + Raises: + googleapiclient.errors.HttpError: on API failure + """ + msg = MIMEText(body) + if sender: + msg['from'] = sender + msg['to'] = to + msg['subject'] = subject + if cc: + msg['cc'] = cc + if in_reply_to: + msg['In-Reply-To'] = in_reply_to + msg['References'] = references or in_reply_to + + raw = base64.urlsafe_b64encode(msg.as_bytes()).decode() + draft_body = {'message': {'raw': raw}} + + # Find the existing thread in Gmail so the draft appears as a reply + if in_reply_to: + thread_id = _find_thread(service, in_reply_to) + if thread_id: + draft_body['message']['threadId'] = thread_id + tout.notice(f' Found thread: {thread_id}') + else: + tout.notice(f' Thread not found for {in_reply_to}' + ' - draft will be standalone') + + draft = service.users().drafts().create( + userId='me', body=draft_body).execute() + return draft + + +def _build_cc(headers, list_email): + """Build a CC list from the original patch headers + + Combines the original To, Cc headers and mailing list address, + deduplicating entries. + + Args: + headers (dict): Email headers from patchwork get_patch() + list_email (str or None): Mailing list email address + + Returns: + str: Comma-separated CC addresses + """ + addrs = [] + for field in ('To', 'Cc'): + val = headers.get(field, '') + if val: + addrs.append(val) + if list_email and list_email not in ', '.join(addrs): + addrs.append(list_email) + return ', '.join(addrs) + + +def _get_msgid(patch_data, hdrs): + """Get the Message-ID from patch data or headers + + Args: + patch_data (dict): Patch or cover letter data from patchwork + hdrs (dict): Email headers from patchwork get_patch() + + Returns: + str or None: Message-ID + """ + return (patch_data.get('msgid') or + hdrs.get('Message-Id') or + hdrs.get('Message-ID')) + + +def _make_draft(params, patch_data, body, hdrs, refs=None): + """Create a Gmail draft for a review reply + + Args: + params (DraftParams): Common draft parameters + patch_data (dict): Patch or cover letter data from patchwork + body (str): Review email body + hdrs (dict): Email headers + refs (str or None): References header value. If None, uses + the Message-ID as the reference (for cover letters). + + Returns: + str or None: Gmail draft ID, or None for dry run + """ + subject = hdrs.get('Subject', patch_data.get('name', '')) + if not subject.startswith('Re: '): + subject = f"Re: {subject}" + msgid = _get_msgid(patch_data, hdrs) + if refs is None: + refs = msgid + to_addr = hdrs.get('Reply-To', params.fallback_addr) + cc = _build_cc(hdrs, params.list_email) + if params.dry_run: + tout.notice(f"Would create draft: {subject}") + tout.notice(f" To: {to_addr}, Cc: {cc}") + return None + draft = create_draft(params.service, to_addr, subject, + body, in_reply_to=msgid, + references=refs, cc=cc, + sender=params.sender) + tout.notice(f"Created draft: {subject}") + return draft['id'] + + +def create_review_drafts(series_data, review_bodies, patch_headers=None, + dry_run=False, account=None, sender=None): + """Create Gmail drafts for a reviewed series + + Creates one draft per patch (and optionally the cover letter), + each threaded as a reply to the original email. + + Args: + series_data (dict): Series data from patchwork + get_series(), containing 'patches', 'cover_letter', + 'submitter', 'project' + review_bodies (dict): Map of patch index to review body + text. Key 0 is the cover letter, 1..N are patches. + patch_headers (dict or None): Map of patch index to + headers dict from patchwork get_patch() + dry_run (bool): If True, print what would be created + without calling the API + account (str or None): Gmail account email to use + sender (str or None): Reviewer identity, e.g. + 'Name <email>'. If this differs from the Gmail + account, it is set as the From header. + + Returns: + dict: Map of patch index to Gmail draft ID (empty for + dry run) + """ + if patch_headers is None: + patch_headers = {} + submitter = series_data.get('submitter', {}) + fallback_addr = submitter.get('email', '') + project = series_data.get('project', {}) + list_email = project.get('list_email') + cover = series_data.get('cover_letter') + patches = series_data.get('patches', []) + + cover_hdrs = patch_headers.get(0, {}) + cover_msgid = _get_msgid(cover, cover_hdrs) if cover else None + + service = None + if not dry_run: + if not check_available(): + return {} + service = get_service(account) + + params = DraftParams(service, fallback_addr, list_email, + dry_run, sender, cover_msgid) + + return _make_all_drafts(params, cover, patches, + review_bodies, patch_headers) + + +def _make_all_drafts(params, cover, patches, review_bodies, + patch_headers): + """Create drafts for the cover letter and each patch + + Args: + params (DraftParams): Common draft parameters + cover (dict or None): Cover letter data from patchwork + patches (list of dict): Patch data from patchwork + review_bodies (dict): Map of index to review body text + patch_headers (dict): Map of index to email headers + + Returns: + dict: Map of patch index to Gmail draft ID + """ + draft_ids = {} + + if cover and 0 in review_bodies: + hdrs = patch_headers.get(0, {}) + did = _make_draft(params, cover, review_bodies[0], + hdrs) + if did: + draft_ids[0] = did + + for i, patch in enumerate(patches): + idx = i + 1 + if idx not in review_bodies: + continue + hdrs = patch_headers.get(idx, {}) + msgid = _get_msgid(patch, hdrs) + refs = (f'{params.cover_msgid} {msgid}' + if params.cover_msgid else msgid) + did = _make_draft(params, patch, review_bodies[idx], hdrs, refs) + if did: + draft_ids[idx] = did + + return draft_ids + + +def fetch_thread_replies(service, thread_id, after_msg_id): + """Fetch replies in a thread that appeared after a given message + + Args: + service (googleapiclient.discovery.Resource): Gmail API service + thread_id (str): Gmail thread ID + after_msg_id (str): Gmail message ID of our sent review + + Returns: + list of dict: Replies, each with 'from', 'date', 'body' + """ + try: + thread = service.users().threads().get( + userId='me', id=thread_id, format='full').execute() + except Exception: + return [] + + messages = thread.get('messages', []) + + # Find our message index + our_idx = -1 + for i, msg in enumerate(messages): + if msg['id'] == after_msg_id: + our_idx = i + break + + if our_idx < 0: + return [] + + # Collect messages after ours + replies = [] + for msg in messages[our_idx + 1:]: + payload = msg.get('payload', {}) + headers = {h['name']: h['value'] + for h in payload.get('headers', [])} + body = _extract_body(payload) + if body: + replies.append({ + 'from': headers.get('From', ''), + 'date': headers.get('Date', ''), + 'body': body, + 'msg_id': msg['id'], + 'thread_id': thread_id, + }) + return replies + + +def sync_drafts(service, reviews): + """Check if review drafts have been sent or deleted + + For each review that has a draft_id, checks if the draft still + exists. If gone, checks the sent folder for a matching message. + + Args: + service (googleapiclient.discovery.Resource): Gmail API service + reviews (list of Review): Review records with draft_id set + + Returns: + tuple: (sent, deleted) where: + sent (dict): Map of review ID to (body, msg_id, thread_id) + deleted (list): List of review IDs whose drafts were + deleted without sending + """ + sent = {} + deleted = [] + + for rev in reviews: + if not rev.draft_id: + continue + + # Check if the draft still exists + try: + service.users().drafts().get( + userId='me', id=rev.draft_id).execute() + # Draft still exists — not sent yet + continue + except Exception: + pass + + # Draft is gone — check if it was sent by looking for a + # sent message with matching subject and timestamp + found = False + try: + results = service.users().messages().list( + userId='me', q='in:sent is:sent', + maxResults=50).execute() + for msg_info in results.get('messages', []): + msg = service.users().messages().get( + userId='me', id=msg_info['id'], + format='full').execute() + payload = msg.get('payload', {}) + body = _extract_body(payload) + if body and rev.body[:50] in body: + sent[rev.idnum] = (body, msg_info['id'], + msg.get('threadId')) + found = True + break + except Exception: + pass + + if not found: + deleted.append(rev.idnum) + + return sent, deleted From patchwork Sat Apr 4 21:29:02 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: 2141 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=1775338374; bh=cH4OmYT6974EI5+74qEIkJXDhvXlKWxnMzZqUmesUwE=; 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=R1QrTSSKXWF2U6mVChviLCDPffznfgu7d5s2+aX5s7LKUdeErf9U+UXsKLoddAJxA gmQ7zY9vH8lLznQK5r5hnb3PdUDZFV9muDuGeT4RfUa4juzaBnA2CKATCVCK2DIyoR vaKPCZ7aIr5m7KKzHCAgOQo1eGlwy+3NMUqHgSKk+E0nQ4BAXmvxtKQ+nCt4tOzqYR ZMooqBPhadjPIS8bJIf8GeyRRmrbAF1Wi/VCTFRDKDXMcoiLqWKZfU+Y0jWD0HkOBn OOCwu6W2xIMQnzpZHGeLSaDTp57jGvobayNkG620uqO3mM5QusSrLfQN3xWlW9U8mf mbgnFIVaG1JNQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id CD5A05FC8F for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32: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 10024) with ESMTP id 1UI8hA3cgEPF for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32:54 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338374; bh=cH4OmYT6974EI5+74qEIkJXDhvXlKWxnMzZqUmesUwE=; 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=R1QrTSSKXWF2U6mVChviLCDPffznfgu7d5s2+aX5s7LKUdeErf9U+UXsKLoddAJxA gmQ7zY9vH8lLznQK5r5hnb3PdUDZFV9muDuGeT4RfUa4juzaBnA2CKATCVCK2DIyoR vaKPCZ7aIr5m7KKzHCAgOQo1eGlwy+3NMUqHgSKk+E0nQ4BAXmvxtKQ+nCt4tOzqYR ZMooqBPhadjPIS8bJIf8GeyRRmrbAF1Wi/VCTFRDKDXMcoiLqWKZfU+Y0jWD0HkOBn OOCwu6W2xIMQnzpZHGeLSaDTp57jGvobayNkG620uqO3mM5QusSrLfQN3xWlW9U8mf mbgnFIVaG1JNQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BA7E26833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32:54 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338372; bh=PjvGtPjWINKMKuUEy65wksxGiCSRwF+F1ZE5oObO+Xk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KOlkThyJpLu2pp5q6CEndln+pcGskKN/W6xktnUjrZ6CmUdQCk5z19ahyb+o9X1FE 6dl0nKuqIaBtbmrhbsA55DwSjETE0+UPIBDafpe4iaVhtPNEcHk987L/yZzR9gq6SJ ++pdaVLCqZEjXXeGBMb7BTFSVqkYGmqSo+AxRWaqjaDxElYKQZ04a3uyCEo54p77I1 snFYz9ok/7ww48BrnPBgWuaqYI1YTwQllMpr0UO8AFwuZOC3rjeU9nJW7aTGBepS7U 9USFEHv8n+OWaTm1tYtOlIDLzsoX25SBaeTxnoY03KwGSLzGeTSjIeZ9X8rheG3eH5 oFgO5J2Tc061w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 870E95E7B4; Sat, 4 Apr 2026 15:32: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 10026) with ESMTP id ll5qqaVb_99K; Sat, 4 Apr 2026 15:32:52 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338368; bh=zZZP7HiyXUiWfA4hkqoAlh+M4jL0VwkwTLiC//D1fyI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dBa0cPAf6gCQ+PiI3W8yFXgrYjJbf/7mACeMydKjKjshH6+q5n10wFgNM/y6EZkc7 C+IEsttRlOSCVXsG6Ds8zWD14rglSBOxuTDabSzwBGhKd2lwvt+jxP4YWAk7WkrlG0 GqrgMQnONLTocMn1Vf2sq1NvESl3R5yRkRSwnM0LlqwFw/2d+QZfwGBwjIBc/G7j3r ZBEDTIlbQ/jNEYKBAE05X50qXykw9MLzlNaQ2Pdz7rFwe+IILvYuifYtBB6RnWNZid oXpRb0SapoCuU3JeLVpyu3Be7mHf6FL8RU9fY6OC/cKc22IA6UXnCqF3O81lTtDW7y GXGQSn86lS33Q== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 1BD5E6833B; Sat, 4 Apr 2026 15:32:48 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:02 -0600 Message-ID: <20260404213020.372253-27-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: EHOSG2OJLKZEJMJE33EZPVMP2HHFX7TV X-Message-ID-Hash: EHOSG2OJLKZEJMJE33EZPVMP2HHFX7TV 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/37] patman: Extend database for review draft tracking and notes 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/EHOSG2OJLKZEJMJE33EZPVMP2HHFX7TV/> 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 schema migrations v9 and v10 and supporting methods: - v9: Add draft_id, status, gmail_msg_id and gmail_thread_id columns to the review table for tracking Gmail draft lifecycle - v10: Add notes column to ser_ver for storing review-handling notes New methods: - series_set_source() to mark a series as coming from review - review_set_draft_id/sent/replied/deleted() for draft lifecycle - review_delete_for_version() for force re-review - ser_ver_set_notes() and ser_ver_get_all_notes() for the handle-reviews skill Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/database.py | 155 +++++++++++++++++++++++++++++++++-- tools/patman/test_cseries.py | 2 +- 2 files changed, 148 insertions(+), 9 deletions(-) diff --git a/tools/patman/database.py b/tools/patman/database.py index 7f33137d0b7..8a96030b68f 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -20,12 +20,20 @@ from u_boot_pylib import tout from patman.series import Series # Schema version (version 0 means there is no database yet) -LATEST = 8 +LATEST = 10 # Information about a review record +# Review status values +REVIEW_NEW = 'new' # AI review generated, no draft created +REVIEW_DRAFT = 'draft' # Gmail draft created +REVIEW_SENT = 'sent' # Draft was sent +REVIEW_DELETED = 'deleted' # Draft was deleted without sending +REVIEW_REPLIED = 'replied' # Author has replied to our review + Review = namedtuple( 'REVIEW', - 'idnum,svid,seq,body,approved,timestamp') + 'idnum,svid,seq,body,approved,timestamp,draft_id,status,' + 'gmail_msg_id,gmail_thread_id') # Information about a series/version record SerVer = namedtuple( @@ -243,6 +251,19 @@ class Database: # pylint:disable=R0904 'timestamp TEXT, ' 'FOREIGN KEY (svid) REFERENCES ser_ver (id))') + def _migrate_to_v9(self): + """Add draft tracking, status, and Gmail IDs to review table""" + self.cur.execute('ALTER TABLE review ADD COLUMN draft_id') + self.cur.execute('ALTER TABLE review ADD COLUMN status') + self.cur.execute('ALTER TABLE review ADD COLUMN gmail_msg_id') + self.cur.execute('ALTER TABLE review ADD COLUMN gmail_thread_id') + self.cur.execute("UPDATE review SET status = 'new'") + + def _migrate_to_v10(self): + """Add review notes column to ser_ver table""" + self.cur.execute('ALTER TABLE ser_ver ADD COLUMN notes') + + # pylint: disable=R0912 def migrate_to(self, dest_version): """Migrate the database to the selected version @@ -277,6 +298,10 @@ class Database: # pylint:disable=R0904 self._migrate_to_v7() elif version == 8: self._migrate_to_v8() + elif version == 9: + self._migrate_to_v9() + elif version == 10: + self._migrate_to_v10() # Save the new version if we have a schema_version table if version > 1: @@ -566,6 +591,17 @@ class Database: # pylint:disable=R0904 'UPDATE series SET upstream = ? WHERE id = ?', (ups, series_idnum)) + def series_set_source(self, series_idnum, source): + """Update the source field for a series + + Args: + series_idnum (int): ID num of the series + source (str): Source value, e.g. 'review' + """ + self.execute( + 'UPDATE series SET source = ? WHERE id = ?', + (source, series_idnum)) + def series_get_null_upstream(self): """Get a list of series names that have no upstream set @@ -688,6 +724,32 @@ class Database: # pylint:disable=R0904 """ self.execute('UPDATE ser_ver SET desc = ? WHERE id = ?', (desc, svid)) + def ser_ver_set_notes(self, svid, notes): + """Store review-handling notes for a series version + + Args: + svid (int): ser_ver ID num + notes (str): Notes text from review-notes.txt + """ + self.execute( + 'UPDATE ser_ver SET notes = ? WHERE id = ?', (notes, svid)) + + def ser_ver_get_all_notes(self, series_id): + """Get review notes from all versions of a series + + Args: + series_id (int): Series ID + + Return: + list of tuple: (version, notes) for versions that have notes, + ordered by version + """ + res = self.execute( + 'SELECT version, notes FROM ser_ver ' + 'WHERE series_id = ? AND notes IS NOT NULL ' + 'ORDER BY version', (series_id,)) + return [(v, n) for v, n in res.fetchall() if n] + def ser_ver_add(self, series_idnum, version, link=None, desc=None): """Add a new ser_ver record @@ -864,7 +926,7 @@ class Database: # pylint:disable=R0904 # upstream functions - # pylint: disable=R0913 + # pylint: disable=R0913,R0917 def upstream_add(self, name, url, patchwork_url=None, identity=None, series_to=None, no_maintainers=False, no_tags=False): """Add a new upstream record @@ -1224,7 +1286,7 @@ class Database: # pylint:disable=R0904 res = self.execute(query) return res.fetchall() - # pylint: disable=R0913 + # pylint: disable=R0913,R0917 def review_add(self, svid, seq, body, approved, timestamp): """Add a review record @@ -1239,11 +1301,68 @@ class Database: # pylint:disable=R0904 int: ID num of the new review record """ self.execute( - 'INSERT INTO review (svid, seq, body, approved, timestamp) ' - 'VALUES (?, ?, ?, ?, ?)', - (svid, seq, body, 1 if approved else 0, timestamp)) + 'INSERT INTO review (svid, seq, body, approved, timestamp, ' + 'status) VALUES (?, ?, ?, ?, ?, ?)', + (svid, seq, body, 1 if approved else 0, timestamp, + REVIEW_NEW)) return self.lastrowid() + def review_set_draft_id(self, review_id, draft_id): + """Set the Gmail draft ID for a review record + + Args: + review_id (int): Review record ID + draft_id (str or None): Gmail draft ID + """ + status = REVIEW_DRAFT if draft_id else None + self.execute( + 'UPDATE review SET draft_id = ?, status = ? WHERE id = ?', + (draft_id, status, review_id)) + + def review_set_sent(self, review_id, body, gmail_msg_id=None, + gmail_thread_id=None): + """Mark a review as sent and update its body + + Args: + review_id (int): Review record ID + body (str): Sent email body text + gmail_msg_id (str or None): Gmail message ID of sent email + gmail_thread_id (str or None): Gmail thread ID + """ + self.execute( + 'UPDATE review SET body = ?, draft_id = NULL, status = ?, ' + 'gmail_msg_id = ?, gmail_thread_id = ? WHERE id = ?', + (body, REVIEW_SENT, gmail_msg_id, gmail_thread_id, + review_id)) + + def review_set_replied(self, review_id): + """Mark a review as having received a reply + + Args: + review_id (int): Review record ID + """ + self.execute( + 'UPDATE review SET status = ? WHERE id = ?', + (REVIEW_REPLIED, review_id)) + + def review_set_deleted(self, review_id): + """Mark a review draft as deleted (not sent) + + Args: + review_id (int): Review record ID + """ + self.execute( + 'UPDATE review SET draft_id = NULL, status = ? ' + 'WHERE id = ?', (REVIEW_DELETED, review_id)) + + def review_delete_for_version(self, svid): + """Delete all review records for a given series version + + Args: + svid (int): ser_ver ID num + """ + self.execute('DELETE FROM review WHERE svid = ?', (svid,)) + def review_get_for_version(self, svid): """Get review records for a given series version @@ -1254,10 +1373,30 @@ class Database: # pylint:disable=R0904 list of Review: Review records ordered by sequence """ res = self.execute( - 'SELECT id, svid, seq, body, approved, timestamp ' + 'SELECT id, svid, seq, body, approved, timestamp, draft_id, ' + 'status, gmail_msg_id, gmail_thread_id ' 'FROM review WHERE svid = ? ORDER BY seq', (svid,)) return [Review(*row) for row in res.fetchall()] + def review_get_by_status(self, status, need_thread=False): + """Get review records with a given status + + Args: + status (str): Status to filter by (e.g. 'draft', 'sent') + need_thread (bool): If True, only return records with a + gmail_thread_id + + Return: + list of Review: Matching review records + """ + sql = ('SELECT id, svid, seq, body, approved, timestamp, ' + 'draft_id, status, gmail_msg_id, gmail_thread_id ' + 'FROM review WHERE status = ?') + if need_thread: + sql += ' AND gmail_thread_id IS NOT NULL' + res = self.execute(sql, (status,)) + return [Review(*row) for row in res.fetchall()] + def review_get_previous(self, series_id, version): """Get reviews from the previous version of a series diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 3b78399d907..e207e8bc173 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -3590,7 +3590,7 @@ Date: .* self.assertEqual(f'Update database to v{version}', out.getvalue().strip()) self.assertEqual(version, db.get_schema_version()) - self.assertEqual(8, database.LATEST) + self.assertEqual(10, database.LATEST) def test_migrate_future_version(self): """Test that a database newer than patman is rejected""" From patchwork Sat Apr 4 21:29:03 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: 2142 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=1775338379; bh=/T4oxHDP1sUrlqiQ1KfC5LbplVJ2qLA5JjkfX0GRU5o=; 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=T8SmFayIwZ0NI08CIaVz/AhFU5lih/HS1Ah19Qku9r4M6MezDq8kYdIJ1VqkKNJ7E LatWSNkzofvlSQBqk+oLcXZued2nQC6OzBVjqb3RFmCb18I8sriZgZvCDlhkucufDV RY3w5mCo3y6RcJsucsWFH6w+pVdIVzZpSx/nO18K1EGhd1FBhTKxBRSpf0QdO/GqlY P09Ik487M0N4BurFOT6AnDEULoXEPukyA8mnwBYyOir28SNbaILqTKNBV6DLJDftPL 6DOz9AUjlIVhMElonC9aIVVPmaKW7q23c/qF08QuoRoFiLLGUJV+h4ymCRbOfmiVsx rG2pzrlvMz2oQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6A6115FC8F for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32: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 10024) with ESMTP id IUKLhOz_Pqjk for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32:59 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338379; bh=/T4oxHDP1sUrlqiQ1KfC5LbplVJ2qLA5JjkfX0GRU5o=; 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=T8SmFayIwZ0NI08CIaVz/AhFU5lih/HS1Ah19Qku9r4M6MezDq8kYdIJ1VqkKNJ7E LatWSNkzofvlSQBqk+oLcXZued2nQC6OzBVjqb3RFmCb18I8sriZgZvCDlhkucufDV RY3w5mCo3y6RcJsucsWFH6w+pVdIVzZpSx/nO18K1EGhd1FBhTKxBRSpf0QdO/GqlY P09Ik487M0N4BurFOT6AnDEULoXEPukyA8mnwBYyOir28SNbaILqTKNBV6DLJDftPL 6DOz9AUjlIVhMElonC9aIVVPmaKW7q23c/qF08QuoRoFiLLGUJV+h4ymCRbOfmiVsx rG2pzrlvMz2oQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 57BBF6833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:32:59 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338377; bh=++uXSoZpQTCDLZffLF2AICqm5o3236y5pK/u3dUIlR8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bV0umPQwHqatorWuBWuF4FtOSTOHxzd3YyhRNtjqvLEdtbIOlQGL14TnyJowKoIB/ Iqhdr+fRDGYoo1Ch3gOTLs/ufZYR/w+JF8w4t/IO5uzhFKuCsXERyywl7+qOJYNaGN +bu7+fUYNRCSA311HEZGCUJjrxq6TfouNDY/PJDUMd7xsR501zqZcX0aGOcSRIvv7r SnO2G70Vy9qYuVXjDl0LfNh4L9FJpRW44gny/GZ7KPnNBkE0fjO/tob6XNTBeBsK2Y jQVuQTDbqiE739v8RsHKobDipo/JNlB1tZKE/i2vgY3qjmgqiVtcGk2vi/M8IsF6Vk qeEyjo0aKUq9Q== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A268D5FC8F; Sat, 4 Apr 2026 15:32:57 -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 UyltlHiQQSqZ; Sat, 4 Apr 2026 15:32:57 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338373; bh=UzzU0IqJGtb710nVrVpWc1nCwt/bXqgEwxZEqRJFjGo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lopQqnorHEb2NtQJrx5Rxo1LUEaM01z3LfD4ibDm1PmdSAtcjAMDYmLW9/ZTK2U5u lqCdzBPD5Bs+YRP+nmRKNwEElB4p878mt4pdajLhdpuAo+M+MPWPyqxI4+sRi2OSOe +Orvy3aJMvLJaJJhn9JV17ttB1QAxy/EbwIWOwVEByCBGCt3O5JTGm8z27MnQ2pbkk rMAFpUxYwDRXx297XftWEjtE6ZVma4hrjQLfExS0DI+GLJbv6X9ULsiIB1FZ1FWSTC Fhg+8kWL5X/V9CO7+suPQteFKiiC0454XsTLpkM0Y19n45NCaG3dKRfnA9r3zOG7PX VStcDtzmQBkOg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 8E9315E7B4; Sat, 4 Apr 2026 15:32:53 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:03 -0600 Message-ID: <20260404213020.372253-28-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: S44PQDQF3P4OWBOEHRPJ3D5VKJ6WGINL X-Message-ID-Hash: S44PQDQF3P4OWBOEHRPJ3D5VKJ6WGINL 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/37] patman: Add notes field to SerVer namedtuple 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/S44PQDQF3P4OWBOEHRPJ3D5VKJ6WGINL/> 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 notes column exists in the ser_ver table but the SerVer namedtuple and its SELECT queries do not include it. Add the field and update all queries to select it. Also fix SerVer construction in cser_helper to include the extra None for the notes position. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/cser_helper.py | 4 ++-- tools/patman/database.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/patman/cser_helper.py b/tools/patman/cser_helper.py index 7979deda6dc..31a5e3663dc 100644 --- a/tools/patman/cser_helper.py +++ b/tools/patman/cser_helper.py @@ -1370,10 +1370,10 @@ class CseriesHelper: updated += 1 if cover: info = SerVer(svid, None, None, None, cover.id, - cover.num_comments, cover.name, None, None) + cover.num_comments, cover.name, None, None, None) else: info = SerVer(svid, None, None, None, None, None, patches[0].name, - None, None) + None, None, None) self.db.ser_ver_set_info(info) return updated, 1 if cover else 0 diff --git a/tools/patman/database.py b/tools/patman/database.py index 8a96030b68f..50b9ea0dca5 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -39,7 +39,7 @@ Review = namedtuple( SerVer = namedtuple( 'SER_VER', 'idnum,series_id,version,link,cover_id,cover_num_comments,name,' - 'archive_tag,desc') + 'archive_tag,desc,notes') # Record from the pcommit table: # idnum (int): record ID @@ -783,7 +783,7 @@ class Database: # pylint:disable=R0904 ValueError: There is no matching idnum/version """ base = ('SELECT id, series_id, version, link, cover_id, ' - 'cover_num_comments, name, archive_tag, desc ' + 'cover_num_comments, name, archive_tag, desc, notes ' 'FROM ser_ver WHERE series_id = ?') if version: res = self.execute(base + ' AND version = ?', @@ -825,7 +825,8 @@ class Database: # pylint:disable=R0904 """ res = self.execute( 'SELECT id, series_id, version, link, cover_id, ' - 'cover_num_comments, name, archive_tag, desc FROM ser_ver') + 'cover_num_comments, name, archive_tag, desc, notes ' + 'FROM ser_ver') items = res.fetchall() return [SerVer(*x) for x in items] From patchwork Sat Apr 4 21:29:04 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: 2143 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=1775338385; bh=cVzLG2EG1Bu2VkCAc+J/e5SgjTfu6wUvqc8yLyFTwBQ=; 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=qwQEd/b9RM8dNzK7MHtuPSa4IRsaV3hAg2Hq/6IpVPYLDe32L0GlzSjl46+FJ8Ou5 VPArGaxY/dBnAQ1Ue6xFel1bRtZZMR17NAULykoOCTI7Ebwy/1qAZnkN9ksgUXMNkU dnZB9JEP5waqXcQkcpzWA8xHiruibqSxtY7OughomoMVLz9L/pMbVmGXkepRrD8RBy Y3Tq/9eQDRnq0g+O72A61ul5ctXrcZVMC4Nw7KzchAkRL2wWn0XqCc7ZwRiK4tQJgH 9RL7JzXo6iGHVA057u3r9uC3qbjdY+RzFzowzyBfrOvgAexI3qsyJv/DQMs4kuy/YR QJHfH5YOLkJSQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2B43A5FC8F for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:05 -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 Qjy_EU64dSKI for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:05 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338385; bh=cVzLG2EG1Bu2VkCAc+J/e5SgjTfu6wUvqc8yLyFTwBQ=; 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=qwQEd/b9RM8dNzK7MHtuPSa4IRsaV3hAg2Hq/6IpVPYLDe32L0GlzSjl46+FJ8Ou5 VPArGaxY/dBnAQ1Ue6xFel1bRtZZMR17NAULykoOCTI7Ebwy/1qAZnkN9ksgUXMNkU dnZB9JEP5waqXcQkcpzWA8xHiruibqSxtY7OughomoMVLz9L/pMbVmGXkepRrD8RBy Y3Tq/9eQDRnq0g+O72A61ul5ctXrcZVMC4Nw7KzchAkRL2wWn0XqCc7ZwRiK4tQJgH 9RL7JzXo6iGHVA057u3r9uC3qbjdY+RzFzowzyBfrOvgAexI3qsyJv/DQMs4kuy/YR QJHfH5YOLkJSQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 19B446833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:05 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338383; bh=q+7UeSKEn0FDtRpivHOwVzEIxaCrDo37fZzs3P67chM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=F8fByOKOup3NQE+bFxRWEjlP9qOPxSQT0OLHbaca3s3bdgIjKR7KYtgeHuMX8+hrm 3nptrfEDFrKazInOyj3+em00a9m7MJ8OUdNmqnSM9/SqQQ7Fx7MTfEQ9evU1JAZrEP aZYTV0l0XMNiNnbZGd+fPZS+35bdwiSNI9akMev02H+Yj4YGEiXhunoBqlHUF/z+Z+ qcfJoOGpOq7+WGCxBNdUbF4XEhtnnOGz+xh3U08MWjRMY05Jd+xVp3zqDuPRTOg4fB pW4ZQh87EmPhLoFXz1ET23e6QSlqR8WLe4eUcAxDyQ5qN+tvo12des7XF4NoJcMLFJ RZdgZVC2nAPsw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1057E6833B; Sat, 4 Apr 2026 15:33: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 10026) with ESMTP id XFz_HiWKzBTV; Sat, 4 Apr 2026 15:33:03 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338378; bh=7FsehO4bxwavjCe9pw/Kb6KZwhhctHKtn1S61wrGFLM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GD/H4Lmh+rWBoPoMt7nVWn5FFYfCR7Ch8GPDtr8yu2UsMMKC14nreLSJnDXkJnWKK mM6frs68p3f6keGW/TixoA2lm03D3duUgDXvTdO2IGs0fgH6G4A4DzAiVwRe2ILxYt gsxs6+qgu7Vordf3O+fbaFnR1oWf+fKbRxWqdNpp2eSYLJ6S8/ynwEmbk0z1lhj7Sy k7+u0XUaVBfNwqqXSpj4t2EgpGswXG6QsOl95BSMBeTc2Ho3mU28xjeQMzQMJvlkfo 8alK4+xmoSYiidma5LuGv869r5cvnizGwcXzFXbAxF0YqXQ7rQmkVQHWhNSYJN/6ot QENoN79rIAvSQ== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id A406D5E7B4; Sat, 4 Apr 2026 15:32:58 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:04 -0600 Message-ID: <20260404213020.372253-29-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: WSITVR4SVFO6FPVZ7IHKXWEZJVMYRHAT X-Message-ID-Hash: WSITVR4SVFO6FPVZ7IHKXWEZJVMYRHAT 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/37] patman: Add 'series info' command to show version details 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/WSITVR4SVFO6FPVZ7IHKXWEZJVMYRHAT/> 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 'patman series info' command that displays detailed information about a series including description, upstream, and for each version: the patchwork link, per-version description, cover letter name, patch list with state, archive tag and review notes. This helps diagnose autolink issues by showing what description each version has stored, which is what patchwork searches use. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/cmdline.py | 1 + tools/patman/control.py | 2 ++ tools/patman/cseries.py | 47 ++++++++++++++++++++++++++++++++++++ tools/patman/test_cseries.py | 42 ++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index edefa778446..bee9fd21483 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -294,6 +294,7 @@ def add_series_subparser(subparsers): series_subparsers.add_parser('get-link') series_subparsers.add_parser('inc') + series_subparsers.add_parser('info') ls = series_subparsers.add_parser('ls', aliases=['list']) _add_archived(ls) diff --git a/tools/patman/control.py b/tools/patman/control.py index 3ce9736d6ba..9ba9b6e0b8e 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -199,6 +199,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 == 'info': + cser.show_info(args.series) 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 1dd1550f367..e272a5839fc 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -861,6 +861,53 @@ class Cseries(cser_helper.CseriesHelper): if dry_run: tout.info('Dry run completed') + def show_info(self, series): + """Show detailed information about a series and all its versions + + Args: + series (str): Series name, or None for current branch + """ + 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)'}") + + versions = self.db.ser_ver_get_for_series(ser.idnum) + if not isinstance(versions, list): + versions = [versions] + + 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)'}") + if sv.name: + print(f" Cover: {sv.name}") + if sv.archive_tag: + print(f" Archive tag: {sv.archive_tag}") + + # 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)") + def set_upstream(self, series, ups, dry_run=False): """Set the upstream for a series diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index e207e8bc173..5974c69253a 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -4438,3 +4438,45 @@ Date: .* # Future 3 weeks when = datetime(2025, 3, 31, 10, 0, 0) self.assertEqual('in 3w', wf.friendly_time(now, when)) + + def test_series_info(self): + """Test the series info command""" + cser = self.get_database() + + # Create a series with upstream and two versions + cser.db.upstream_add('us', 'https://us.example.com') + series_id = cser.db.series_add('test-info', 'My test series', ups='us') + svid1 = cser.db.ser_ver_add(series_id, 1, link='12345', + desc='First version desc') + 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, + patch_id=None, num_comments=0), + Pcommit(idnum=None, seq=1, subject='Add widget tests', + svid=svid1, change_id=None, state=None, + patch_id=None, num_comments=0)]) + + # Add notes to v2 + cser.db.ser_ver_set_notes(svid2, 'Fixed review feedback') + cser.commit() + + with terminal.capture() as (out, _): + cser.show_info('test-info') + + output = out.getvalue() + self.assertIn('Series: test-info', output) + self.assertIn('Description: My test series', output) + self.assertIn('Upstream: us', output) + self.assertIn('Version 1:', output) + self.assertIn('Link: 12345', output) + self.assertIn('First version desc', output) + self.assertIn('Patches: 2', output) + self.assertIn('Fix the widget', output) + self.assertIn('Add widget tests', output) + self.assertIn('Version 2:', output) + self.assertIn('Second version desc', output) + self.assertIn('Notes: Fixed review feedback', output) From patchwork Sat Apr 4 21:29:06 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: 2144 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=1775338396; bh=4vCCrgYcDobRAtvTzvOIfYB1vFkYucyS2zIUDhBK7fA=; 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=IM/W2vTNHpJWXaM3byUfNfLeerIDgutzQBkW8VrRLi1xEfMjh5k98aq00QNHgBol/ 1bBJJ8QUU9yJNlivycdWdjTqff30eAraIdEUzXWffIGHWom+XmlVKJaMVlhnId/LBC I256N//dyWhKYlFdUAN8gBo+vlDS7dwAZ0LNDH8jdDGhXSrPtdrHOrTScB2tBG6JWI YCerYYKPdoleUsYyfxDOati8fx7EtbhcVi7W/eElRGnUR12NNc/ROcbphO7PvwZSkx juyazqCRqt1QtGFw52sD9Gztz4SglxKkmKj+7R/JjKa88Osz0uecIbtBZmYGXw1UnT CGqdhJo9Vu7yA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 82F5768F53 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:16 -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 EVN-2yBJMZda for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:16 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338396; bh=4vCCrgYcDobRAtvTzvOIfYB1vFkYucyS2zIUDhBK7fA=; 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=IM/W2vTNHpJWXaM3byUfNfLeerIDgutzQBkW8VrRLi1xEfMjh5k98aq00QNHgBol/ 1bBJJ8QUU9yJNlivycdWdjTqff30eAraIdEUzXWffIGHWom+XmlVKJaMVlhnId/LBC I256N//dyWhKYlFdUAN8gBo+vlDS7dwAZ0LNDH8jdDGhXSrPtdrHOrTScB2tBG6JWI YCerYYKPdoleUsYyfxDOati8fx7EtbhcVi7W/eElRGnUR12NNc/ROcbphO7PvwZSkx juyazqCRqt1QtGFw52sD9Gztz4SglxKkmKj+7R/JjKa88Osz0uecIbtBZmYGXw1UnT CGqdhJo9Vu7yA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 72E386833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:16 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338394; bh=mE0MNDLGIs+BTvO+0qVKXUWpkzkIaotKd2fACMYJUtQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WqUxtKjMQdBOP7qEHqhPCLPHxnKjFBtTudCfaIBnZEM0zbDsRgZ4yOWYZqOqOm2+k 0RkebU01KH1fQ1QIVTrT8+1xi76ApXkQdiQt2ndpzoFot32Bc4u5nIaNE41k3nJsH3 +JRozP98lpoo+5EPEwnlp5vMe6NknmSmsHQMVOb8TFNmYddAtJ4ZEx1MH02W9wuArN EmlEbWDw3eKv585B9T3avZB+uevpGT2a8gOfCCzd1M5bWsA9XmrblAR3gjjy+B231m YSmAIaB1h/9w2WMLlFLRVrgnYeX1DRG6TpqDzZhibAEf2+X6BUvHh+UttAmwAwDFYi m7QoHC6zVTDsA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id CC98964DB2; Sat, 4 Apr 2026 15:33: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 10026) with ESMTP id hcHubisqYoKu; Sat, 4 Apr 2026 15:33:14 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338390; bh=3oLXQywGn1auiJhV4WkVeA6KyraOgRvgqJJnZqb1PeY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=r238QPvQnyRI+q2Yz8E+9b3jiHZcIJHu5qAT15dbb1evqBIAtEryMRPkbmuHmpGYT hHZ0rg1dCDEYuGgFW+QiMZCYrHb029GahhB8ZkAP/R4tDpij5Ibr0x8ORfbcOe8lf1 rV3Zi4phBB+HGLNK/fZ5KsjHtmuhzOL0iy44+XDVE8JN0xd8yERWggCxIn4p7jJBCP p3V14yFXcp2LMIU3EEc5raMMO6u9dMz7f5S50jwSYzjeXwZr3B0BUmMhqWBtidTNzs FblKnnkhrW6IExmpeMylXSedWPnZiRL8Fju2eHU14RP2z+QbQJUJYWhE6bSYkDEnVG T4NvPrWEDs90g== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 687CE5E7B4; Sat, 4 Apr 2026 15:33:10 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:06 -0600 Message-ID: <20260404213020.372253-31-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: CGENAFFWTWEYCLIQHKDG6PKKYEGVQVMP X-Message-ID-Hash: CGENAFFWTWEYCLIQHKDG6PKKYEGVQVMP 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 30/37] patman: Add save-notes/show-notes and review integration 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/CGENAFFWTWEYCLIQHKDG6PKKYEGVQVMP/> 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 series subcommands for storing and displaying review-handling notes: - 'patman s save-notes' reads review-notes.txt and stores it against the current series version in the database - 'patman s show-notes' displays notes from all previous versions Also wire up the review command to handle --learn-voice and --sync modes, and use _setup_patchwork() for consistent patchwork resolution. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/control.py | 21 ++++++++++++++------- tools/patman/cseries.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/tools/patman/control.py b/tools/patman/control.py index 9ba9b6e0b8e..e664c93dafd 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -105,6 +105,7 @@ def patchwork_status(branch, count, start, end, dest_branch, force, single_thread) + def _setup_patchwork(cser, pwork, ups, pw_url): """Set up a Patchwork instance from upstream and project settings @@ -230,6 +231,10 @@ def do_series(args, test_db=None, pwork=None, cser=None): cser.remove(args.series, dry_run=args.dry_run) elif args.subcmd == 'rm-version': cser.version_remove(args.series, args.version, dry_run=args.dry_run) + elif args.subcmd == 'save-notes': + cser.save_notes(args.series, args.notes_file) + elif args.subcmd == 'show-notes': + cser.show_notes(args.series) elif args.subcmd == 'rename': cser.rename(args.series, args.new_name, dry_run=args.dry_run) elif args.subcmd == 'set-upstream': @@ -365,7 +370,7 @@ def patchwork(args, test_db=None, pwork=None): cser.db.patchwork_delete(args.remote) cser.commit() ups_str = f" for upstream '{args.remote}'" if args.remote else '' - tout.info(f'Deleted patchwork project{ups_str}') + tout.notice(f'Deleted patchwork project{ups_str}') elif args.subcmd == 'ls': cser.project_list() else: @@ -419,11 +424,13 @@ def do_review(args, test_db=None, pwork=None, cser=None): try: cser.open_database() - ups = args.upstream - if not ups: - ups = cser.db.upstream_get_default() - pwork = _setup_patchwork( - cser, pwork, ups, args.patchwork_url) + # Resolve patchwork URL + if not pwork and not args.learn_voice and not args.sync: + ups = args.upstream + if not ups: + ups = cser.db.upstream_get_default() + pwork = _setup_patchwork( + cser, pwork, ups, args.patchwork_url) return review_mod.do_review(args, pwork, cser) finally: @@ -480,7 +487,7 @@ def do_patman(args, test_db=None, pwork=None, cser=None): elif args.cmd == 'patchwork': patchwork(args, test_db, pwork) elif args.cmd == 'review': - do_review(args, test_db, pwork, cser) + ret_code = do_review(args, test_db, pwork, cser) or 0 elif args.cmd == 'workflow': do_workflow(args, test_db) except Exception as exc: # pylint: disable=W0718 diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index e272a5839fc..3af9ef19eab 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -7,11 +7,13 @@ import asyncio from collections import OrderedDict, defaultdict +import os 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 import tools from u_boot_pylib import tout from patman import patchstream @@ -861,6 +863,40 @@ class Cseries(cser_helper.CseriesHelper): if dry_run: tout.info('Dry run completed') + def save_notes(self, series, notes_file='review-notes.txt'): + """Save review-handling notes for the current series version + + Args: + series (str): Series name, or None for current branch + notes_file (str): Path to the notes file + """ + if not os.path.exists(notes_file): + raise FileNotFoundError(f"Notes file not found: {notes_file}") + + notes = tools.read_file(notes_file, binary=False).strip() + ser, version = self._parse_series_and_version(series, None) + svid = self.get_series_svid(ser.idnum, version) + self.db.ser_ver_set_notes(svid, notes) + self.commit() + tout.notice(f"Saved notes for '{ser.name}' v{version}") + + def show_notes(self, series): + """Show review-handling notes from all versions of a series + + Args: + series (str): Series name, or None for current branch + """ + ser, _ = self._parse_series_and_version(series, None) + all_notes = self.db.ser_ver_get_all_notes(ser.idnum) + if not all_notes: + tout.notice(f"No review notes for '{ser.name}'") + return + for version, notes in all_notes: + terminal.tprint(f'\n--- v{version} ---', + colour=terminal.Color.YELLOW) + print(notes) + print() + def show_info(self, series): """Show detailed information about a series and all its versions From patchwork Sat Apr 4 21:29:07 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: 2145 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=1775338399; bh=uGD1Ibw+ZkAvWCIZswgEuAhhs8urn2rpGHBp9gAqdjU=; 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=l3SBFw1ru6lKQuZDy0qvGPMmiN6Hj4JMXVMv39kRTTWKvJkA4YxviZk58EdGBQFeO Dkl5RIYn79B0CoX797mwCSuOdPEseOmcgr4SsOMvuSs8J4jh95k1qcwSB6i+ugKuCA xcTWL8ZFeMYWS8PdkHVaCycNsSpDKHy7O8TFUz6n64EcYZ0jebAQlkba8/aNWW6DcH Dhr4qepwMmWHGf9WY5ErkkbLlvloqqTBOc9AqbMijVLzTtS/sllDLQhGXZw50lqiE7 czyviIFOT6hbM2jkvFe8ctF/aMizUvnrF3nTY9tcyYItVT/TpfNXDKRv/Q+GeB+78S xtZ0GCcahL4RQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0D12364DB2 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33: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 ItwN_gKpkoRO for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:18 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338397; bh=uGD1Ibw+ZkAvWCIZswgEuAhhs8urn2rpGHBp9gAqdjU=; 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=SDp+xhKNiqUhvt5ZCDJ+atnV88zrpQMyspAQSUgI+bpAed/UDTmHkg0M/anooembG KTwU11/O0phwG6K4i20O+iUp8VDamfhSFMKJdaGwmhsidtOx14Y4SVrXLV8BtORKVo AhAvGe3aEsjLepkNkQz5WBfdvFub31M5z77U5R/zmECr97E+dMZw9GW0QrdEnvgeSA qDnUziQ2/ekVROXz6WMkpqKl+Den7mRZdmijQ9o3L9+Ei1UjXlDqvEzHPV22v6H5NA jjiz/+8dOsr06+ic+VPNWQ3YIH3aqQmvTgL8YpQj8MRHcghJ8r2PII8YnWsUpy+SZA mrxn6fUaQx0JA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E5C896833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:17 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338396; bh=zcpX9nTNmgPTAWM2YRG1A0v4P7CLAyOEMeQ2Y7HHwGk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OUFFVwYYwyTNW8Z3l5fQQKd/zfxU/vjUtJcSMj3uJj6VjBhhmnVCek2jSnfjSQUKJ W7+q5/1kYNL6fFNv9snK/YxwuYLTSYZ8Bv0saCOhFAsRhwpaG3QVKHbP5sNkyEEztT M4IzQpWu2pbcpX4N2A61NHV+o8CCwIzbMayf6MPQvfAQqmjf+PILqBOHUwUIOg7sp7 CMGM+/wmIma6QN0lWE3oYJyAkqzOCI6BbUTC3A9/wiZtklWpQRsnb+Vk2czBZZVY1Y iR0uysywDM917Fb4KHT1A4//jfzOpVQMEpRyPjKjecZPyI1dENFKvWQ/tanx44qRtn YmFTJs9nzkkuQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5A07664DB2; Sat, 4 Apr 2026 15:33:16 -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 jnhGH4pj0Dc4; Sat, 4 Apr 2026 15:33:16 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338396; bh=qT3rqAOogXx8rlLD+GP1BI5MwgzgfiG62yKrCmFe6Fw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NRqKsD9oDLniHdW5qAAAu3PAb2lMpostwTws+0CZVnU+gYdQeG4Oto8Vn9cr/r7pf /Xo36yuVv/tv/rtQZamsf4+F/0dHXEcpigOX9dbgIBvPPa54rFBTCUaqt+a5fLScal ilToXQSuUK2Rz5koajYSA5Bel9S+EPK5Fkx6HuPOkDeTrxTi/H96e7u9t+rXB0k16h /2SMVH1l+3mWyB86ZoHWdRK2bLV9eulepfXKqq7c4mMc0lfw5zzPoOn62/S3SsHz0m 8b2eVU/N3hUE+j1iFZEgxbF5M+t2fBXEwrNSGn6PxJQlCU9ysd/GSiX6fYj2XUwQbn qA14MEe80oJsA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id E4C7C5E7B4; Sat, 4 Apr 2026 15:33:15 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:07 -0600 Message-ID: <20260404213020.372253-32-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 7UPHUQIRA5WZ3DL5ES5XHPNNOKQWEOI2 X-Message-ID-Hash: 7UPHUQIRA5WZ3DL5ES5XHPNNOKQWEOI2 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 31/37] patman: Fix import ordering in control.py 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/7UPHUQIRA5WZ3DL5ES5XHPNNOKQWEOI2/> 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> Place u_boot_pylib (third-party) imports before patman (local) imports to satisfy pylint C0411 (wrong-import-order). Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/control.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/patman/control.py b/tools/patman/control.py index e664c93dafd..28d53ef60d2 100644 --- a/tools/patman/control.py +++ b/tools/patman/control.py @@ -17,6 +17,11 @@ except ImportError: # for Python 3.6 import importlib_resources as resources +from u_boot_pylib import gitutil +from u_boot_pylib import terminal +from u_boot_pylib import tools +from u_boot_pylib import tout + from patman import cseries from patman import patchstream from patman import review as review_mod @@ -25,10 +30,6 @@ from patman import settings from patman import status from patman import workflow from patman.patchwork import Patchwork -from u_boot_pylib import gitutil -from u_boot_pylib import terminal -from u_boot_pylib import tools -from u_boot_pylib import tout def setup(): From patchwork Sat Apr 4 21:29:08 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: 2146 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=1775338401; bh=KcaTvZT/zj6mO1JaN/dXzv8SRixj09+ThZ6jz85f0B0=; 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=ur0w6pyzoqfmLlhRtlFt2UVGcWhvWTyx0rAAZYzUZYoKFJNRUNhLbk/pUs9y71MTD KhIstySSMbZ8+q2dPkJCORLY85jCKr51EWxncp4hwD2dAA8hUHpaUygU3wo12No/3z yz5UYEBtwaYUk8c4Cc+E3GObI/Qi/gm0x3o1rXTphW9IVfgWHRfJWSoFxFnv4K7FJe jdWc8GEAUXd/cw5dpNw+Mj39FoxjjxQLqKX1ccA/RwqgeUP+GHJPhqXsZKBjSV+GLV ymmA41qXECtK5qcrzyDuXR6OfxpmSiansN6vJyrDXRK5LDejJXGM5Gnb1kXbYYM0rH AZw9MvjWMgeiA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 757B868F53 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33: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 10024) with ESMTP id pMoaHVf_SIn5 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:21 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338401; bh=KcaTvZT/zj6mO1JaN/dXzv8SRixj09+ThZ6jz85f0B0=; 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=ur0w6pyzoqfmLlhRtlFt2UVGcWhvWTyx0rAAZYzUZYoKFJNRUNhLbk/pUs9y71MTD KhIstySSMbZ8+q2dPkJCORLY85jCKr51EWxncp4hwD2dAA8hUHpaUygU3wo12No/3z yz5UYEBtwaYUk8c4Cc+E3GObI/Qi/gm0x3o1rXTphW9IVfgWHRfJWSoFxFnv4K7FJe jdWc8GEAUXd/cw5dpNw+Mj39FoxjjxQLqKX1ccA/RwqgeUP+GHJPhqXsZKBjSV+GLV ymmA41qXECtK5qcrzyDuXR6OfxpmSiansN6vJyrDXRK5LDejJXGM5Gnb1kXbYYM0rH AZw9MvjWMgeiA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 64DED6833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:21 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338398; bh=3sKh1rj6sXdUNSTTCbK9b2W+ybK/ClBkyo3R8HCiTq0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fLnTJNHIb4cTKESa5ca5D3bxdliJHMdxrL7D6PS6+37aHFVcNbi7Hgi7gNV99dTuS 4BXodsRBba4Ms62uaIP3dXM8ECW9Ql3hAmsjbEeXVr1qquWOu1vYIkb7G/3i/6vvOU geB6o409YW7XYLboh/YL9XH977ap5QpiLnAiQWLRtGUIYdmwfMkT5FrjJDWDLn84lz DloRXnSzzXjJhluDlc8Om4GzizwxmPtuX1KZVacttoKfnKzI4r3+I2KTcJSsftW7iF GJMoZLhnSXU2aEimYIEqM4q4lUTqyz1AU1pOK+ZHJBY5d5AuXQwrzMIrv/C9BtbjN0 lruC6Ey0c90Og== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id EE52669015; Sat, 4 Apr 2026 15:33:18 -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 OJ7N2wH4VqR0; Sat, 4 Apr 2026 15:33:18 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338397; bh=zbs6oSIHI986i/82jMlolJYk1x28S5c/QC4oxAFBgro=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IG0B1IS48WYqlyUfJ7lQ8JE5I/XqVrrtXkxtk82h1fOp/DZ03rmYt/ZVdRXpUFm/h 6t3QSwfuxTXcdZAQbyr/+2SZmwQ5uOrIqAIJ1tffOL/1MrHfNYl4E2ExGKhIjXRgSn 5yHS8AMhWkrrsT9o5+m+UdQordbWbwIo315Go4WA7GLxc1arr6mP9TSXW8KMgub74e 4Xeazrlb6Ei8jl8w2f4f80CAwnQx/F2r2ugfzws3dG2ewpYG49WYF5FhpWZeFM+SjC 2Nc6qZvIV2u4HeSGR6VYFhr2YPVFPQ4zGFKxzTtTXQwXW8FBDSb1/oQUv/HM1IbYnU OUbvUyiGS650A== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 72C985E7B4; Sat, 4 Apr 2026 15:33:17 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:08 -0600 Message-ID: <20260404213020.372253-33-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: NFT25AHUTUPCERYERP33AWLOT22ZGYZ3 X-Message-ID-Hash: NFT25AHUTUPCERYERP33AWLOT22ZGYZ3 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 32/37] patman: Add patchwork method to fetch user comments for voice learning 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/NFT25AHUTUPCERYERP33AWLOT22ZGYZ3/> 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 fetch_user_comments() to the Patchwork class, which retrieves recent review comments by a given user email. This is used by the voice-learning feature to build a style profile from past reviews posted to patchwork. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/patchwork.py | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py index 3e8f7c6c62c..5613cbb3383 100644 --- a/tools/patman/patchwork.py +++ b/tools/patman/patchwork.py @@ -12,6 +12,7 @@ import aiohttp from collections import namedtuple from u_boot_pylib import terminal +from u_boot_pylib import tout # Information passed to series_get_states() # link (str): Patchwork link for series @@ -808,6 +809,54 @@ On Tue, 4 Mar 2025 at 06:09, Simon Glass <sjg@chromium.org> wrote: cover = COVER(cover_id, len(info), cover['name'], info) return cover + async def fetch_user_comments(self, client, user_email, max_comments=20): + """Fetch comments made by a user on recent patches + + Paginates through recent patches for the project, fetching + comments on each until the target count is reached. + + Args: + client (aiohttp.ClientSession): Session to use + user_email (str): Email address to match + max_comments (int): Number of comments to collect + + Returns: + list of str: Comment body texts + """ + comments = [] + page = 1 + per_page = 50 + patches_scanned = 0 + + while len(comments) < max_comments: + patches = await self._request( + client, f'patches/?project={self.proj_id}&order=-date' + f'&per_page={per_page}&page={page}') + if not patches: + break + + for patch in patches: + if len(comments) >= max_comments: + break + patches_scanned += 1 + tout.progress( + f'Scanned {patches_scanned} patches, ' + f'found {len(comments)}/{max_comments} comments') + patch_comments = await self._request( + client, f"patches/{patch['id']}/comments/") + for comment in patch_comments: + submitter = comment.get('submitter', {}) + if submitter.get('email') == user_email: + content = comment.get('content', '') + if content and '>' in content: + comments.append(content) + if len(comments) >= max_comments: + break + + page += 1 + tout.clear_progress() + return comments + async def series_get_state(self, client, link, read_comments, read_cover_comments): """Sync the series information against patchwork, to find patch status From patchwork Sat Apr 4 21:29:09 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: 2147 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=1775338406; bh=tt5cjM9qmbnpGxeOkCTIYzpQT/amy4ys+vrtHSGnYUI=; 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=M33CNn0gIoVkLLeXmylVtEX40tx4X4FIR/3iVy5XeXok2PtHNyY8P8TxIgudN2z28 AWL3dbK0T0frzWQ/ViK6xtaYAiTzlmoJf4tpvUiblUPj+mUWY0gNrWyJPYZsmAG4za Ot83JHvg3OsT5eLbBgwex92Jcf7NKN4e+FKzh2n2dHqtxj7bxd9vJbPn+uplV6IF5t DZCzeigaofBW06pXUB+QsxDi+ob9vJyC3hL+GLqdESW1YDcPQRQh5vP/xnQV9BLZtv CwsZnl5zf9hlnKZkbno0RDMk/PR00zh445EQpPnrecceCthjzTr5NmP2a+pW3c9hLu ed+OkuZBb+ICA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 007F068F53 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33: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 10024) with ESMTP id 3JuZNlwFuB2G for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:25 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338405; bh=tt5cjM9qmbnpGxeOkCTIYzpQT/amy4ys+vrtHSGnYUI=; 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=BWiCHg6ma8GHSvFdsv8KDTpOWawkPW50YMxbsH1KKLWaX4q6RhcneLUDikWjJI9aX nX3C9CmFY/KFYT7EMPycc8xzSMh+9RKWdSUkmvEQjDUZFyP6W4RZycZIq0XIilb/yQ 9YmRsasNR/83eEvpcHS/o3vXSKeaeOuO0fGhAvFv2mJxkZCyjO5h7ghqqzUUT3aQx8 XHKYuRmlMJthFVzZHjzfJvNhTDbgWSo0wrC9ui5ra3B2v36yiQU6uDgsxxXVoaGvct 2+npxmAAM2g7Cn/cdFCSo2k7w9Ekd39JAY9e/1jhix6IhvlonIJc+90rt1CYXW6q9N jJUbmp76IOrqw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E3FB56833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:25 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338403; bh=9nGmM9FmnFfo8dJXK1z8d8ZAdKDg9uQWT2S/LpJ2j94=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=J9YBDX5t56TolmuED0oRYoK/dsCyXG0nf1rCJORAXev0HCHpW2ZJHoAC9ZnqpvEA4 STcQrNaD7Hlutp9y1MztILtrUdK3gP/iDNooHN6tdkV2O9jaKxKoxeQMdS54GqjPFE NZEJ6z1jO7qrRg934ARaPPPt6K1Jqy7yUsUrxehFDB1xceDy0do67S0stnHFAZxteO WzZYw3niCu2eRJzwYXuS2LcGsuOXifX/t7nFNrcXgaxWCA4+1tY4YB28f8enuFMwUd oKYXjemvGXPt/WFt3PMAYIQ3gtbBUN19O1TOvY0FU77UUpRlTfuNaYQvUXc5+PLYSw cxYYuYb3mw94g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E5DFA64DB2; Sat, 4 Apr 2026 15:33:23 -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 LwT4TpfYpbVR; Sat, 4 Apr 2026 15:33:23 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338399; bh=9eqyueivYSLC+2tFN3qFQtGDPPa4gVy8Xvc9pBghwfY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XvLWvjSC/6y/5jLCKUIL5GRM5ciJIxSkTxXpDfGtin0+ehpCRjpOijo4lMdVkM5J6 eeFRPSDdrCg+l7pjpqltrZhWO5bc9XfIkY/NQGnVscYM3RTrjyeG1ndiCXCXy5mRbp 3xNGyW7OIXbnrJsJ2OJy5dIcjNVN/dXlbTQTFuRh/+ed8O/my+oLVw9RDmODsS6WAO QJot2DkQKPIjdS14nLZKMJqFuuX2Ci6mthV0/EFaQ0eWxN9KFv4KNu0VAbbWSyDegV 5eeCZPwjFIg+LO9dSJ5aFAS/2kGjM6sbok7Qc+OAdK/lxeGVcl8QRPC0+pjqXqNkP0 f401PdFDVXvYg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 0851F5E7B4; Sat, 4 Apr 2026 15:33:18 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:09 -0600 Message-ID: <20260404213020.372253-34-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: GPT2GGY2XTTTSZVURESKL47AHEVZUBBK X-Message-ID-Hash: GPT2GGY2XTTTSZVURESKL47AHEVZUBBK 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 33/37] patman: Add review and notes command-line arguments 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/GPT2GGY2XTTTSZVURESKL47AHEVZUBBK/> 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 new arguments to the review subparser: - --gmail-account, --signoff, --spelling for draft creation - --learn-voice/--voice-count for voice profile learning - --sync for draft status synchronisation - -f/--force for re-reviewing completed series Add series subcommands: - save-notes: store review-handling notes in the database - show-notes: display notes from all previous versions Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/cmdline.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py index bee9fd21483..cbb6e4742b7 100644 --- a/tools/patman/cmdline.py +++ b/tools/patman/cmdline.py @@ -324,6 +324,13 @@ def add_series_subparser(subparsers): series_subparsers.add_parser('rm') + snotes = series_subparsers.add_parser('save-notes') + snotes.add_argument( + 'notes_file', nargs='?', default='review-notes.txt', + help='Path to the review notes file (default: review-notes.txt)') + + series_subparsers.add_parser('show-notes') + sup = series_subparsers.add_parser('set-upstream') sup.add_argument('upstream_name', nargs='?', help='Name of the upstream for this series') @@ -548,6 +555,9 @@ def add_review_subparser(subparsers): review.add_argument( '--create-drafts', action='store_true', help='Create Gmail draft emails for each review') + review.add_argument( + '--gmail-account', type=str, default=None, + help='Gmail account to create drafts in (e.g. user@gmail.com)') review.add_argument( '--no-cover', action='store_true', help='Skip reviewing the cover letter') @@ -560,6 +570,29 @@ def add_review_subparser(subparsers): review.add_argument( '--apply-only', action='store_true', help='Only download and apply patches, skip AI review') + review.add_argument( + '--signoff', type=str, default='', + help="Sign-off for reviews with comments (from .patman settings)") + review.add_argument( + '--spelling', type=str, default='British', + help="Spelling convention for review comments (from .patman " + "settings)") + review.add_argument( + '--learn-voice', type=str, nargs='?', const='gmail', + choices=['gmail', 'patchwork'], + help="Analyse past reviews to build a voice profile " + "(from 'gmail' or 'patchwork', default: gmail)") + review.add_argument( + '--voice-count', type=int, default=20, + help='Number of review emails/comments to collect for ' + '--learn-voice (default: 20)') + review.add_argument( + '--sync', action='store_true', + help='Check if review drafts have been sent and record the ' + 'final email content') + review.add_argument( + '-f', '--force', action='store_true', + help='Force re-review even if the series was already reviewed') return review From patchwork Sat Apr 4 21:29:10 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: 2148 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=1775338410; bh=xbHjrSDHSuD3+ZKgtU2a03tlOfbz1mWKKLhmxk+zS+k=; 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=wwPZDd9YYm73Gxk7MWvYgleQtlIqf2XLYDKNdQ12qE7oRbeE8t1uy8BESyG0bLD5q kWVVXVj7KW+AIWtsTNiFJ9JJpC1lgGjXbloeZGEQsraVFiOPzG0McUaY5YrM7Jgnpz C0/KB8tws9TLdnX855pgaQ42zDsN5hx5irLO+E+vagh7NEvWglwCrKv9/yWBbqAY4C smVbMwS1E2XkbUNaCSDGUt5k6+GISBPVf6rY3g6JC2xgVcH2USWVWZLn6/KJj0jGS7 Q76q3e2+TPMP9PIEKHIWCAUhtuV3WOb6A591QtuB7cl5Fj+K0vLJmJ7csOb3HeJBP3 CdeyhXEebf/4w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 7788D69002 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33: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 OZznrAw_nhZE for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:30 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338410; bh=xbHjrSDHSuD3+ZKgtU2a03tlOfbz1mWKKLhmxk+zS+k=; 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=wwPZDd9YYm73Gxk7MWvYgleQtlIqf2XLYDKNdQ12qE7oRbeE8t1uy8BESyG0bLD5q kWVVXVj7KW+AIWtsTNiFJ9JJpC1lgGjXbloeZGEQsraVFiOPzG0McUaY5YrM7Jgnpz C0/KB8tws9TLdnX855pgaQ42zDsN5hx5irLO+E+vagh7NEvWglwCrKv9/yWBbqAY4C smVbMwS1E2XkbUNaCSDGUt5k6+GISBPVf6rY3g6JC2xgVcH2USWVWZLn6/KJj0jGS7 Q76q3e2+TPMP9PIEKHIWCAUhtuV3WOb6A591QtuB7cl5Fj+K0vLJmJ7csOb3HeJBP3 CdeyhXEebf/4w== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 66BE56833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:30 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338408; bh=BsFMJedx4GYx4VtoYUSidZ4SmBy5Ienj2jmTdo6BYVA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=d5OXb4bUCKE4fqUhYECdkCIRc6eWUoyWEja91xD6/7JnPCctjyKKn3J+AKPXVvpk0 Sbl8q9K4uInD4P2jnZJLO4hjDY52yQ30mWI5FLWoj/TeC+EUsppAikXius77UXhCEK mOb1GYvtEJkeQYWTS5cM329xwSH/3gMv42WecKwofiafpFUXQGQJsxoWWl0kLE4JNk mEJAhV+GVgPS79YrcdFYlpfJ+Pwz5pzhndi/tVsY+PthleQTAugE0k8XvPqkgWNtjQ xtLOqP1SSonikZvwdHsQPoOjjJHKiDdikdl3o+iFB2B422G7brReP7FYtgGmuQPx8x PTbBsb0LfHiJw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C4AF264DB2; Sat, 4 Apr 2026 15:33: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 10026) with ESMTP id oqm1o-o8sHIT; Sat, 4 Apr 2026 15:33:28 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338404; bh=zNKDn/cdoZn+h2yspeQVGE4NGhG3ri1lHo0jDMzaEic=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nOieGkIh+qNKFe7gQwErThimYa9T8Ndyg0BxDc+aGDMtRsccg4le3ODAxLOy8H88Q Lxo6CedD5EwYuJ6P/1sf9xyPxhHYJ5TjSXxS3dLMmOKefea6Io1qw2f9shszdYtuIP 4H/AO65a5lEujnsbSx5rdyMRWAmi3yXzyy3nUg49Egd3yW5hCDIiO6W0Pc0uT0evwA h4aaZjXKSIUVSKn7e9aSNYBq3+R0ad9gcR8IX282aNJnq1Bi4QIiRKwJsdEGtdncmq 3CbRavQF4WA1icOt8NDO/FPoAF8vztt1Lit50MDyF3TSeGjQ+jSfuPWTm9bTzigish tAeHk18Gdj61g== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 372785E7B4; Sat, 4 Apr 2026 15:33:24 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:10 -0600 Message-ID: <20260404213020.372253-35-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: LKI4S54GLE2IH2ZESM2OY4BTKARVMJXX X-Message-ID-Hash: LKI4S54GLE2IH2ZESM2OY4BTKARVMJXX 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 34/37] patman: Add tests for review and Gmail features 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/LKI4S54GLE2IH2ZESM2OY4BTKARVMJXX/> 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 26 tests covering the review feature: - Integration tests: new series, already-reviewed, new version, title search, no-link-or-title error, apply failure, draft creation (dry run and actual) - Review parsing: approved, skip verdict, changes with comments - Greeting: guess from email, fallback when empty - Refinement: skips approved reviews, processes reviews with comments - Cleanup: backtick removal, function quoting - Commit message: deduplication, subject + body quoting - Gmail: subject preserves [PATCH], falls back to name, From header set/omitted - Database: review_delete_for_version, series_set_source - Notes: save and retrieve, empty version filtering Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/review.py | 11 + tools/patman/test_cseries.py | 690 +++++++++++++++++++++++++++++++++++ 2 files changed, 701 insertions(+) -- 2.43.0 diff --git a/tools/patman/review.py b/tools/patman/review.py index 74bc81a2a30..f0fa4e865e4 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -1479,6 +1479,17 @@ def _register_series(cser, clean_name, version, link, series_data): if pcommits: cser.db.pcommit_add_list(svid, pcommits) + # pcommit_add_list only stores seq/subject/change_id; update + # patch_id from the patchwork data + pclist = cser.db.pcommit_get_list(svid) + for pcm, patch in zip(pclist, patches): + patch_id = patch.get('id') + if patch_id: + cser.db.pcommit_update(database.Pcommit( + idnum=pcm.idnum, seq=pcm.seq, subject=pcm.subject, + svid=svid, change_id=pcm.change_id, state=pcm.state, + patch_id=patch_id, num_comments=pcm.num_comments)) + cser.commit() tout.notice(f"Added series '{clean_name}' v{version} to database") return series_id, svid diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 5974c69253a..7d6a6179c1e 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -5,6 +5,7 @@ """Functional tests for checking that patman behaves correctly""" import asyncio +import contextlib from datetime import datetime import os import re @@ -4480,3 +4481,692 @@ Date: .* self.assertIn('Version 2:', output) self.assertIn('Second version desc', output) self.assertIn('Notes: Fixed review feedback', output) + + # Series link used by the review tests + REVIEW_LINK = 497923 + REVIEW_LINK_V2 = 497924 + REVIEW_NAME = 'boot/bootm: Disable interrupts after loading the image' + + def _fake_patchwork_review(self, subpath): + """Fake Patchwork server for review tests + + Args: + subpath (str): URL subpath to use + """ + if subpath == 'projects/': + return [ + {'id': self.PROJ_ID, 'name': 'U-Boot', + 'link_name': self.PROJ_LINK_NAME}, + ] + + re_search = re.match(r'series/\?project=(\d+)&q=(.*)$', subpath) + if re_search: + return [ + {'id': self.REVIEW_LINK, 'name': self.REVIEW_NAME, + 'version': 1, 'date': '2026-03-29T15:17:33'}, + {'id': self.REVIEW_LINK_V2, 'name': self.REVIEW_NAME, + 'version': 2, 'date': '2026-04-01T10:00:00'}, + ] + + m_series = re.match(r'series/(\d+)/$', subpath) + if m_series: + series_id = int(m_series.group(1)) + if series_id == self.REVIEW_LINK: + return { + 'name': f'[PATCH] {self.REVIEW_NAME}', + 'version': 1, + 'received_total': 1, + 'mbox': f'https://my-fake-url/series/{series_id}/mbox/', + 'submitter': {'name': 'Test Author', + 'email': 'author@example.com'}, + 'project': {'list_email': 'u-boot@lists.denx.de'}, + 'cover_letter': None, + 'patches': [ + {'id': 900, + 'name': f'[PATCH] {self.REVIEW_NAME}', + 'msgid': '<20260329-bootm-v1-1-abc@posteo.net>'}, + ], + } + if series_id == self.REVIEW_LINK_V2: + return { + 'name': f'[PATCH v2] {self.REVIEW_NAME}', + 'version': 2, + 'received_total': 1, + 'mbox': f'https://my-fake-url/series/{series_id}/mbox/', + 'submitter': {'name': 'Test Author', + 'email': 'author@example.com'}, + 'project': {'list_email': 'u-boot@lists.denx.de'}, + 'cover_letter': None, + 'patches': [ + {'id': 901, + 'name': f'[PATCH,v2] {self.REVIEW_NAME}', + 'msgid': '<20260401-bootm-v2-1-def@posteo.net>'}, + ], + } + raise ValueError( + f'Fake Patchwork unknown series_id: {series_id}') + + m_patch = re.match(r'patches/(\d+)/$', subpath) + if m_patch: + return { + 'headers': { + 'Reply-To': 'author@posteo.net', + 'To': 'u-boot@lists.denx.de', + 'Cc': 'Tom Rini <trini@konsulko.com>', + }, + } + + m_pcomm = re.match(r'patches/(\d+)/comments/$', subpath) + if m_pcomm: + return [] + + m_ccomm = re.match(r'covers/(\d+)/comments/$', subpath) + if m_ccomm: + return [] + + raise ValueError(f'Fake Patchwork unhandled URL: {subpath}') + + REVIEWER = 'Test Reviewer <test@test.com>' + + def run_review(self, *argv, **kwargs): + """Run a review command with the test reviewer identity""" + return self.run_args('review', '--reviewer', self.REVIEWER, + *argv, **kwargs) + + def _mock_review(self): + """Context manager to mock apply, upstream, git and AI review""" + fake_review = {1: f'Reviewed-by: {self.REVIEWER}'} + return (mock.patch('patman.review._apply_and_check', + return_value='review1'), + mock.patch('patman.review._get_upstream_branch', + return_value='origin/master'), + mock.patch('patman.review.review_patches_sync', + return_value=fake_review), + mock.patch('patman.review.gitutil.get_top_level', + return_value=self.tmpdir), + mock.patch('patman.review.command.output', + return_value='pati'), + mock.patch('patman.review._git_restore')) + + def test_review_new_series(self): + """Test reviewing a new series creates database records""" + cser = self.get_cser() + pwork = Patchwork.for_testing(self._fake_patchwork_review) + pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) + + mocks = self._mock_review() + with contextlib.ExitStack() as stack: + for m in mocks: + stack.enter_context(m) + with terminal.capture() as _: + self.run_review( '-l', str(self.REVIEW_LINK), + pwork=pwork) + + # Check the series was created with source='review' + self.db_open() + 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(1, version) + + # Check source is 'review' + res = cser.db.execute( + 'SELECT source FROM series WHERE id = ?', (series_id,)) + self.assertEqual('review', res.fetchone()[0]) + + # Check pcommit was created + pclist = cser.db.pcommit_get_list(svid) + self.assertEqual(1, len(pclist)) + self.assertEqual(900, pclist[0].patch_id) + + def test_review_already_reviewed(self): + """Test that reviewing the same link again is detected""" + cser = self.get_cser() + pwork = Patchwork.for_testing(self._fake_patchwork_review) + pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) + + mocks = self._mock_review() + with contextlib.ExitStack() as stack: + for m in mocks: + stack.enter_context(m) + with terminal.capture() as _: + self.run_review( '-l', str(self.REVIEW_LINK), + pwork=pwork) + + # Review the same link again + mocks = self._mock_review() + with contextlib.ExitStack() as stack: + 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.assertIn('Already reviewed', out.getvalue()) + + def test_review_new_version(self): + """Test that reviewing v2 detects v1 as previously reviewed""" + cser = self.get_cser() + pwork = Patchwork.for_testing(self._fake_patchwork_review) + pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) + + # Review v1 first + mocks = self._mock_review() + with contextlib.ExitStack() as stack: + for m in mocks: + stack.enter_context(m) + with terminal.capture() as _: + self.run_review( '-l', str(self.REVIEW_LINK), + pwork=pwork) + + # Now review v2 - should detect the previous review + mocks = self._mock_review() + with contextlib.ExitStack() as stack: + 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.assertIn('Previously reviewed', out.getvalue()) + + # Check both versions are under the same series + self.db_open() + v1 = cser.db.series_find_by_link(str(self.REVIEW_LINK)) + v2 = cser.db.series_find_by_link(str(self.REVIEW_LINK_V2)) + self.assertEqual(v1[0], v2[0]) # same series_id + self.assertEqual(1, v1[2]) # version 1 + self.assertEqual(2, v2[2]) # version 2 + + def test_review_title_search(self): + """Test searching for a series by title""" + cser = self.get_cser() + pwork = Patchwork.for_testing(self._fake_patchwork_review) + pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) + + mocks = self._mock_review() + with contextlib.ExitStack() as stack: + for m in mocks: + stack.enter_context(m) + with terminal.capture() as (out, _): + self.run_review( '-t', 'Disable interrupts', + pwork=pwork) + # Should pick the most recent (v2) + self.assertIn('Using most recent', out.getvalue()) + + self.db_open() + result = cser.db.series_find_by_link(str(self.REVIEW_LINK_V2)) + self.assertIsNotNone(result) + + def test_review_no_link_or_title(self): + """Test that missing -l and -t gives a proper error""" + self.get_cser() + pwork = Patchwork.for_testing(self._fake_patchwork_review) + pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) + + with terminal.capture() as _: + self.run_review( pwork=pwork, expect_ret=1) + + def test_review_apply_failure(self): + """Test that apply failure is reported""" + self.get_cser() + pwork = Patchwork.for_testing(self._fake_patchwork_review) + pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) + + with mock.patch('patman.review._apply_and_check', + return_value=None), \ + mock.patch('patman.review._get_upstream_branch', + return_value='origin/master'), \ + mock.patch('patman.review.gitutil.get_top_level', + return_value=self.tmpdir), \ + mock.patch('patman.review.command.output', + return_value='test'), \ + mock.patch('patman.review._git_restore'): + with terminal.capture() as _: + self.run_review('-l', str(self.REVIEW_LINK), + pwork=pwork, expect_ret=1) + + def test_review_create_drafts_dry_run(self): + """Test dry-run draft creation shows what would be created""" + self.get_cser() + pwork = Patchwork.for_testing(self._fake_patchwork_review) + pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) + + mocks = self._mock_review() + with contextlib.ExitStack() as stack: + 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) + output = out.getvalue() + self.assertIn('Would create draft', output) + self.assertIn('author@posteo.net', output) + self.assertIn('trini@konsulko.com', output) + + def test_review_create_drafts(self): + """Test actual draft creation calls Gmail API""" + self.get_cser() + pwork = Patchwork.for_testing(self._fake_patchwork_review) + pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) + + mocks = self._mock_review() + with contextlib.ExitStack() as stack: + for m in mocks: + stack.enter_context(m) + with mock.patch('patman.gmail.check_available', + return_value=True): + with mock.patch('patman.gmail.get_service') as mock_svc: + mock_svc.return_value.users.return_value \ + .drafts.return_value \ + .create.return_value \ + .execute.return_value = {'id': 'draft123'} + mock_svc.return_value.users.return_value \ + .messages.return_value \ + .list.return_value \ + .execute.return_value = {'messages': []} + with terminal.capture() as (out, _): + self.run_review( '-l', + str(self.REVIEW_LINK), + '--create-drafts', pwork=pwork) + output = out.getvalue() + self.assertIn('Created 1 Gmail draft', output) + + def _make_review_ctx(self, reviewer_name='Test', reviewer_email='test@test.com', + author_name='', author_email='', date='', signoff=None, + diffstat=None): + """Build a ReviewContext for testing format_review_email()""" + from patman.review import ReviewContext + + ctx = ReviewContext(None, None, + {'submitter': {'name': author_name, 'email': author_email}, + 'date': date}) + ctx.reviewer_name = reviewer_name + ctx.reviewer_email = reviewer_email + ctx.signoff = signoff + ctx.diffstat = diffstat + return ctx + + def test_review_parse_approved(self): + """Test parsing an approved review""" + from patman.review import parse_review_output, format_review_email + + text = """GREETING: Marek +VERDICT: approved""" + greeting, verdict, comments = parse_review_output(text) + self.assertEqual('Marek', greeting) + self.assertEqual('approved', verdict) + self.assertEqual([], comments) + + ctx = self._make_review_ctx(author_name='Marek Vasut', + author_email='marex@denx.de', date='2026-03-21', + diffstat=' drivers/pci.c | 2 +-\n 1 file changed') + email = format_review_email(ctx, greeting, verdict, comments, + commit_message='pci: Fix the return type\n\nThe return is wrong.') + self.assertNotIn('Hi Marek,', email) + self.assertIn('On 2026-03-21, Marek Vasut', email) + self.assertIn('> pci: Fix the return type', email) + self.assertIn('> The return is wrong.', email) + self.assertIn('> drivers/pci.c', email) + self.assertIn('Reviewed-by: Test <test@test.com>', email) + + def test_review_parse_skip(self): + """Test parsing a skipped review (e.g. cover letter with no issues)""" + from patman.review import parse_review_output + + text = """GREETING: Michal +VERDICT: skip""" + greeting, verdict, comments = parse_review_output(text) + self.assertEqual('Michal', greeting) + self.assertEqual('skip', verdict) + self.assertEqual([], comments) + + def test_review_guess_name(self): + """Test guessing first name from email address""" + from patman.review import guess_name_from_email + + self.assertEqual('Simon', guess_name_from_email('simon.glass@xxx')) + self.assertEqual('Michal', guess_name_from_email('michal@amd.com')) + self.assertEqual('Marek', + guess_name_from_email('marek-vasut@denx.de')) + self.assertEqual('', guess_name_from_email('j@posteo.net')) + self.assertEqual('', guess_name_from_email('')) + self.assertEqual('', guess_name_from_email('12345@test.com')) + + def test_review_cleanup(self): + """Test mechanical cleanup of review text""" + from patman.review import cleanup_review_text + + # Backticks removed + self.assertEqual('Use foo here', + cleanup_review_text('Use `foo` here')) + + # Quoted function references unquoted + self.assertEqual('Call malloc() first', + cleanup_review_text("Call 'malloc()' first")) + self.assertEqual('Call free() after', + cleanup_review_text('Call "free()" after')) + + # Normal quotes preserved + self.assertEqual("Normal 'text' stays", + cleanup_review_text("Normal 'text' stays")) + + # Quoted diff lines not mangled + line = "> +\t`something`" + self.assertEqual('> +\tsomething', cleanup_review_text(line)) + + def test_review_greeting_fallback(self): + """Test greeting falls back to email when name is empty""" + from patman.review import format_review_email + + # Empty greeting should be guessed from email + ctx = self._make_review_ctx(author_name='Simon Glass', + author_email='simon.glass@xxx.com', date='2026-04-01', + signoff='Regards,\nTest') + email = format_review_email(ctx, '', 'changes_needed', + [('> +\tsome code', 'Fix this')]) + self.assertIn('Hi Simon,', email) + + # Unguessable email falls back to bare 'Hi,' + ctx = self._make_review_ctx(author_email='x@test.com', + date='2026-04-01') + email = format_review_email(ctx, '', 'changes_needed', + [('> +\tsome code', 'Fix this')]) + self.assertIn('Hi,', email) + self.assertNotIn('Hi ,', email) + + def test_review_refine_skips_approved(self): + """Test that refinement skips approved reviews without comments""" + import asyncio + from unittest.mock import patch, AsyncMock + + from patman.review import refine_reviews + + # An approved review with only structural lines + approved = ('On 2026-04-01, A <a@b.com> wrote:\n' + '> Some commit message\n' + '>\n' + '> drivers/foo.c | 2 +-\n' + '\n' + 'Reviewed-by: Test <test@test.com>\n') + + # Should be returned unchanged without calling the agent + loop = asyncio.new_event_loop() + with patch('patman.review.get_voice', return_value=None): + result = loop.run_until_complete( + refine_reviews({1: approved})) + loop.close() + self.assertEqual({1: approved}, result) + + def test_review_refine_processes_comments(self): + """Test that refinement processes reviews with comments""" + import asyncio + import sys + import types + from unittest.mock import patch, MagicMock + + review_with_comments = ( + 'Hi Marek,\n\n' + 'On 2026-04-01, Marek <m@d.de> wrote:\n' + '> +\tsome code\n\n' + 'This needs fixing.\n\n' + 'Regards,\nSimon\n') + + # Mock the agent to return a slightly trimmed version + refined = '---SEQ 1---\n' + review_with_comments.replace( + 'This needs fixing.', 'Fix this.') + + async def mock_agent(prompt, options): + return True, refined + + from patman import review as review_mod + + loop = asyncio.new_event_loop() + with patch.object(review_mod, 'get_voice', return_value=None), \ + patch.object(review_mod.claude_mod, 'run_agent_collect', + side_effect=mock_agent), \ + patch.object(review_mod, 'ClaudeAgentOptions', MagicMock()), \ + terminal.capture(): + result = loop.run_until_complete( + review_mod.refine_reviews({1: review_with_comments})) + loop.close() + self.assertIn('Fix this.', result[1]) + + def test_review_parse_changes(self): + """Test parsing a review with comments""" + from patman.review import parse_review_output, format_review_email + + text = """GREETING: J. +COMMENT: +> + if (ret < 0) +> + return ret; + +This should use goto err instead. + +COMMENT: +> + bootm_disable_interrupts(); + +This call should be conditional. + +VERDICT: changes_needed""" + + greeting, verdict, comments = parse_review_output(text) + self.assertEqual('J.', greeting) + self.assertEqual('changes_needed', verdict) + self.assertEqual(2, len(comments)) + self.assertIn('goto err', comments[0][1]) + self.assertIn('conditional', comments[1][1]) + + ctx = self._make_review_ctx(author_name='J. Neuschäfer', + author_email='j.ne@posteo.net', date='2026-03-29', + signoff='Regards,\nSimon') + email = format_review_email(ctx, greeting, verdict, comments) + self.assertIn('Hi J.,', email) + self.assertIn('> +\tif (ret < 0)', email) + self.assertIn('goto err', email) + self.assertNotIn('Reviewed-by', email) + self.assertIn('Regards,\nSimon', email) + + def test_review_commit_msg_no_duplicate(self): + """Test that the commit-msg builder avoids duplicating the subject""" + # Simulate cmt.subject and cmt.msg where msg starts with subject + subject = 'Drop unused macros' + msg_with_dup = 'Drop unused macros\n\nThese macros are never used.' + msg_without_dup = '\nThese macros are never used.' + + # When body starts with subject, use body as-is + body = msg_with_dup.strip() + if body.startswith(subject): + commit_msg = body + else: + commit_msg = (subject + '\n' + body).strip() + self.assertEqual(1, commit_msg.count('Drop unused macros')) + + # When body doesn't start with subject, prepend it + body = msg_without_dup.strip() + if body.startswith(subject): + commit_msg = body + else: + commit_msg = (subject + '\n' + body).strip() + self.assertTrue(commit_msg.startswith('Drop unused macros')) + self.assertIn('These macros are never used.', commit_msg) + + def test_review_commit_msg_with_body(self): + """Test that subject + body are both quoted when body differs""" + from patman.review import format_review_email + + ctx = self._make_review_ctx(author_name='Author', + author_email='a@b.com', date='2026-04-01', + diffstat=' file.c | 1 +\n 1 file changed') + email = format_review_email(ctx, '', 'approved', [], + commit_message='Fix the bug\n\nThe bug causes a crash.') + self.assertIn('> Fix the bug', email) + self.assertIn('> The bug causes a crash.', email) + + def test_gmail_subject_preserves_patch_prefix(self): + """Test that reply subjects use the original Subject header""" + from patman.gmail import create_review_drafts + + series_data = { + 'submitter': {'email': 'a@b.com'}, + 'project': {'list_email': 'list@test.com'}, + 'cover_letter': None, + 'patches': [ + {'id': 1, 'name': 'Fix the bug', + 'msgid': '<1@test.com>'}, + ], + } + patch_headers = { + 1: {'Subject': '[PATCH v2 1/3] Fix the bug', + 'Message-Id': '<1@test.com>'}, + } + review_bodies = {1: 'Reviewed-by: Test <test@test.com>'} + + with terminal.capture(): + create_review_drafts(series_data, review_bodies, + patch_headers=patch_headers, dry_run=True) + + def test_gmail_subject_falls_back_to_name(self): + """Test that reply subjects fall back to patchwork name""" + from patman.gmail import create_review_drafts + + series_data = { + 'submitter': {'email': 'a@b.com'}, + 'project': {'list_email': 'list@test.com'}, + 'cover_letter': None, + 'patches': [ + {'id': 1, 'name': 'Fix the bug', + 'msgid': '<1@test.com>'}, + ], + } + # No Subject in headers — should fall back to patch name + patch_headers = {1: {'Message-Id': '<1@test.com>'}} + review_bodies = {1: 'Reviewed-by: Test <test@test.com>'} + + with terminal.capture(): + create_review_drafts(series_data, review_bodies, + patch_headers=patch_headers, dry_run=True) + + def test_gmail_from_header(self): + """Test that create_draft sets From when sender is provided""" + from patman.gmail import create_draft + from email import message_from_bytes + from unittest.mock import MagicMock, patch + import base64 + + service = MagicMock() + service.users().drafts().create().execute.return_value = { + 'id': 'draft123'} + + draft = create_draft( + service, 'to@test.com', 'Re: test', 'body', + sender='Simon Glass <sjg@chromium.org>') + + # Extract the raw message that was passed to the API + call_kwargs = (service.users().drafts().create + .call_args) + raw = call_kwargs[1]['body']['message']['raw'] + msg = message_from_bytes(base64.urlsafe_b64decode(raw)) + self.assertEqual('Simon Glass <sjg@chromium.org>', msg['from']) + + def test_gmail_no_from_without_sender(self): + """Test that create_draft omits From when sender is None""" + from patman.gmail import create_draft + from email import message_from_bytes + from unittest.mock import MagicMock + import base64 + + service = MagicMock() + service.users().drafts().create().execute.return_value = { + 'id': 'draft123'} + + draft = create_draft( + service, 'to@test.com', 'Re: test', 'body') + + call_kwargs = (service.users().drafts().create + .call_args) + raw = call_kwargs[1]['body']['message']['raw'] + msg = message_from_bytes(base64.urlsafe_b64decode(raw)) + self.assertIsNone(msg['from']) + + def test_review_delete_for_version(self): + """Test deleting all reviews for a series version""" + cser = self.get_database() + + series_id = cser.db.series_add('test-delete', 'Test') + svid = cser.db.ser_ver_add(series_id, 1) + + from datetime import datetime + ts = datetime.now().isoformat() + cser.db.review_add(svid, 1, 'review body 1', True, ts) + cser.db.review_add(svid, 2, 'review body 2', False, ts) + cser.commit() + + reviews = cser.db.review_get_for_version(svid) + self.assertEqual(2, len(reviews)) + + cser.db.review_delete_for_version(svid) + cser.commit() + + reviews = cser.db.review_get_for_version(svid) + self.assertEqual(0, len(reviews)) + + def test_series_set_source(self): + """Test setting the source field on a series""" + cser = self.get_database() + + series_id = cser.db.series_add('test-source', 'Test') + cser.db.series_set_source(series_id, 'review') + cser.commit() + + res = cser.db.execute( + 'SELECT source FROM series WHERE id = ?', (series_id,)) + self.assertEqual('review', res.fetchone()[0]) + + def test_review_notes_save_and_show(self): + """Test saving and retrieving review notes""" + cser = self.get_database() + + # Create a series with two versions + series_id = cser.db.series_add('test-notes', 'Test series') + svid1 = cser.db.ser_ver_add(series_id, 1) + svid2 = cser.db.ser_ver_add(series_id, 2) + + # No notes initially + notes = cser.db.ser_ver_get_all_notes(series_id) + self.assertEqual([], notes) + + # Save notes for v1 + cser.db.ser_ver_set_notes(svid1, 'Fixed the memory leak issue') + cser.commit() + + notes = cser.db.ser_ver_get_all_notes(series_id) + self.assertEqual(1, len(notes)) + self.assertEqual(1, notes[0][0]) + self.assertIn('memory leak', notes[0][1]) + + # Save notes for v2 + cser.db.ser_ver_set_notes(svid2, 'Addressed style feedback') + cser.commit() + + notes = cser.db.ser_ver_get_all_notes(series_id) + self.assertEqual(2, len(notes)) + self.assertEqual(1, notes[0][0]) + self.assertEqual(2, notes[1][0]) + + def test_review_notes_skips_empty(self): + """Test that versions without notes are excluded""" + cser = self.get_database() + + series_id = cser.db.series_add('test-empty', 'Test') + svid1 = cser.db.ser_ver_add(series_id, 1) + svid2 = cser.db.ser_ver_add(series_id, 2) + svid3 = cser.db.ser_ver_add(series_id, 3) + + # Only set notes for v1 and v3 + cser.db.ser_ver_set_notes(svid1, 'v1 notes') + cser.db.ser_ver_set_notes(svid3, 'v3 notes') + cser.commit() + + notes = cser.db.ser_ver_get_all_notes(series_id) + self.assertEqual(2, len(notes)) + self.assertEqual(1, notes[0][0]) + self.assertEqual(3, notes[1][0]) From patchwork Sat Apr 4 21:29:11 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: 2149 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=1775338415; bh=LCznyxnr/oGY8azrkSGHZYYEd25uNgAb0km01hvZD6M=; 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=Hr7kakM21+eEeyZEWRMSyX5esaeJtUIgWAmpVzqJiQGmD14clIq4ZEF96d5kla9U0 e2+sY96JQb3vfhm3JA7ldyPACReM8nErC0qdWD457fVg/SIyD1h9B2Ni/xD0QTPPP6 QYMDJ91IzncJRXtpBWvIuKx1eXAjEWOp9WpGVwyyV3onEi5ffqgjz793QiFGz56SNk 3YPbkU6Im4sNoL79GAkSlJ+UhV/61dYr4s93J2rCuH1OUcbPsyxSDeULRazW0aLBnA +BgKP0KAQAGRSvU9cURsUoqLLpVz2axZ66OnS9GUAP4jzFANW+k6Ua6YUyq9+ndzhN 9S1vRYEp9ZS7g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 66F346869D for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:35 -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 0HtnqMiV5n9X for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:35 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338415; bh=LCznyxnr/oGY8azrkSGHZYYEd25uNgAb0km01hvZD6M=; 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=Hr7kakM21+eEeyZEWRMSyX5esaeJtUIgWAmpVzqJiQGmD14clIq4ZEF96d5kla9U0 e2+sY96JQb3vfhm3JA7ldyPACReM8nErC0qdWD457fVg/SIyD1h9B2Ni/xD0QTPPP6 QYMDJ91IzncJRXtpBWvIuKx1eXAjEWOp9WpGVwyyV3onEi5ffqgjz793QiFGz56SNk 3YPbkU6Im4sNoL79GAkSlJ+UhV/61dYr4s93J2rCuH1OUcbPsyxSDeULRazW0aLBnA +BgKP0KAQAGRSvU9cURsUoqLLpVz2axZ66OnS9GUAP4jzFANW+k6Ua6YUyq9+ndzhN 9S1vRYEp9ZS7g== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 50FDF64DB2 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:35 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338414; bh=8Y0aOSIciOs8SWMCrcVW6Ov2GYbPe+9SMM/2AkuNlOo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gElEaW1M94qjnqSuWhsLrmW7hbS5li3sgGyepVgJkOItR0aINyUcxeFcp8vZYcjru oThRyebEtt2rIX2JyKqXbmVrfj59hQlhdUqEvGl4cSyE3wkbXtPrZzhg/vr2ZH61ir 5tAyToWS5vAUd30RFZHG+oTemGBfuwaaNLKKo+8RYaFq7lxk3FFCTpXhEl1BpzGM23 z84NYnu+muDkvQlviRQ9HBk5ighID0bCqS8OAYv7QMG7YkyJvXMD17eOAcXC2BzI+F YSAUCRUnPeIe7MrKkQJDEq0K6T5Js3V9+xxj694A0OSJraabwbqRilPS2CYAOZF55V JwACw066e7OEw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4642364DB2; Sat, 4 Apr 2026 15:33:34 -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 6w637xOioUWb; Sat, 4 Apr 2026 15:33:34 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338410; bh=MK52P8Q/GEQfGziW9Be6kOpQP1q0ZmPmyLt5PJ/iAHA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=t2mSVujHL0dEqr8/9uOFYflQREKRkkXPIpNhMd6ggyBwZolxx3EqZjFRSRj+dC75P cIfaIr1xq7lyJ8Y/tUeob9zu94MmIb5nyPolnYzkgxNloVlrYWswUR4++YuskvWQLM FHFk91HPHS7JbErvgVlHwm0qTn8UzYFZcMpAZPb96FB1ytErlyxOon6JPfauT7lWGm hvp6rlSt0mN2st1R8C289CrXPe75bdffU2ohkqC7/sKGoyrT09xcUkqU7Vqet8gwrN TuNi7Y9W7Q4dyZlr1ZnvmM5nH+zOBw3EYVNClV6UpMkfWZ3p0Kcwpz2YXum/of/gh6 JdNpvdQmVMK7A== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id D5A4C5E7B4; Sat, 4 Apr 2026 15:33:29 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:11 -0600 Message-ID: <20260404213020.372253-36-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: SZCCJJD75LZJ6CQARUL42OA2LSPF7JJP X-Message-ID-Hash: SZCCJJD75LZJ6CQARUL42OA2LSPF7JJP 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 35/37] patman: Add documentation for AI-assisted patch 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/SZCCJJD75LZJ6CQARUL42OA2LSPF7JJP/> 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> Document the 'patman review' command and its options in the patman README. Covers series selection, patch application, AI review, Gmail draft creation, voice learning, draft synchronisation and the review workflow. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/patman.rst | 207 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) -- 2.43.0 diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index 87e0984c0d7..4c1dba07e6a 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -1188,3 +1188,210 @@ putting an incorrect tag in a commit may provide a confusing message. There might be a few other features not mentioned in this README. They might be bugs. In particular, tags are case sensitive which is probably a bad thing. + + +AI-assisted patch review +======================== + +Patman can review other people's patches from Patchwork using a Claude +AI agent, and create Gmail draft replies with the review comments. + +Prerequisites +------------- + +Install the required packages:: + + sudo apt install python3-google-auth python3-google-auth-oauthlib \ + python3-google-auth-httplib2 python3-googleapi + pip install claude-agent-sdk + +Setting up Gmail API access +--------------------------- + +1. Go to https://console.cloud.google.com and create (or select) a project. + +2. Enable the **Gmail API**: + + - Navigate to **APIs & Services > Enabled APIs & services** + - Click **Enable APIs and services**, search for "Gmail API", enable it + +3. Configure the **OAuth consent screen**: + + - Navigate to **APIs & Services > OAuth consent screen** + - Set publishing status to **Testing** + - Under **Test users**, add your email address (e.g. sjg@chromium.org) + +4. Create **OAuth client credentials**: + + - Navigate to **APIs & Services > Credentials** + - Click **Create Credentials > OAuth client ID** + - Application type: **Desktop app** + - Download the JSON file + +5. Save the credentials:: + + mkdir -p ~/.config/patman.d + mv ~/Downloads/client_secret_*.json ~/.config/patman.d/client_secret.json + +The first time you create drafts, a browser window opens for OAuth +consent. After authentication, the token is cached in +``~/.config/patman.d/`` and reused for future runs. + +Setting up Patchwork +-------------------- + +Configure an upstream with its Patchwork URL:: + + patman upstream add us https://source.denx.de/u-boot/u-boot.git \ + --patchwork-url https://patchwork.ozlabs.org + patman patchwork set-project U-Boot us + +Basic usage +----------- + +Review a series by Patchwork link:: + + patman review -l 497923 -U us --reviewer 'Your Name <your@email>' + +Or search by cover-letter title:: + + patman review -t 'boot/bootm: Disable interrupts' -U us \ + --reviewer 'Your Name <your@email>' + +To create Gmail drafts threaded under the original emails:: + + patman review -l 497923 -U us \ + --reviewer 'Your Name <your@email>' \ + --create-drafts --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 ``--apply-only`` to download and apply patches without running the +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 <your@email>' + +If the reviewer email (from ``--reviewer`` or git config) differs from +the ``--gmail-account``, patman sets the From header on the draft so +the email is sent with the correct identity. + +How the review works +-------------------- + +For each patch, the AI agent: + +1. Studies all patches in the series to understand the overall design +2. Re-reads the specific patch in detail +3. Examines surrounding source code for context +4. Checks existing comments from other reviewers on Patchwork and + avoids repeating points already made +5. Produces a structured review (GREETING/COMMENT/VERDICT format) + +After all patches are reviewed, a refinement agent makes a second pass +over the drafts to tighten the language, remove cross-patch duplicates +and check voice consistency. Approved reviews without comments are +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```). + +Patchwork subcommands +--------------------- + +Remove a patchwork project configuration:: + + patman patchwork rm [remote] + +If no remote is given, the default (no-upstream) entry is deleted. + +Review notes +------------ + +The ``handle-reviews`` workflow produces a ``review-notes.txt`` file +describing how feedback was addressed. Store it in the database so +future versions can reference it:: + + patman series save-notes [notes-file] + +The default filename is ``review-notes.txt``. Display notes from all +previous versions:: + + patman series show-notes + +Settings +-------- + +Add these to your ``~/.patman`` file to avoid repeating flags: + +.. code-block:: ini + + [settings] + signoff: Regards,\nSimon + spelling: British + +The ``signoff`` is appended to reviews that have comments (not to +clean Reviewed-by-only replies). The ``spelling`` setting controls +the spelling convention used in review comments. + +Voice learning +-------------- + +Build a voice profile so AI reviews match your writing style:: + + # From Gmail (searches your sent reviews to the mailing list) + patman review --learn-voice gmail -U us \ + --gmail-account your@email --reviewer 'Your Name <your@email>' + + # From Patchwork (scans your comments on recent patches) + patman review --learn-voice patchwork -U us \ + --reviewer 'Your Name <your@email>' + +Use ``--voice-count N`` to control how many reviews to collect +(default: 20). The voice profile is saved to +``~/.config/patman.d/voice.md`` and automatically used in subsequent +reviews. + +Syncing drafts +-------------- + +After editing and sending (or deleting) Gmail drafts:: + + patman review --sync --gmail-account your@email \ + --reviewer 'Your Name <your@email>' + +This: + +- Detects which drafts have been sent and records the final email + content in the database (for context when reviewing future versions) +- Detects deleted drafts (review not sent) +- Refines the voice profile if the sent text differs from the AI draft +- Detects replies from the patch author or other reviewers +- Generates response drafts when appropriate (e.g. answering + questions, pushing back on objections, or conceding gracefully) + +Review lifecycle +---------------- + +Each review goes through these states: + +- **new**: AI review generated, not yet sent +- **draft**: Gmail draft created +- **sent**: Draft was sent; body updated with actual sent content +- **deleted**: Draft was deleted without sending +- **replied**: Author or another reviewer has replied to our review + +When reviewing a new version of a previously reviewed series, patman +loads the previous review as context for the AI, so it can check +whether earlier issues have been addressed. + +Aliases +------- + +The ``review`` command supports aliases ``r`` and ``rev``:: + + patman r -l 497923 -U us --reviewer 'Your Name <your@email>' From patchwork Sat Apr 4 21:29:12 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: 2150 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=1775338420; bh=Ewg4y9HkXaAKAjimY4h2aH+NUAEcws28MV8qBrSecgU=; 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=DTIA8gFMYggIEHYoh6a8viMXYldCRBGuWyxBwpzVHPmWSyt5Qo1PLL4TARq2HjBeS CPV5gl38JeMMJ0xytAkC1PViaQyVYzojsa8E9Oo9nVwSsm2TIWAJA8HHb13r5PJJeO kvmK2bVMhlqEc+Kg4OjnT2fPO46il7cxvdgcba7aXEhvdt440fRvzKgJ2QSP1YsADF SG4j4GcKeDMqyhwPoWZ/5R+5bP4MIxvKQbTuyJ5MLr7N9sdtXp88dlfreZi+y/W1zu uTku2c9KT1ooo8xCAuN76+cCfMdyTXwjQXRjYQ3jtq6z5AoLCG1QNLnxj1UMxtHuP/ FQyh3NSUJK/8g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id F0DEE68F53 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33: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 mmy45IqfQU8P for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:40 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338420; bh=Ewg4y9HkXaAKAjimY4h2aH+NUAEcws28MV8qBrSecgU=; 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=DTIA8gFMYggIEHYoh6a8viMXYldCRBGuWyxBwpzVHPmWSyt5Qo1PLL4TARq2HjBeS CPV5gl38JeMMJ0xytAkC1PViaQyVYzojsa8E9Oo9nVwSsm2TIWAJA8HHb13r5PJJeO kvmK2bVMhlqEc+Kg4OjnT2fPO46il7cxvdgcba7aXEhvdt440fRvzKgJ2QSP1YsADF SG4j4GcKeDMqyhwPoWZ/5R+5bP4MIxvKQbTuyJ5MLr7N9sdtXp88dlfreZi+y/W1zu uTku2c9KT1ooo8xCAuN76+cCfMdyTXwjQXRjYQ3jtq6z5AoLCG1QNLnxj1UMxtHuP/ FQyh3NSUJK/8g== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E0C7D6833B for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:40 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338419; bh=bbeke7KpRPbSfn3Vt0Xugu1ZNIcZRvQQmCWZFaHeG20=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=S9Ap2TpCU6KG3GwamCIgtgwx0V3Boq0jIgJEOcoJfZCnypBE7q8VHkP/j0SN+EcqW AD/YN5bqNoZfkPixnQSsxVE7JBeg1k/OKAIQ3JqRWNQ4CRGNOwTEQlhoHRSjV9dWE/ BAQuQOgivNw4XR8idqzag9XEbNR5Aw9TWDXgGbtkcs/U2cJzxGBARcvEhDAZs8vfOs RIER4udmuMZcxcR0TMvdvqGcL0R2Ti+UtK0CwMv5w+wtqPiR0g7ixj9ZOUO9t0v5uF oLyNppzc+vpdspAzVB3/8mUktlJFn6Skxf7kA++731GWZnLswbvvir+hsa33A2NI38 NwD4d94THqeiA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 7890364DB2; Sat, 4 Apr 2026 15:33:39 -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 OfglsBlNP2KA; Sat, 4 Apr 2026 15:33:39 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338415; bh=sfpp+ujfqXc4HZIIrKbHKXYOaJPfF66xVrfvvCRwUe8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=C1x+YH7eYDBxjrkySCz89PnEv50BxSMiuBuHWMSWyQ98b8qOi6maQ2B10lCVSilKl CGukqQP9duuIIpOHoD2TepWaDOfFvtxk3ZAbmhra2n/p01OmP6u7MEitpMZ2TKdhGz JcfeRgHIZkPc34eV55a7sjb2uTJexhyfdt6TuKFE41TMCqa2K5p1xQ6aBxS1VFuaq2 xSMrhRKwDUE9HRvdXMZeb+TrYKAs3wA85In7uaEbMPqcP1CGk+M9WflXY8EWAY97ZB 5QTc3jEDoG1/h/y/5JPt2wVC/dT3JLQI/xN0529qiyns4Skx1r9lTo5uPnDq9umzk3 mB8RO0djYUGpw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 5BA915E7B4; Sat, 4 Apr 2026 15:33:35 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:12 -0600 Message-ID: <20260404213020.372253-37-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: MN57CUGOQTBKUGRRX4CUPMBPRMFDYHTA X-Message-ID-Hash: MN57CUGOQTBKUGRRX4CUPMBPRMFDYHTA 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 36/37] patman: Add database schema documentation 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/MN57CUGOQTBKUGRRX4CUPMBPRMFDYHTA/> 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> Document the SQLite database schema in patman.rst including all tables, columns, types, foreign key relationships and the schema version each column is added in. This covers the full schema at v10 with series, ser_ver, pcommit, review, upstream, patchwork, workflow and schema_version tables. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/patman.rst | 214 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 213 insertions(+), 1 deletion(-) -- 2.43.0 diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index 4c1dba07e6a..0644f4d0e08 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -1268,7 +1268,7 @@ Use ``-n`` with ``--create-drafts`` 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 @@ -1395,3 +1395,215 @@ Aliases The ``review`` command supports aliases ``r`` and ``rev``:: patman r -l 497923 -U us --reviewer 'Your Name <your@email>' + + +Database schema +=============== + +Patman stores series tracking, review and workflow state in a SQLite +database (``.patman.db``) in the top-level git directory. The schema is +versioned and auto-migrated on startup (currently at v10). + +The database allows patman to track a patch series across multiple +versions, recording which patches belong to each version and how they +map to patchwork entries. This means patman can detect when a new +version of a series arrives, carry forward review tags and change logs +from earlier versions, and show upstream progress without re-querying +patchwork each time. + +For AI-assisted review of other people's patches, the database stores +the generated review text, Gmail draft IDs and thread state so that +patman can resume where it left off - showing stored reviews, creating +drafts for reviews that were not yet sent, detecting when drafts have +been sent or deleted, and providing previous review context when a new +version of the series is posted. Review-handling notes from the +``handle-reviews`` workflow are also stored per-version so the AI has +context about what was changed and why. + +Entity relationships +-------------------- + +:: + + upstream 1---* patchwork (patchwork.upstream references + upstream.name) + + series 1---* ser_ver (ser_ver.series_id -> series.id) + + ser_ver 1---* pcommit (pcommit.svid -> ser_ver.id) + + ser_ver 1---* review (review.svid -> ser_ver.id) + + series 1---* workflow (workflow.series_id -> series.id) + +A **series** represents a named patch series across all its versions. +Each **ser_ver** is one version of that series (e.g. v1, v2), linked to +patchwork by a series link. Each ser_ver has one **pcommit** per patch +and zero or more **review** records (one per AI review). + +Tables +------ + +series +~~~~~~ + +Patman tracks series that you are working on so it can keep an eye on +any feedback from other people (from patchwork). Series that are being +reviewed (other people's patches) are also stored here with +``source='review'``. + +========== ======== ========================================== +Column Type Description +========== ======== ========================================== +id INTEGER Primary key (auto-increment) +name TEXT Series name (unique) +desc TEXT Series description / cover-letter title +archived BIT True if series is archived +upstream TEXT Upstream name (added v5) +source TEXT 'review' for AI-reviewed series (added v8) +========== ======== ========================================== + +ser_ver +~~~~~~~ + +Each time you send a new version of a series, patman creates a ser_ver +record linking it to patchwork. This lets patman track review tags, +change logs and upstream progress separately for each version. For +AI-reviewed series, review-handling notes are stored here so the next +version has context about what was changed. + +================== ======== ========================================== +Column Type Description +================== ======== ========================================== +id INTEGER Primary key (auto-increment) +series_id INTEGER FK -> series.id +version INTEGER Version number (1, 2, ...) +link TEXT Patchwork series link/ID +cover_id INTEGER Patchwork cover letter ID (added v3) +cover_num_comments INTEGER Number of comments on cover (added v3) +name TEXT Cover letter name (added v3) +archive_tag TEXT Git tag for archived version (added v4) +desc TEXT Version description (added v6) +notes TEXT Review-handling notes (added v10) +================== ======== ========================================== + +pcommit +~~~~~~~ + +Each patch in a series version gets a pcommit record. Patman uses the +Change-Id to match patches across versions (so it knows which patch in +v2 corresponds to which patch in v1) and tracks the patchwork state and +comment count so ``patman series progress`` can show upstream status. + +============== ======== ========================================== +Column Type Description +============== ======== ========================================== +id INTEGER Primary key (auto-increment) +svid INTEGER FK -> ser_ver.id +seq INTEGER Patch sequence (0-based) +subject TEXT Patch subject line +patch_id INTEGER Patchwork patch ID +change_id TEXT Change-Id tag value +state TEXT Patchwork state (e.g. 'new', 'accepted') +num_comments INTEGER Number of patchwork comments +============== ======== ========================================== + +review +~~~~~~ + +When patman reviews someone else's patches, it stores the generated +review email text here - one record per patch plus optionally the cover +letter. This allows patman to show the review again later, create Gmail +drafts from stored reviews, detect when drafts have been sent or +deleted, and provide the previous review as context when a new version +of the series arrives. When ``--sync`` detects that a draft was sent, +the body is updated to the final text the user actually sent (after any +manual edits), so the stored review reflects what was really posted to +the mailing list. + +================ ======== ========================================== +Column Type Description +================ ======== ========================================== +id INTEGER Primary key (auto-increment) +svid INTEGER FK -> ser_ver.id +seq INTEGER Patch sequence (0=cover, 1..N=patches) +body TEXT Review email body text +approved BIT True if Reviewed-by was given +timestamp TEXT ISO datetime of review creation +draft_id TEXT Gmail draft ID (added v9) +status TEXT 'new', 'draft', 'sent', 'deleted', + 'replied' (added v9) +gmail_msg_id TEXT Gmail message ID after sending (added v9) +gmail_thread_id TEXT Gmail thread ID for replies (added v9) +================ ======== ========================================== + +upstream +~~~~~~~~ + +Patman needs to know about the upstream repositories you send patches +to. Each upstream has a git remote name, URL and optional patchwork +settings. One upstream can be marked as the default so you do not need +to specify ``-U`` on every command. + +================ ======== ========================================== +Column Type Description +================ ======== ========================================== +name TEXT Git remote name (unique, e.g. 'us') +url TEXT Repository URL +is_default BIT True if this is the default upstream +patchwork_url TEXT Patchwork server URL (added v6) +identity TEXT Git sendemail identity (added v6) +series_to TEXT Default To: alias for series (added v6) +no_maintainers BIT Skip get_maintainer.pl (added v6) +no_tags BIT Skip subject-tag processing (added v6) +================ ======== ========================================== + +patchwork +~~~~~~~~~ + +Each upstream can have a patchwork project associated with it. This +stores the project name, ID and link name so patman can query patchwork +for series status, review tags and comments. Multiple upstreams can +point to different patchwork projects (e.g. U-Boot mainline vs a +custodian tree). + +============== ======== ========================================== +Column Type Description +============== ======== ========================================== +name TEXT Project name (e.g. 'U-Boot') +proj_id INTEGER Patchwork project ID +link_name TEXT Patchwork link name (e.g. 'uboot') +upstream TEXT Upstream name, or NULL for default +============== ======== ========================================== + +workflow +~~~~~~~~ + +Patman records workflow events such as when a series was sent, when +review feedback arrived and what needs attention. The ``patman workflow +todo`` command uses this to show outstanding tasks across all your +series. + +============== ======== ========================================== +Column Type Description +============== ======== ========================================== +id INTEGER Primary key (auto-increment) +series_id INTEGER FK -> series.id +action TEXT Workflow action (e.g. 'sent', 'todo') +timestamp TEXT ISO datetime +data TEXT JSON payload +ser_ver_id INTEGER FK -> ser_ver.id (added v7b) +============== ======== ========================================== + +schema_version +~~~~~~~~~~~~~~ + +A single-row table that records which schema version the database is at. +Patman checks this on startup and runs any needed migrations +automatically. + +============== ======== ========================================== +Column Type Description +============== ======== ========================================== +version INTEGER Current schema version (currently 10) +============== ======== ========================================== From patchwork Sat Apr 4 21:29: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: 2151 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=1775338426; bh=O+pf8mOK+YlyWo0eW06J5gCKAQ+oH/70CQBaWXQ6c2Q=; 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=bVKUXUL29AgeXnWMM7OtezYBtSfC2rXraKSkxOAZGffFkqRc6T8KS9ddm2/BeYIUM DGDh91rspjgJ+CTwxisjNT6O2CPtLu889RhkaNwC7JrOhc7vIaLAr4xDydHO//raf8 o8kcDO658ChGCBno3tku7WtzchA31URI9l1kw8MIvVhi2lKTuYIXBiupbmvxYS1Z4+ SHFhyNpNTgT4bqzcwiXwMTgv6FEu1esVI7UHlyMYhWqbucxudsysCU9+l5yIB7OmSG 6tWemiwfsyvNaheVtnjQomNR3NuC0MMYiqIIFKCOVo5lcKTFO+MYm699Mvhc3NFxlG DPFMqiXyzRE7Q== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 80FE25E7B4 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:46 -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 zIbqOq4jo4W1 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:46 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338426; bh=O+pf8mOK+YlyWo0eW06J5gCKAQ+oH/70CQBaWXQ6c2Q=; 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=bVKUXUL29AgeXnWMM7OtezYBtSfC2rXraKSkxOAZGffFkqRc6T8KS9ddm2/BeYIUM DGDh91rspjgJ+CTwxisjNT6O2CPtLu889RhkaNwC7JrOhc7vIaLAr4xDydHO//raf8 o8kcDO658ChGCBno3tku7WtzchA31URI9l1kw8MIvVhi2lKTuYIXBiupbmvxYS1Z4+ SHFhyNpNTgT4bqzcwiXwMTgv6FEu1esVI7UHlyMYhWqbucxudsysCU9+l5yIB7OmSG 6tWemiwfsyvNaheVtnjQomNR3NuC0MMYiqIIFKCOVo5lcKTFO+MYm699Mvhc3NFxlG DPFMqiXyzRE7Q== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 70B5F64DB2 for <u-boot-concept@u-boot.org>; Sat, 4 Apr 2026 15:33:46 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338424; bh=uzu6G3FQiUxMl0y/V2SaqzWEsqyuHPzajybemJVcP6o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cOrRwica7qjwcBjXf6SnoepwgmnK9I9oR8R6PCMiFOcWflSeJ6ojtqx+7JUbWHA2C XcktLuNVg42OLvMRvfSkzkfWZemhPRTr8QyTLdobxpJIpJTYwNUQZCn9EUOeLTX/gr etH1r4q/vqwTlt9oxDcmicM1W9BvIZTRaCY8oSZ7xJEJFF5ZUh+3JwUwyVY2Fsl/28 kyILD94NaL0g2kf8JQh2doJUg3y36o68yUKMe0ej6IQr237yyKs5w0IU0II1rxQ28Z vUoFFee1AhIZr7cil3E4i2+yWz8Vyuf6pJUiLjyRcJ2xkXEXbbmh/RPvh8qP4fqaZ1 lVMG30p/saYgw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C9ED964DB2; Sat, 4 Apr 2026 15:33: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 b_y9GtRA4uvn; Sat, 4 Apr 2026 15:33:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775338420; bh=JJtMvqOBF13couErQo2WxlLz15vLqcirwJSocwch/hw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kArEUNfN2LkYD7wk1iv8X+aK7IwEfQ6x6Xlo5pPFN7fHIowo20M11o5R6sbnBaYqS rJZw76yk+l1ERtm17r+plZ9DWGFyueCp6bzBZOc0jC25JrRl8of+BcRKqSKnmUBRD0 dDAlnkkLwsghtZMQ7QGLKLzQvERhXYP03iBlb+sny8+LxGqgU9hIEVpqFpQQxwukTq jJYaCUAV2jlvQVWnUp6+JnqNgzz3XsQeDHGHVQrGEIUixK8EBNnKjLw8hCK59xy4Dr RnEezS0nt9arA6iH7gg474fPBt5UuelNRqPsPfzaEUqK28KYUrlzHR6RsyDIYV4qdD //jF9NKd5b4Uw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 8D5945E7B4; Sat, 4 Apr 2026 15:33:40 -0600 (MDT) From: Simon Glass <sjg@u-boot.org> To: U-Boot Concept <concept@u-boot.org> Date: Sat, 4 Apr 2026 15:29:13 -0600 Message-ID: <20260404213020.372253-38-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260404213020.372253-1-sjg@u-boot.org> References: <20260404213020.372253-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: FA3PYHRV6C4JPCKGZXFIT2UTN3RDEXI6 X-Message-ID-Hash: FA3PYHRV6C4JPCKGZXFIT2UTN3RDEXI6 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 37/37] patman: Add dependencies for AI review feature 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/FA3PYHRV6C4JPCKGZXFIT2UTN3RDEXI6/> 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 claude-agent-sdk, google-api-python-client, google-auth-oauthlib and google-auth-httplib2 to requirements.txt for the AI-assisted review and Gmail draft-creation features. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/patman/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/patman/requirements.txt b/tools/patman/requirements.txt index d4fcb1061c2..c3de6979aa8 100644 --- a/tools/patman/requirements.txt +++ b/tools/patman/requirements.txt @@ -1,5 +1,9 @@ aiohttp==3.10.11 +claude-agent-sdk ConfigParser==7.1.0 +google-api-python-client +google-auth-httplib2 +google-auth-oauthlib importlib_resources==6.5.2 pygit2==1.14.1 requests==2.32.4