[Concept,08/11] patman: Add 'workflow' command with todo subcommands

Message ID 20260329150140.4095446-9-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>

Wire the workflow todo feature into the command line with a new
'workflow' (alias 'wf') top-level command. Subcommands:

  patman wf todo [-s SERIES] [DAYS]  - mark series for todo
  patman wf todo [-s SERIES] --clear - clear the todo marker
  patman wf todo-list [--all]        - list due (or all) todos

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

 tools/patman/cmdline.py      | 32 +++++++++++++++++++++++++++++++-
 tools/patman/control.py      | 29 +++++++++++++++++++++++++++++
 tools/patman/test_cseries.py | 20 ++++++++++++++++++++
 3 files changed, 80 insertions(+), 1 deletion(-)
  

Patch

diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py
index 5bf917e4712..3628c12459c 100644
--- a/tools/patman/cmdline.py
+++ b/tools/patman/cmdline.py
@@ -26,6 +26,7 @@  ALIASES = {
     'status': ['st'],
     'patchwork': ['pw'],
     'upstream': ['us'],
+    'workflow': ['wf'],
 
     # Subcommand aliases
     'archive': ['ar'],
@@ -35,6 +36,7 @@  ALIASES = {
     'open': ['o'],
     'progress': ['p', 'pr', 'prog'],
     'rm-version': ['rmv'],
+    'todo-list': ['tl'],
     'unarchive': ['unar'],
     }
 
@@ -476,6 +478,32 @@  def add_upstream_subparser(subparsers):
     return upstream
 
 
+def add_workflow_subparser(subparsers):
+    """Add the 'workflow' subparser
+
+    Args:
+        subparsers (argparse action): Subparser parent
+
+    Return:
+        ArgumentParser: workflow subparser
+    """
+    workflow = subparsers.add_parser('workflow', aliases=ALIASES['workflow'],
+                                     help='Manage workflow items')
+    workflow_subparsers = workflow.add_subparsers(dest='subcmd')
+    todo = workflow_subparsers.add_parser('todo')
+    todo.add_argument('-s', '--series', help='Name of series')
+    todo.add_argument('days', nargs='?', type=int, default=14,
+                      help='Number of days until due (default: 14)')
+    todo.add_argument('--clear', action='store_true',
+                      help='Clear the todo marker instead of setting it')
+
+    tlist = workflow_subparsers.add_parser('todo-list',
+                                           aliases=ALIASES['todo-list'])
+    tlist.add_argument('--all', action='store_true', dest='show_all',
+                       help='Show all scheduled todos, not just due ones')
+    return workflow
+
+
 def setup_parser():
     """Set up command-line parser
 
@@ -518,6 +546,7 @@  def setup_parser():
     series = add_series_subparser(subparsers)
     add_status_subparser(subparsers)
     upstream = add_upstream_subparser(subparsers)
+    workflow = add_workflow_subparser(subparsers)
 
     # Only add the 'test' action if the test data files are available.
     if HAS_TESTS:
@@ -530,6 +559,7 @@  def setup_parser():
         'series': series,
         'patchwork': patchwork,
         'upstream': upstream,
+        'workflow': workflow,
         }
     return parsers
 
@@ -578,7 +608,7 @@  def parse_args(argv=None, config_fname=None, parsers=None):
             args.cmd = full
         if 'subcmd' in args and args.subcmd in aliases:
             args.subcmd = full
-    if args.cmd in ['series', 'upstream', 'patchwork'] and not args.subcmd:
+    if args.cmd in ['series', 'upstream', 'patchwork', 'workflow'] and not args.subcmd:
         parser.parse_args([args.cmd, '--help'])
 
     return args
diff --git a/tools/patman/control.py b/tools/patman/control.py
index cabd2138bb3..fe012f1a21b 100644
--- a/tools/patman/control.py
+++ b/tools/patman/control.py
@@ -344,6 +344,33 @@  def patchwork(args, test_db=None, pwork=None):
     finally:
         cser.close_database()
 
+def do_workflow(args, test_db=None):
+    """Process a 'workflow' subcommand
+
+    Args:
+        args (Namespace): Arguments to process
+        test_db (str or None): Directory containing the test database, None to
+            use the normal one
+    """
+    from patman import cseries
+    from patman import workflow
+
+    cser = cseries.Cseries(test_db)
+    try:
+        cser.open_database()
+        if args.subcmd == 'todo':
+            if args.clear:
+                workflow.todo_clear(cser, args.series)
+            else:
+                workflow.todo(cser, args.series, args.days)
+        elif args.subcmd == 'todo-list':
+            workflow.todo_list(cser, args.show_all)
+        else:
+            raise ValueError(f"Unknown workflow subcommand '{args.subcmd}'")
+    finally:
+        cser.close_database()
+
+
 def do_patman(args, test_db=None, pwork=None, cser=None):
     """Process a patman command
 
@@ -392,6 +419,8 @@  def do_patman(args, test_db=None, pwork=None, cser=None):
             upstream(args, test_db)
         elif args.cmd == 'patchwork':
             patchwork(args, test_db, pwork)
+        elif args.cmd == 'workflow':
+            do_workflow(args, test_db)
     except Exception as exc:
         terminal.tprint(f'patman: {type(exc).__name__}: {exc}',
                         colour=terminal.Color.RED)
diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py
index b4dce4cb853..8fcdbd340fb 100644
--- a/tools/patman/test_cseries.py
+++ b/tools/patman/test_cseries.py
@@ -4273,3 +4273,23 @@  Date:   .*
         with terminal.capture() as (out, _):
             cser.summary(None)
         self.assertNotIn('[todo]', out.getvalue())
+
+    def test_workflow_todo_cmdline(self):
+        """Test todo via the command line"""
+        cser = self.get_cser()
+        with terminal.capture():
+            cser.add('first', 'my description', allow_unmarked=True)
+
+        # Test via command line
+        self.db_close()
+        with terminal.capture() as (out, _):
+            self.run_args('workflow', 'todo', '-s', 'first', '7')
+        self.assertIn('marked for todo', out.getvalue())
+
+        with terminal.capture() as (out, _):
+            self.run_args('workflow', 'todo', '-s', 'first', '--clear')
+        self.assertIn('Todo cleared', out.getvalue())
+
+        with terminal.capture() as (out, _):
+            self.run_args('wf', 'todo-list')
+        self.assertIn('No todos due', out.getvalue())