[Concept,11/11] patman: Record ser_ver in workflow sent entries

Message ID 20260329150140.4095446-12-sjg@u-boot.org
State New
Headers
Series patman: Add workflow tracking for patch series |

Commit Message

Simon Glass March 29, 2026, 3:01 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

Add a ser_ver_id column to the workflow table (schema v7) so that
SENT entries record which version of a series was sent. This is a
foreign key referencing ser_ver.id, or NULL for entries like TODO
that are not tied to a specific version.

Show the version in the 'workflow list' output.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 tools/patman/cseries.py      |  3 ++-
 tools/patman/database.py     | 30 ++++++++++++++++++++++++------
 tools/patman/test_cseries.py | 19 ++++++++++++++-----
 tools/patman/workflow.py     | 14 ++++++++------
 4 files changed, 48 insertions(+), 18 deletions(-)
  

Patch

diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py
index 4dddc70b679..0643d44cc01 100644
--- a/tools/patman/cseries.py
+++ b/tools/patman/cseries.py
@@ -990,7 +990,8 @@  class Cseries(cser_helper.CseriesHelper):
         likely_sent = send.send(args, git_dir=self.gitdir, cwd=self.topdir)
 
         if likely_sent:
-            workflow.sent(self, ser.idnum)
+            svid = self.get_series_svid(ser.idnum, version)
+            workflow.sent(self, ser.idnum, ser_ver_id=svid)
 
         if likely_sent and autolink:
             tout.notice(f'Autolinking with Patchwork ({autolink_wait} seconds)')
diff --git a/tools/patman/database.py b/tools/patman/database.py
index 3133a320694..ec1daa1c073 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 = 6
+LATEST = 7
 
 # Information about a series/version record
 SerVer = namedtuple(
@@ -213,6 +213,16 @@  class Database:  # pylint:disable=R0904
             'type, series_id INTEGER, timestamp, archived BIT,'
             'FOREIGN KEY (series_id) REFERENCES series (id))')
 
+    def _migrate_to_v7(self):
+        """Add ser_ver_id to workflow table for tracking which version was sent
+
+        Fields:
+            ser_ver_id: Foreign key referencing ser_ver.id, or NULL for
+                entries not tied to a specific version (e.g. todo)
+        """
+        self.cur.execute(
+            'ALTER TABLE workflow ADD COLUMN ser_ver_id INTEGER')
+
     def migrate_to(self, dest_version):
         """Migrate the database to the selected version
 
@@ -243,6 +253,8 @@  class Database:  # pylint:disable=R0904
                 self._migrate_to_v5()
             elif version == 6:
                 self._migrate_to_v6()
+            elif version == 7:
+                self._migrate_to_v7()
 
             # Save the new version if we have a schema_version table
             if version > 1:
@@ -1061,17 +1073,20 @@  class Database:  # pylint:disable=R0904
 
     # workflow functions
 
-    def workflow_add(self, wtype, series_id, timestamp):
+    def workflow_add(self, wtype, series_id, timestamp, ser_ver_id=None):
         """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'
+            ser_ver_id (int or None): ID of the ser_ver record, if applicable
         """
         self.execute(
-            'INSERT INTO workflow (type, series_id, timestamp, archived) '
-            'VALUES (?, ?, ?, 0)', (wtype, series_id, timestamp))
+            'INSERT INTO workflow '
+            '(type, series_id, timestamp, archived, ser_ver_id) '
+            'VALUES (?, ?, ?, 0, ?)',
+            (wtype, series_id, timestamp, ser_ver_id))
 
     def workflow_archive(self, wtype, series_id):
         """Archive active workflow entries for a given type and series
@@ -1144,10 +1159,13 @@  class Database:  # pylint:disable=R0904
                 str: series description
                 str: timestamp
                 int: archived flag (0 or 1)
