[PATCH RFC 07/11] review-tui: add a "Find older revisions" action

Christian Brauner <[email protected]> Sat, 18 Jul 2026 00:37:43 +0200
Newsgroups org.kernel.linux.tools
Message-ID <[email protected]>
Offer discover_older_revisions() from the tracker's action menu,
available wherever manual revision linking is.  The search runs in a
lore worker; on success the list reloads (deferring to the DB mtime
poll when a modal is up).

Assisted-by: LLM
Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 src/b4/review_tui/_tracking_app.py | 55 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/src/b4/review_tui/_tracking_app.py b/src/b4/review_tui/_tracking_app.py
index 3057367..cc20ad6 100644
--- a/src/b4/review_tui/_tracking_app.py
+++ b/src/b4/review_tui/_tracking_app.py
@@ -90,6 +90,7 @@ _ACTION_SHORTCUTS: Dict[str, str] = {
     'unsnooze': 'u',
     'upgrade': 'U',
     'link': 'l',
+    'discover': 'o',
     'thank': 't',
     'abandon': 'A',
     'archive': 'x',
@@ -891,6 +892,36 @@ class TrackingApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[Optional[str]]):
             elif event.state == WorkerState.ERROR:
                 self.notify('Could not fetch series', severity='error')
             return
+        if event.worker.name == '_discover_older':
+            if event.state == WorkerState.SUCCESS:
+                result = event.worker.result or {}
+                error = result.get('error')
+                found = result.get('found', 0)
+                if error:
+                    self.notify(
+                        f'Older-revision search failed: {error}', severity='error'
+                    )
+                elif found:
+                    revs = result.get('revisions') or []
+                    if revs:
+                        rlist = ', '.join(f'v{r}' for r in revs)
+                        self.notify(f'Found and added: {rlist}')
+                    else:
+                        self.notify(f'Found {found} older revision(s)')
+                    # Reload so the new revisions show up right away; if a
+                    # modal is up, the DB mtime poll picks it up instead.
+                    if len(self.app.screen_stack) == 1:
+                        if self._selected_series:
+                            self._focus_change_id = self._selected_series.get(
+                                'change_id'
+                            )
+                        self._invalidate_caches()
+                        self._load_series()
+                else:
+                    self.notify('No older revisions found')
+            elif event.state == WorkerState.ERROR:
+                self.notify('Older-revision search failed', severity='error')
+            return
         if event.worker.name != '_startup_rescan':
             return
         if event.state == WorkerState.SUCCESS:
@@ -1338,6 +1369,7 @@ class TrackingApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[Optional[str]]):
                 actions.append(('upgrade', 'Upgrade to newer revision'))
             if status == 'new':
                 actions.append(('link', 'Manually link a revision'))
+                actions.append(('discover', 'Find older revisions'))
             actions.append(('abandon', 'Abandon series'))
             if status == 'new':
                 actions.append(('waiting', 'Mark as waiting on new revision'))
@@ -1365,6 +1397,7 @@ class TrackingApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[Optional[str]]):
                 actions.append(('thank', 'Send thank-you'))
             if status in ('reviewing', 'replied', 'partial', 'waiting'):
                 actions.append(('link', 'Manually link a revision'))
+                actions.append(('discover', 'Find older revisions'))
             # 'Return to reviewing' sits just above the abandon/archive block
             # rather than at the top of the menu.
             if status in ('accepted', 'partial', 'thanked'):
@@ -1389,6 +1422,7 @@ class TrackingApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[Optional[str]]):
             'thank': self.action_thank,
             'upgrade': self.action_update_revision,
             'link': self.action_link_revision,
+            'discover': self.action_discover_older,
             'archive': self.action_archive,
             'waiting': self.action_waiting,
             'snooze': self.action_snooze,
@@ -3882,6 +3916,27 @@ class TrackingApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[Optional[str]]):
             ),
         )
 
+    def action_discover_older(self) -> None:
+        """Search lore for older revisions of the selected series."""
+        if not self._selected_series or not self._identifier:
+            return
+        series = dict(self._selected_series)
+        config = b4.get_main_config()
+        linkmask = str(config.get('linkmask', ''))
+        topdir = b4.git_get_toplevel()
+        identifier = self._identifier
+        self.notify('Searching lore for older revisions…')
+
+        def _discover() -> Dict[str, Any]:
+            # The search machinery logs to the console; keep it from
+            # scribbling over the TUI.
+            with _quiet_worker():
+                return b4.review.tracking.discover_older_revisions(
+                    identifier, series, linkmask, topdir=topdir
+                )
+
+        run_lore_worker(self, _discover, name='_discover_older')
+
     def action_link_revision(self) -> None:
         """Manually link another revision to the selected series by msgid.
 

-- 
2.53.0