From patchwork Sun Mar 29 15:01:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2080 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=1774796532; bh=hn9xGORctKuykAIBW2bYoZwdE0xdc6cu/VoIGaKkVpE=; 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=B5I60tk5q2wY+dlVzMP+Gz4zaKGxTI2z0L5q17n5ciObc5xwrGy7VI4Sv6tE1hOts 4OrQFcDGz3RhCuVct4tgxVDQKXtfjMdmnPQ/pw/QkORr2b5JWgfmjW5ajqEKQmTdXq +6nkqxj8I0fturUuaoZfET6R5jeTVO/VbYuc24om94xbxbfY1t3IpX1XUly6hfhDnr 3dxeLwkei/cZEQKg1tsRicvOrfSBXx/mFgF/NWK6ur/9ay8hXzwAwEW+6axZ+wrIik GQi93OQYNLB+lmQSfxpSQtRg5ok2RqjGCuCs2f+hZD3oglt0yTHIi6z0ZkpyE3hCDH Nnppu+vlFnNqg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 32EA86A2DB for ; Sun, 29 Mar 2026 09:02:12 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id uOAJOrS2RzcE for ; Sun, 29 Mar 2026 09:02:12 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1774796531; bh=hn9xGORctKuykAIBW2bYoZwdE0xdc6cu/VoIGaKkVpE=; 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=caeX85FOjEMpnEF8GAIB5G7BIpSm8Bd9QIV9b4LzvOxZy9KCfCFDnLYAz4riXz7FE eeWRD1jXoC6bBeSdrL3QeqlkK452nDbSq1lOrYWq0jbR+xN6o97Gk2g7dSyhX28oU+ GawBWefCipcs7QmKqviLOurYBU3sqKrNO+j7qM+V7p2B1zvWbB82tHhaS6KER7W8l0 Dx7fjo6fp19fSRf3YCYGjs1vUeEFXcLCljRGxh6a6LywB0M/U+mXGRFiOL2X57BnHZ cqjXeBe90l2zLp3MwEAE9OJSBRHjhB1ComnjAINYnjowVoemuwvyVkS/O/0V7BjQ6g hpIeW5MI9L+lw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 7FB796A2CB for ; Sun, 29 Mar 2026 09:02:11 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1774796529; bh=nsXcvKAqXEHKCPb2RKsI1AZnkjjmjykpCPjldIHIq3g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vXSU0EX8zBPTzmNiHDs7UmRf+EdlfZjGyyda34iB03R+k+hlJ2hymgMz2bXYpudnv eb8LEmAF4dmd342eU6i8dMVFK/ceyPVIzTiu9uEPFSINjHH0RE6buuRHZHAhjWJxUn ILEwTQxqJ4rAoYwfII3wAfvF9luzS+uuU5143JTz25UPqRlWlNwPi/Hn2BY0kGEFRE 9eefarfbxcjQ2ICb9ThrYvujHPez5FWamLwgsqi2deZ+oHX0wnrSVEMPLbxmufAUAV sym2UIDwZ1blY68xUkn/YkIS17OP+sMGk/NJ7/3PB5qFEIfoXR2wz2QUh5HAJvEHVd cIHcUGmrQmE3A== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BE4E36A2E2; Sun, 29 Mar 2026 09:02: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 uLIroXcTPMVP; Sun, 29 Mar 2026 09:02:09 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1774796523; bh=bgwMxGXUxZHYZuNgyHV9yv54xYV407XXUjcy/DODHq0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=iZIH7rY3Ieza1VLU0bBjL1ZSUAlYPI7FjJ4f++1OHo0rw+I7tPXEVO/6WmCjFHZgr XYx+XeXO3i4wM6qdjPIGr6H7hLKT0h6bQwnlyadHc3j9snA/nNpalyRGfl3KyZuUvw ijuFjoBi+nUSMYOMpbC2SebHkyfJDBsajxBZ3FEv2Cyh/Emctn+m01OBU+RRIivDIM AqdIhdX3zTA7IhBogByYabvYxNw/Eu+tHrc2C4t0cII2uIZ4guMcQ2zY4pnJVrasNb GxeGABuuDKfE5rfgumbJ1xROFCbnHOpTnnfG4akWJmDsf0tyzCwDpETxXoN6l/gdSw cZUbN/zuL5NnA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id AF26F6A2D3; Sun, 29 Mar 2026 09:02:03 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sun, 29 Mar 2026 09:01:31 -0600 Message-ID: <20260329150140.4095446-7-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260329150140.4095446-1-sjg@u-boot.org> References: <20260329150140.4095446-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 3WEJFSKGOA5S3WXZARNJPOTGEOBM3VTP X-Message-ID-Hash: 3WEJFSKGOA5S3WXZARNJPOTGEOBM3VTP 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/11] patman: Add workflow table to the database 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 an extensible workflow table for tracking series-related tasks. Each entry has a type, series reference, timestamp and an archived flag for preserving history. The first type is 'todo' for marking series that need attention after a certain date. Add database methods for adding, archiving and querying workflow entries, with filtering by type and due date. Archived entries are kept for history but excluded from active queries. Signed-off-by: Simon Glass --- tools/patman/database.py | 91 +++++++++++++++++++++++++++++++++++- tools/patman/test_cseries.py | 48 ++++++++++++++++++- tools/patman/workflow.py | 13 ++++++ 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 tools/patman/workflow.py diff --git a/tools/patman/database.py b/tools/patman/database.py index e1ec0dc00e2..edb7d116c33 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -19,7 +19,7 @@ from u_boot_pylib import tout from patman.series import Series # Schema version (version 0 means there is no database yet) -LATEST = 5 +LATEST = 6 # Information about a series/version record SerVer = namedtuple( @@ -198,6 +198,21 @@ class Database: # pylint:disable=R0904 self.cur.execute('ALTER TABLE upstream ADD COLUMN no_tags BIT') self.cur.execute('ALTER TABLE ser_ver ADD COLUMN desc') + def _migrate_to_v6(self): + """Add workflow table for tracking todos and other workflow items + + Fields: + id: Auto-increment primary key + type: Workflow-entry type, e.g. 'todo' or 'sent' + series_id: Foreign key referencing series.id + timestamp: Due/event time as 'YYYY-MM-DD HH:MM:SS' + archived: 0 for active entries, 1 for archived (soft-delete) + """ + self.cur.execute( + 'CREATE TABLE workflow (id INTEGER PRIMARY KEY AUTOINCREMENT,' + 'type, series_id INTEGER, timestamp, archived BIT,' + 'FOREIGN KEY (series_id) REFERENCES series (id))') + def migrate_to(self, dest_version): """Migrate the database to the selected version @@ -226,6 +241,8 @@ class Database: # pylint:disable=R0904 self._migrate_to_v4() elif version == 5: self._migrate_to_v5() + elif version == 6: + self._migrate_to_v6() # Save the new version if we have a schema_version table if version > 1: @@ -1041,3 +1058,75 @@ class Database: # pylint:disable=R0904 if not recs: return None return recs[0] + + # workflow functions + + def workflow_add(self, wtype, series_id, timestamp): + """Add a workflow entry + + Args: + wtype (str): Workflow type, e.g. 'todo' + series_id (int): ID of the series + timestamp (str): Timestamp string, e.g. '2025-01-15 10:30:00' + """ + self.execute( + 'INSERT INTO workflow (type, series_id, timestamp, archived) ' + 'VALUES (?, ?, ?, 0)', (wtype, series_id, timestamp)) + + def workflow_archive(self, wtype, series_id): + """Archive active workflow entries for a given type and series + + Args: + wtype (str): Workflow type, e.g. 'todo' + series_id (int): ID of the series + """ + self.execute( + 'UPDATE workflow SET archived = 1 ' + 'WHERE type = ? AND series_id = ? AND archived = 0', + (wtype, series_id)) + + def workflow_get(self, wtype, series_id): + """Get the active workflow entry for a given type and series + + Args: + wtype (str): Workflow type, e.g. 'todo' + series_id (int): ID of the series + + Return: + str or None: Timestamp string if found, else None + """ + res = self.execute( + 'SELECT timestamp FROM workflow ' + 'WHERE type = ? AND series_id = ? AND archived = 0', + (wtype, series_id)) + rec = res.fetchone() + if rec: + return rec[0] + return None + + def workflow_get_by_type(self, wtype, before=None): + """Get active workflow entries for a given type, joined with series + + Args: + wtype (str): Workflow type, e.g. 'todo' + before (str or None): If set, only return entries where + timestamp <= this value + + Return: + list of tuple: + int: series ID + str: series name + str: series description + str: timestamp + """ + query = ('SELECT s.id, s.name, s.desc, w.timestamp ' + 'FROM workflow w ' + 'JOIN series s ON w.series_id = s.id ' + 'WHERE w.type = ? AND w.archived = 0 AND s.archived = 0') + params = [wtype] + if before is not None: + query += ' AND w.timestamp <= ?' + params.append(before) + query += ' ORDER BY w.timestamp' + res = self.execute(query, params) + return res.fetchall() diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index c0beb128265..798673e09cb 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -26,6 +26,7 @@ from patman import database from patman import patchstream from patman.patchwork import Patchwork from patman.test_common import TestCommon +from patman import workflow as wf HASH_RE = r'[0-9a-f]+' #pylint: disable=protected-access @@ -3556,7 +3557,7 @@ Date: .* self.assertEqual(f'Update database to v{version}', out.getvalue().strip()) self.assertEqual(version, db.get_schema_version()) - self.assertEqual(5, database.LATEST) + self.assertEqual(6, database.LATEST) def test_migrate_future_version(self): """Test that a database newer than patman is rejected""" @@ -4130,3 +4131,48 @@ Date: .* self.run_args('series', '-s', 'first', 'version-change', '--new-version', '3', pwork=True) method.assert_called_once_with('first', None, 3, dry_run=False) + + def test_workflow_db_methods(self): + """Test workflow database methods""" + cser = self.get_cser() + with terminal.capture(): + cser.add('first', 'my description', allow_unmarked=True) + + ser = cser.get_series_by_name('first') + + # Initially there is no workflow entry + self.assertIsNone(cser.db.workflow_get('todo', ser.idnum)) + + # Add a todo entry + cser.db.workflow_add('todo', ser.idnum, '2025-03-15 10:00:00') + cser.commit() + + # Should be able to read it back + ts = cser.db.workflow_get('todo', ser.idnum) + self.assertEqual('2025-03-15 10:00:00', ts) + + # Get by type should return it + entries = cser.db.workflow_get_by_type('todo') + self.assertEqual(1, len(entries)) + entry = entries[0] + self.assertEqual(ser.idnum, entry[0]) + self.assertEqual('first', entry[1]) + self.assertEqual('my description', entry[2]) + self.assertEqual('2025-03-15 10:00:00', entry[3]) + + # Get by type with before filter + entries = cser.db.workflow_get_by_type( + 'todo', before='2025-03-14 00:00:00') + self.assertEqual(0, len(entries)) + entries = cser.db.workflow_get_by_type( + 'todo', before='2025-03-16 00:00:00') + self.assertEqual(1, len(entries)) + + # Archive it - should no longer be active, but still in the table + cser.db.workflow_archive('todo', ser.idnum) + cser.commit() + self.assertIsNone(cser.db.workflow_get('todo', ser.idnum)) + res = cser.db.execute( + 'SELECT archived FROM workflow WHERE series_id = ?', + (ser.idnum,)) + self.assertEqual(1, res.fetchone()[0]) diff --git a/tools/patman/workflow.py b/tools/patman/workflow.py new file mode 100644 index 00000000000..37644a5de88 --- /dev/null +++ b/tools/patman/workflow.py @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2025 Google LLC +# + +"""Workflow types and operations for patman series management""" + +import enum + + +class Wtype(enum.StrEnum): + """Types of workflow entry""" + TODO = 'todo'