[Concept,17/37] patman: Add 'patchwork rm' subcommand to delete a project

Message ID 20260404213020.372253-18-sjg@u-boot.org
State New
Headers
Series patman: Autolink fixes and AI-assisted patch review |

Commit Message

Simon Glass April 4, 2026, 9:28 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

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 <sjg@chromium.org>
---

 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(+)
  

Patch

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 <msmith@wibble.com>   # 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')