patman: Implement the patchwork subcommand

Add a command to allow setting and getting the patchwork project. This
is needed so that patman can use the correct ID when talking to the
patchwork server.

To support testing, allow passing in the test database, patchwork
object and Cseries object. Fake versions can then easily be provided for
certain test cases.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py
index 1b2a32c..5943aa7 100644
--- a/tools/patman/cmdline.py
+++ b/tools/patman/cmdline.py
@@ -116,6 +116,29 @@
                         help='Show comments from each patch')
 
 
+def add_patchwork_subparser(subparsers):
+    """Add the 'patchwork' subparser
+
+    Args:
+        subparsers (argparse action): Subparser parent
+
+    Return:
+        ArgumentParser: patchwork subparser
+    """
+    patchwork = subparsers.add_parser(
+        'patchwork',
+        help='Manage patchwork connection')
+    patchwork.defaults_cmds = [
+        ['set-project', 'U-Boot'],
+    ]
+    patchwork_subparsers = patchwork.add_subparsers(dest='subcmd')
+    patchwork_subparsers.add_parser('get-project')
+    uset = patchwork_subparsers.add_parser('set-project')
+    uset.add_argument(
+        'project_name', help="Patchwork project name, e.g. 'U-Boot'")
+    return patchwork
+
+
 def add_send_subparser(subparsers):
     """Add the 'send' subparser
 
@@ -201,6 +224,7 @@
 
     subparsers = parser.add_subparsers(dest='cmd')
     add_send_subparser(subparsers)
+    patchwork = add_patchwork_subparser(subparsers)
     add_status_subparser(subparsers)
 
     # Only add the 'test' action if the test data files are available.
@@ -211,6 +235,7 @@
 
     parsers = {
         'main': parser,
+        'patchwork': patchwork,
         }
     return parsers
 
diff --git a/tools/patman/control.py b/tools/patman/control.py
index ec94b23..a7d381c 100644
--- a/tools/patman/control.py
+++ b/tools/patman/control.py
@@ -113,11 +113,42 @@
                                  show_comments, False, pwork)
 
 
-def do_patman(args):
+def patchwork(args, test_db=None, pwork=None):
+    """Process a 'patchwork' subcommand
+    Args:
+        args (Namespace): Arguments to process
+        test_db (str or None): Directory containing the test database, None to
+            use the normal one
+        pwork (Patchwork): Patchwork object to use
+    """
+    cser = cseries.Cseries(test_db)
+    try:
+        cser.open_database()
+        if args.subcmd == 'set-project':
+            if not pwork:
+                pwork = Patchwork(args.patchwork_url)
+            cser.project_set(pwork, args.project_name)
+        elif args.subcmd == 'get-project':
+            info = cser.project_get()
+            if not info:
+                raise ValueError("Project has not been set; use 'patman patchwork set-project'")
+            name, pwid, link_name = info
+            print(f"Project '{name}' patchwork-ID {pwid} link-name {link_name}")
+        else:
+            raise ValueError(f"Unknown patchwork subcommand '{args.subcmd}'")
+    finally:
+        cser.close_database()
+
+def do_patman(args, test_db=None, pwork=None, cser=None):
     """Process a patman command
 
     Args:
         args (Namespace): Arguments to process
+        test_db (str or None): Directory containing the test database, None to
+            use the normal one
+        pwork (Patchwork): Patchwork object to use, or None to create one
+        cser (Cseries): Cseries object to use when executing the command,
+            or None to create one
     """
     if args.full_help:
         with resources.path('patman', 'README.rst') as readme:
@@ -150,6 +181,8 @@
             patchwork_status(args.branch, args.count, args.start, args.end,
                              args.dest_branch, args.force, args.show_comments,
                              args.patchwork_url)
+        elif args.cmd == 'patchwork':
+            patchwork(args, test_db, pwork)
     except Exception as exc:
         terminal.tprint(f'patman: {type(exc).__name__}: {exc}',
                         colour=terminal.Color.RED)