[Concept,09/32] patman: Associate patchwork projects with upstreams

Message ID 20260226200106.1727176-10-sjg@u-boot.org
State New
Headers
Series patman: Add multi-upstream support |

Commit Message

Simon Glass Feb. 26, 2026, 8 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Each upstream typically has its own patchwork project. Add an upstream
column to the patchwork table (schema v5) so each upstream can have
its own project configuration.

Rename the table from 'settings' to 'patchwork' to better reflect its
purpose. During migration, any existing row is associated with the
default upstream. The patchwork_update() and patchwork_get() methods
accept an optional upstream parameter to store and retrieve per-upstream
project configuration.

Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 tools/patman/cseries.py      |  4 +--
 tools/patman/database.py     | 49 +++++++++++++++++------------
 tools/patman/test_cseries.py | 60 ++++++++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+), 21 deletions(-)
  

Patch

diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py
index 246610215ed..5d4a9592de9 100644
--- a/tools/patman/cseries.py
+++ b/tools/patman/cseries.py
@@ -659,7 +659,7 @@  class Cseries(cser_helper.CseriesHelper):
                 tout.detail(f'Name match: ID {proj_id}')
         if not proj_id:
             raise ValueError(f"Unknown project name '{name}'")
-        self.db.settings_update(name, proj_id, link_name)
+        self.db.patchwork_update(name, proj_id, link_name)
         self.commit()
         if not quiet:
             tout.notice(f"Project '{name}' patchwork-ID {proj_id} "
@@ -674,7 +674,7 @@  class Cseries(cser_helper.CseriesHelper):
                 proj_id (int): Patchworks project ID for this project
                 link_name (str): Patchwork's link-name for the project
         """
-        return self.db.settings_get()
+        return self.db.patchwork_get()
 
     def remove(self, name, dry_run=False):
         """Remove a series from the database
diff --git a/tools/patman/database.py b/tools/patman/database.py
index 2bd225e4deb..22f21ce715f 100644
--- a/tools/patman/database.py
+++ b/tools/patman/database.py
@@ -165,12 +165,12 @@  class Database:  # pylint:disable=R0904
         self.cur.execute('ALTER TABLE ser_ver ADD COLUMN archive_tag')
 
     def _migrate_to_v5(self):
-        """Add upstream support to series, settings and upstream tables
+        """Add upstream support to series, patchwork and upstream tables
 
         - Add upstream column to series table
-        - Recreate settings table without UNIQUE constraint on name, adding
-          an upstream column (since the same project can have multiple
-          remotes)
+        - Rename and recreate patchwork table (formerly 'settings') without
+          UNIQUE constraint on name, adding an upstream column (since the
+          same project can have multiple remotes)
         - Add patchwork_url, identity, series_to, no_maintainers and
           no_tags columns to upstream table
         - Add desc column to ser_ver table
@@ -178,17 +178,17 @@  class Database:  # pylint:disable=R0904
         self.cur.execute('ALTER TABLE series ADD COLUMN upstream')
 
         self.cur.execute(
-            'CREATE TABLE settings_new '
+            'CREATE TABLE patchwork_new '
             '(name, proj_id INT, link_name, upstream)')
         self.cur.execute(
-            'INSERT INTO settings_new SELECT name, proj_id, link_name, NULL '
+            'INSERT INTO patchwork_new SELECT name, proj_id, link_name, NULL '
             'FROM settings')
         self.cur.execute('DROP TABLE settings')
-        self.cur.execute('ALTER TABLE settings_new RENAME TO settings')
+        self.cur.execute('ALTER TABLE patchwork_new RENAME TO patchwork')
         default_ups = self.upstream_get_default()
         if default_ups:
             self.cur.execute(
-                'UPDATE settings SET upstream = ?', (default_ups,))
+                'UPDATE patchwork SET upstream = ?', (default_ups,))
 
         self.cur.execute('ALTER TABLE upstream ADD COLUMN patchwork_url')
         self.cur.execute('ALTER TABLE upstream ADD COLUMN identity')
@@ -867,32 +867,43 @@  class Database:  # pylint:disable=R0904
             udict[name] = url, is_default
         return udict
 
-    # settings functions
+    # patchwork functions
 
-    def settings_update(self, name, proj_id, link_name):
-        """Set the patchwork settings of the project
+    def patchwork_update(self, name, proj_id, link_name, ups=None):
+        """Set the patchwork project details for an upstream
 
         Args:
             name (str): Name of the project to use in patchwork
             proj_id (int): Project ID for the project
             link_name (str): Link name for the project
