[Concept,08/29] patman: Fix patchwork search for titles containing '+'

Message ID 20260501110040.1874719-9-sjg@u-boot.org
State New
Headers
Series patman: Review-flow improvements and shared helpers |

Commit Message

Simon Glass May 1, 2026, 11 a.m. UTC
  From: Simon Glass <sjg@chromium.org>

The query_series() method replaces spaces with '+' for the patchwork
API query string. This means a literal '+' in a series title (e.g.
'FS and FW loader + FIP loader') becomes a space, corrupting the
search.

Use urllib.parse.quote() for proper URL encoding so that special
characters like '+' are escaped as '%2B'.

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

 tools/patman/patchwork.py    |  4 +++-
 tools/patman/test_cseries.py | 39 ++++++++++++++++++++++--------------
 2 files changed, 27 insertions(+), 16 deletions(-)
  

Patch

diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py
index 46bacee50d2..f71214d4b8f 100644
--- a/tools/patman/patchwork.py
+++ b/tools/patman/patchwork.py
@@ -7,6 +7,7 @@ 
 
 import asyncio
 import re
+from urllib.parse import quote_plus
 
 import aiohttp
 from collections import namedtuple
@@ -281,8 +282,9 @@  class Patchwork:
         Return:
             list of series matches, each a dict, see get_series()
         """
-        query = desc.replace(' ', '+')
+        query = quote_plus(desc, safe=':')
         subpath = f'series/?project={self.proj_id}&q={query}'
+        tout.info(f"Searching for '{desc}'")
         tout.debug(f'  GET {self.url}/api/1.2/{subpath}')
         return await self._request(client, subpath)
 
diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py
index 7d6a6179c1e..2d56b409a57 100644
--- a/tools/patman/test_cseries.py
+++ b/tools/patman/test_cseries.py
@@ -1101,16 +1101,19 @@  Tested-by: Mary Smith <msmith@wibble.com>   # yak
         self.assertFalse(cser.project_get())
         cser.project_set(pwork, 'U-Boot', quiet=True)
 
-        self.assertEqual(
-            (self.SERIES_ID_SECOND_V1, None, 'second', 1,
-             'Series for my board'),
-            cser.link_search(pwork, 'second', 1))
+        with terminal.capture():
+            self.assertEqual(
+                (self.SERIES_ID_SECOND_V1, None, 'second', 1,
+                 'Series for my board'),
+                cser.link_search(pwork, 'second', 1))
 
         with terminal.capture():
             cser.increment('second')
 
-        self.assertEqual((457, None, 'second', 2, 'Series for my board'),
-                         cser.link_search(pwork, 'second', 2))
+        with terminal.capture():
+            self.assertEqual(
+                (457, None, 'second', 2, 'Series for my board'),
+                cser.link_search(pwork, 'second', 2))
 
     def test_series_link_auto_name(self):
         """Test finding the patchwork link for a cseries with auto name"""
@@ -1197,13 +1200,15 @@  Tested-by: Mary Smith <msmith@wibble.com>   # yak
         self.assertFalse(cser.project_get())
         cser.project_set(pwork, 'U-Boot', quiet=True)
 
-        self.assertEqual(
-            (self.SERIES_ID_SECOND_V1, None, 'second', 1,
-             'Series for my board'),
-            cser.link_search(pwork, 'second', 1))
-        self.assertEqual((457, None, 'second', 2, 'Series for my board'),
-                         cser.link_search(pwork, 'second', 2))
-        res = cser.link_search(pwork, 'second', 3)
+        with terminal.capture():
+            self.assertEqual(
+                (self.SERIES_ID_SECOND_V1, None, 'second', 1,
+                 'Series for my board'),
+                cser.link_search(pwork, 'second', 1))
+            self.assertEqual(
+                (457, None, 'second', 2, 'Series for my board'),
+                cser.link_search(pwork, 'second', 2))
+            res = cser.link_search(pwork, 'second', 3)
         self.assertEqual(
             (None,
              [{'id': self.SERIES_ID_SECOND_V1, 'name': 'Series for my board',
@@ -1444,7 +1449,9 @@  Tested-by: Mary Smith <msmith@wibble.com>   # yak
         with terminal.capture() as (out, _):
             self.run_args('series', 'autolink-all', '-a', '--no-update',
                           pwork=pwork)
-        itr = iter(out.getvalue().splitlines())
+        lines = [ln for ln in out.getvalue().splitlines()
+                 if not ln.startswith('Searching for ')]
+        itr = iter(lines)
         self.assertEqual(
             '1 series linked, 1 already linked, 1 not found (3 requests)',
             next(itr))
@@ -3784,7 +3791,9 @@  Date:   .*
         cser.set_fake_time(h_sleep)
         with terminal.capture() as (out, _):
             cser.link_auto(pwork, 'second3', 3, True, 50)
-        itr = iter(out.getvalue().splitlines())
+        lines = [ln for ln in out.getvalue().splitlines()
+                 if not ln.startswith('Searching for ')]
+        itr = iter(lines)
 
         # Matches shown only once (they don't change between retries)
         self.assertEqual(