fromupstream: Support picking patches with message-id

We're moving towards using message-ids for tracking patches to the linux
kernel. Instead of requiring users to find the patchwork ID from the
website/UI, support deriving that ID based on the message-id passed in
as an argument. The same semantics as patchwork picks apply here, so
reuse a bunch of that code and hardcode a few different locations to
look for patchwork instances that can supply the patch in mbox format.

BUG=chromium:1008546
TEST=run with valid message id and invalid message id for a patch on
lkml

Change-Id: Ie349e890221b1a421938faf837d4b0b9e6e39937
Signed-off-by: Stephen Boyd <swboyd@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/1828169
Reviewed-by: Douglas Anderson <dianders@chromium.org>
Commit-Queue: Douglas Anderson <dianders@chromium.org>
diff --git a/contrib/fromupstream.py b/contrib/fromupstream.py
index 11ebf95..c9d5510 100755
--- a/contrib/fromupstream.py
+++ b/contrib/fromupstream.py
@@ -20,6 +20,7 @@
 import sys
 import textwrap
 import urllib
+import xmlrpclib
 
 errprint = functools.partial(print, file=sys.stderr)
 
@@ -29,6 +30,14 @@
     'https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux.git',
 )
 
+PATCHWORK_URLS = (
+    'https://lore.kernel.org/patchwork',
+    'https://patchwork.kernel.org',
+    'https://patchwork.ozlabs.org',
+    'https://patchwork.freedesktop.org',
+    'https://patchwork.linux-mips.org',
+)
+
 COMMIT_MESSAGE_WIDTH = 75
 
 _PWCLIENTRC = os.path.expanduser('~/.pwclientrc')
@@ -114,19 +123,10 @@
     indent = ' ' * (len(prefix) + 1)
     return textwrap.fill(line, COMMIT_MESSAGE_WIDTH, subsequent_indent=indent)
 
-def _match_patchwork(match, args):
-    """Match location: pw://### or pw://PROJECT/###."""
-    pw_project = match.group(2)
-    patch_id = int(match.group(3))
-
-    if args['debug']:
-        print('_match_patchwork: pw_project=%s, patch_id=%d' %
-              (pw_project, patch_id))
-
+def _pick_patchwork(url, patch_id, args):
     if args['tag'] is None:
         args['tag'] = 'FROMLIST: '
 
-    url = _get_pw_url(pw_project)
     opener = urllib.urlopen('%s/patch/%d/mbox' % (url, patch_id))
     if opener.getcode() != 200:
         errprint('Error: could not download patch - error code %d'
@@ -157,6 +157,40 @@
     git_am.communicate(patch_contents)
     return git_am.returncode
 
+def _match_patchwork(match, args):
+    """Match location: pw://### or pw://PROJECT/###."""
+    pw_project = match.group(2)
+    patch_id = int(match.group(3))
+
+    if args['debug']:
+        print('_match_patchwork: pw_project=%s, patch_id=%d' %
+              (pw_project, patch_id))
+
+    url = _get_pw_url(pw_project)
+    return _pick_patchwork(url, patch_id, args)
+
+def _match_msgid(match, args):
+    """Match location: msgid://MSGID."""
+    msgid = match.group(1)
+
+    if args['debug']:
+        print('_match_msgid: message_id=%s' % (msgid))
+
+    # Patchwork requires the brackets so force it
+    msgid = '<' + msgid + '>'
+    url = None
+    for url in PATCHWORK_URLS:
+        rpc = xmlrpclib.ServerProxy(url + '/xmlrpc/')
+        res = rpc.patch_list({'msgid': msgid})
+        if res:
+            patch_id = res[0]['id']
+            break
+    else:
+        errprint('Error: could not find patch based on message id')
+        sys.exit(1)
+
+    return _pick_patchwork(url, patch_id, args)
+
 def _match_linux(match, args):
     """Match location: linux://HASH."""
     commit = match.group(1)
@@ -300,6 +334,7 @@
                         'where PROJECT is defined in ~/.pwclientrc; if no '
                         'PROJECT is specified, the default is retrieved from '
                         '~/.pwclientrc), '
+                        'Message-ID (msgid://MSGID), '
                         'linux commit like linux://HASH, or '
                         'git reference like git://remote/branch/HASH or '
                         'git://repoURL#branch/HASH or '
@@ -345,6 +380,7 @@
 
     re_matches = (
         (re.compile(r'^pw://(([^/]+)/)?(\d+)'), _match_patchwork),
+        (re.compile(r'^msgid://<?([^>]*)>?'), _match_msgid),
         (re.compile(r'^linux://([0-9a-f]+)'), _match_linux),
         (re.compile(r'^(from)?git://([^/\#]+)/([^#]+)/([0-9a-f]+)$'),
          _match_fromgit),