+            ups (str or None): Upstream name to associate with, or None
         """
-        self.execute('DELETE FROM settings')
         self.execute(
-                'INSERT INTO settings (name, proj_id, link_name) '
-                'VALUES (?, ?, ?)', (name, proj_id, link_name))
+            'DELETE FROM patchwork WHERE upstream IS ?', (ups,))
+        self.execute(
+            'INSERT INTO patchwork (name, proj_id, link_name, upstream) '
+            'VALUES (?, ?, ?, ?)', (name, proj_id, link_name, ups))
+
+    def patchwork_get(self, ups=None):
+        """Get the patchwork project details for an upstream
 
-    def settings_get(self):
-        """Get the patchwork settings of the project
+        Args:
+            ups (str or None): Upstream name to look up, or None for any
 
         Returns:
-            tuple or None if there are no settings:
+            tuple or None if there is no project set:
                 name (str): Project name, e.g. 'U-Boot'
                 proj_id (int): Patchworks project ID for this project
                 link_name (str): Patchwork's link-name for the project
         """
-        res = self.execute("SELECT name, proj_id, link_name FROM settings")
+        if ups is not None:
+            res = self.execute(
+                'SELECT name, proj_id, link_name FROM patchwork '
+                'WHERE upstream = ?', (ups,))
+        else:
+            res = self.execute(
+                'SELECT name, proj_id, link_name FROM patchwork')
         recs = res.fetchall()
-        if len(recs) != 1:
+        if not recs:
             return None
         return recs[0]
diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py
index c6b90d5223d..0922c3e57f5 100644
--- a/tools/patman/test_cseries.py
+++ b/tools/patman/test_cseries.py
@@ -2502,6 +2502,66 @@  Tested-by: Mary Smith <msmith@wibble.com>   # yak
             f"Project 'U-Boot' patchwork-ID {self.PROJ_ID} link-name 'uboot'",
             out.getvalue().strip())
 
+    def test_patchwork_upstream(self):
+        """Test patchwork project with upstream association"""
+        cser = self.get_cser()
+
+        # Add two upstreams
+        cser.db.upstream_add('us', 'https://us.example.com')
+        cser.db.upstream_add('ci', 'https://ci.example.com')
+        cser.db.commit()
+
+        # Set project for a specific upstream
+        cser.db.patchwork_update('U-Boot', 6, 'uboot', 'us')
+        cser.db.commit()
+
+        # Look up by upstream
+        info = cser.db.patchwork_get('us')
+        self.assertEqual(('U-Boot', 6, 'uboot'), info)
+
+        # Different upstream has no project
+        self.assertIsNone(cser.db.patchwork_get('ci'))
+
+        # No upstream arg returns any match
+        info = cser.db.patchwork_get()
+        self.assertEqual(('U-Boot', 6, 'uboot'), info)
+
+        # Set a different project for ci
+        cser.db.patchwork_update('Linux', 10, 'linux', 'ci')
+        cser.db.commit()
+
+        self.assertEqual(('Linux', 10, 'linux'), cser.db.patchwork_get('ci'))
+        self.assertEqual(('U-Boot', 6, 'uboot'), cser.db.patchwork_get('us'))
+
+    def test_migrate_patchwork_upstream(self):
+        """Test that migrating to v5 renames settings to patchwork"""
+        db = database.Database(f'{self.tmpdir}/.patman3.db')
+        with terminal.capture():
+            db.open_it()
+
+        # Create a v4 database with an upstream and a patchwork row
+        with terminal.capture():
+            db.migrate_to(4)
+        db.execute(
+            "INSERT INTO upstream (name, url, is_default) "
+            "VALUES ('us', 'https://us.example.com', 1)")
+        db.execute(
+            "INSERT INTO settings (name, proj_id, link_name) "
+            "VALUES ('U-Boot', 6, 'uboot')")
+        db.commit()
+
+        # Migrate to v5
+        with terminal.capture():
+            db.migrate_to(5)
+
+        # The existing row should now be in 'patchwork' with the default upstream
+        res = db.execute(
+            'SELECT name, proj_id, link_name, upstream FROM patchwork')
+        recs = res.fetchall()
+        self.assertEqual(1, len(recs))
+        self.assertEqual(('U-Boot', 6, 'uboot', 'us'), recs[0])
+        db.close()
+
     def check_series_list_patches(self):
         """Test listing the patches for a series"""
         cser = self.get_cser()