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"""