+                int or None: version number from ser_ver, if applicable
         """
-        query = ('SELECT w.type, s.name, s.desc, w.timestamp, w.archived '
+        query = ('SELECT w.type, s.name, s.desc, w.timestamp, w.archived,'
+                 'sv.version '
                  'FROM workflow w '
-                 'JOIN series s ON w.series_id = s.id')
+                 'JOIN series s ON w.series_id = s.id '
+                 'LEFT JOIN ser_ver sv ON w.ser_ver_id = sv.id')
         if not include_archived:
             query += ' WHERE w.archived = 0'
         query += ' ORDER BY w.timestamp'
diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py
index 4eea5922c84..7a5eede9f82 100644
--- a/tools/patman/test_cseries.py
+++ b/tools/patman/test_cseries.py
@@ -3557,7 +3557,7 @@  Date:   .*
             self.assertEqual(f'Update database to v{version}',
                              out.getvalue().strip())
             self.assertEqual(version, db.get_schema_version())
-        self.assertEqual(6, database.LATEST)
+        self.assertEqual(7, database.LATEST)
 
     def test_migrate_future_version(self):
         """Test that a database newer than patman is rejected"""
@@ -4303,20 +4303,29 @@  Date:   .*
         cser.fake_now = datetime(2025, 3, 1, 12, 0, 0)
         ser = cser.get_series_by_name('first')
 
-        # Record a send
-        wf.sent(cser, ser.idnum)
+        svid = cser.get_series_svid(ser.idnum, 1)
+
+        # Record a send with ser_ver_id
+        wf.sent(cser, ser.idnum, ser_ver_id=svid)
 
         # Should have a SENT entry with current time
         ts = cser.db.workflow_get('sent', ser.idnum)
         self.assertEqual('2025-03-01 12:00:00', ts)
 
-        # Should have a TODO entry 7 days out
+        # The SENT entry should have the ser_ver_id
+        res = cser.db.execute(
+            'SELECT ser_ver_id FROM workflow '
+            'WHERE type = ? AND series_id = ? AND archived = 0',
+            ('sent', ser.idnum))
+        self.assertEqual(svid, res.fetchone()[0])
+
+        # Should have a TODO entry 7 days out (no ser_ver_id)
         ts = cser.db.workflow_get('todo', ser.idnum)
         self.assertEqual('2025-03-08 12:00:00', ts)
 
         # Sending again should archive old entries and create new ones
         cser.fake_now = datetime(2025, 3, 5, 12, 0, 0)
-        wf.sent(cser, ser.idnum)
+        wf.sent(cser, ser.idnum, ser_ver_id=svid)
 
         ts = cser.db.workflow_get('sent', ser.idnum)
         self.assertEqual('2025-03-05 12:00:00', ts)
diff --git a/tools/patman/workflow.py b/tools/patman/workflow.py
index d123e3068a2..85b38bd3f58 100644
--- a/tools/patman/workflow.py
+++ b/tools/patman/workflow.py
@@ -43,16 +43,17 @@  def friendly_time(now, when):
     return f'{days // 7}w ago'
 
 
-def sent(cser, series_id):
+def sent(cser, series_id, ser_ver_id=None):
     """Record that a series was sent and create a follow-up todo
 
     Args:
         cser (CseriesHelper): Series helper with open database
         series_id (int): ID of the series that was sent
+        ser_ver_id (int or None): ID of the ser_ver record that was sent
     """
     ts = cser.get_now().strftime('%Y-%m-%d %H:%M:%S')
     cser.db.workflow_archive(Wtype.SENT, series_id)
-    cser.db.workflow_add(Wtype.SENT, series_id, ts)
+    cser.db.workflow_add(Wtype.SENT, series_id, ts, ser_ver_id=ser_ver_id)
     when = cser.get_now() + timedelta(days=7)
     todo_ts = when.strftime('%Y-%m-%d %H:%M:%S')
     cser.db.workflow_archive(Wtype.TODO, series_id)
@@ -133,8 +134,8 @@  def list_entries(cser, show_all):
     if not entries:
         print('No workflow entries')
         return
-    hdr = f"{'Type':6}  {'Series':17}  {'When':>10}"
-    div = f"{'-' * 6}  {'-' * 17}  {'-' * 10}"
+    hdr = f"{'Type':6}  {'Series':17}  {'Ver':>3}  {'When':>10}"
+    div = f"{'-' * 6}  {'-' * 17}  {'-' * 3}  {'-' * 10}"
     if show_all:
         hdr += '  A'
         div += '  -'
@@ -143,10 +144,11 @@  def list_entries(cser, show_all):
     print(hdr)
     print(div)
     now = cser.get_now()
-    for wtype, name, desc, ts, archived in entries:
+    for wtype, name, desc, ts, archived, version in entries:
         when = datetime.strptime(ts, '%Y-%m-%d %H:%M:%S')
         friendly = friendly_time(now, when)
-        line = f"{wtype:6}  {name:17}  {friendly:>10}"
+        ver = f'v{version}' if version else ''
+        line = f"{wtype:6}  {name:17}  {ver:>3}  {friendly:>10}"
         if show_all:
             line += f"  {'*' if archived else ' '}"
         line += f"  {desc}"