[Concept,29/32] patman: Improve autolink wait with progress and backoff

Message ID 20260226200106.1727176-30-sjg@u-boot.org
State New
Headers
Series patman: Add multi-upstream support |

Commit Message

Simon Glass Feb. 26, 2026, 8 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Use tout.progress() to show an updating status line while waiting for
a series to appear on patchwork, instead of printing the full match
table every iteration. Only show matches when they change.

Back off the sleep interval from 5s up to 30s to reduce server load.
On failure, print the patman autolink command to use later. Remove the
noisy "Sleeping for N seconds" message from sleep().

Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 tools/patman/cser_helper.py  |  1 -
 tools/patman/cseries.py      | 43 +++++++++++++++++++++++++++++++-----
 tools/patman/test_common.py  |  1 +
 tools/patman/test_cseries.py | 28 +++++++++++++++--------
 4 files changed, 57 insertions(+), 16 deletions(-)
  

Patch

diff --git a/tools/patman/cser_helper.py b/tools/patman/cser_helper.py
index dd654738c06..ec3956db4d5 100644
--- a/tools/patman/cser_helper.py
+++ b/tools/patman/cser_helper.py
@@ -205,7 +205,6 @@  class CseriesHelper:
         Args:
             time_s (float): Amount of seconds to sleep for
         """
-        print(f'Sleeping for {time_s} seconds')
         if self._fake_time is not None:
             self._fake_sleep(time_s)
         else:
diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py
index 81a8712b3de..0b4ce91b024 100644
--- a/tools/patman/cseries.py
+++ b/tools/patman/cseries.py
@@ -284,27 +284,58 @@  class Cseries(cser_helper.CseriesHelper):
         start = self.get_time()
         stop = start + wait_s
         sleep_time = 5
+        last_options = None
         while True:
             pws, options, name, version, desc = self.link_search(
                 pwork, series, version)
             if pws:
+                tout.clear_progress()
                 if wait_s:
                     tout.notice('Link completed after '
                                 f'{self.get_time() - start} seconds')
                 break
 
-            print(f"Possible matches for '{name}' v{version} desc '{desc}':")
-            print('  Link  Version  Description')
-            for opt in options:
-                print(f"{opt['id']:6}  {opt['version']:7}  {opt['name']}")
             if not wait_s or self.get_time() > stop:
+                tout.clear_progress()
+                if options != last_options:
+                    self._show_autolink_matches(name, version, desc,
+                                                options)
                 delay = f' after {wait_s} seconds' if wait_s else ''
-                raise ValueError(f"Cannot find series '{desc}{delay}'")
-
+                raise ValueError(
+                    f"Cannot find series '{desc}'{delay}; "
+                    'to try again later:\n'
+                    f"  patman series autolink -s {name} -V {version}")
+
+            if options != last_options:
+                tout.clear_progress()
+                self._show_autolink_matches(name, version, desc, options)
+                last_options = options
+
+            elapsed = int(self.get_time() - start)
+            tout.progress(
+                f'Waiting for series on patchwork ({elapsed}s)')
             self.sleep(sleep_time)
+            sleep_time = min(sleep_time + 5, 30)
 
         self.link_set(name, version, pws, update_commit)
 
+    def _show_autolink_matches(self, name, version, desc, options):
+        """Show possible autolink matches
+
+        Args:
+            name (str): Series name
+            version (int): Series version
+            desc (str): Series description
+            options (list of dict): Possible matches from patchwork
+        """
+        print(f"Possible matches for '{name}' v{version} desc '{desc}':")
+        if options:
+            print('  Link  Version  Description')
+            for opt in options:
+                print(f"{opt['id']:6}  {opt['version']:7}  {opt['name']}")
+        else:
+            print('  (none)')
+
     def link_auto_all(self, pwork, update_commit, link_all_versions,
                       replace_existing, dry_run, show_summary=True):
         """Automatically find a series link by looking in patchwork
diff --git a/tools/patman/test_common.py b/tools/patman/test_common.py
index 7da995dda22..6cf007cc1f2 100644
--- a/tools/patman/test_common.py
+++ b/tools/patman/test_common.py
@@ -66,6 +66,7 @@  class TestCommon:
         self.gitdir = os.path.join(self.tmpdir, '.git')
         tout.init(tout.DEBUG if self.verbosity else tout.INFO,
                   allow_colour=False)
+        tout.stdout_is_tty = False
 
     def tearDown(self):
         """Delete the temporary dir"""
diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py
index 3d475956ff9..c0beb128265 100644
--- a/tools/patman/test_cseries.py
+++ b/tools/patman/test_cseries.py
@@ -3750,15 +3750,25 @@  Date:   .*
         with terminal.capture() as (out, _):
             cser.link_auto(pwork, 'second3', 3, True, 50)
         itr = iter(out.getvalue().splitlines())
-        for i in range(7):
-            self.assertEqual(
-                "Possible matches for 'second' v3 desc 'Series for my board':",
-                next(itr), f'failed at i={i}')
-            self.assertEqual('  Link  Version  Description', next(itr))
-            self.assertEqual('   456        1  Series for my board', next(itr))
-            self.assertEqual('   457        2  Series for my board', next(itr))
-            self.assertEqual('Sleeping for 5 seconds', next(itr))
-        self.assertEqual('Link completed after 35 seconds', next(itr))
+
+        # Matches shown only once (they don't change between retries)
+        self.assertEqual(
+            "Possible matches for 'second' v3 desc 'Series for my board':",
+            next(itr))
+        self.assertEqual('  Link  Version  Description', next(itr))
+        self.assertEqual('   456        1  Series for my board', next(itr))
+        self.assertEqual('   457        2  Series for my board', next(itr))
+
+        # Progress messages with backoff (5, 10, 15, 20s sleeps)
+        self.assertEqual(
+            'Waiting for series on patchwork (0s)...', next(itr))
+        self.assertEqual(
+            'Waiting for series on patchwork (5s)...', next(itr))
+        self.assertEqual(
+            'Waiting for series on patchwork (15s)...', next(itr))
+        self.assertEqual(
+            'Waiting for series on patchwork (30s)...', next(itr))
+        self.assertEqual('Link completed after 50 seconds', next(itr))
         self.assertRegex(
             next(itr), 'Checking out upstream commit refs/heads/base: .*')
         self.assertEqual(