[Concept,30/37] patman: Add save-notes/show-notes and review integration

Message ID 20260404213020.372253-31-sjg@u-boot.org
State New
Headers
Series patman: Autolink fixes and AI-assisted patch review |

Commit Message

Simon Glass April 4, 2026, 9:29 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

Add series subcommands for storing and displaying review-handling notes:

 - 'patman s save-notes' reads review-notes.txt and stores it against
   the current series version in the database
 - 'patman s show-notes' displays notes from all previous versions

Also wire up the review command to handle --learn-voice and --sync
modes, and use _setup_patchwork() for consistent patchwork resolution.

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

 tools/patman/control.py | 21 ++++++++++++++-------
 tools/patman/cseries.py | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+), 7 deletions(-)
  

Patch

diff --git a/tools/patman/control.py b/tools/patman/control.py
index 9ba9b6e0b8e..e664c93dafd 100644
--- a/tools/patman/control.py
+++ b/tools/patman/control.py
@@ -105,6 +105,7 @@  def patchwork_status(branch, count, start, end, dest_branch, force,
         single_thread)
 
 
+
 def _setup_patchwork(cser, pwork, ups, pw_url):
     """Set up a Patchwork instance from upstream and project settings
 
@@ -230,6 +231,10 @@  def do_series(args, test_db=None, pwork=None, cser=None):
             cser.remove(args.series, dry_run=args.dry_run)
         elif args.subcmd == 'rm-version':
             cser.version_remove(args.series, args.version, dry_run=args.dry_run)
+        elif args.subcmd == 'save-notes':
+            cser.save_notes(args.series, args.notes_file)
+        elif args.subcmd == 'show-notes':
+            cser.show_notes(args.series)
         elif args.subcmd == 'rename':
             cser.rename(args.series, args.new_name, dry_run=args.dry_run)
         elif args.subcmd == 'set-upstream':
@@ -365,7 +370,7 @@  def patchwork(args, test_db=None, pwork=None):
             cser.db.patchwork_delete(args.remote)
             cser.commit()
             ups_str = f" for upstream '{args.remote}'" if args.remote else ''
-            tout.info(f'Deleted patchwork project{ups_str}')
+            tout.notice(f'Deleted patchwork project{ups_str}')
         elif args.subcmd == 'ls':
             cser.project_list()
         else:
@@ -419,11 +424,13 @@  def do_review(args, test_db=None, pwork=None, cser=None):
     try:
         cser.open_database()
 
-        ups = args.upstream
-        if not ups:
-            ups = cser.db.upstream_get_default()
-        pwork = _setup_patchwork(
-            cser, pwork, ups, args.patchwork_url)
+        # Resolve patchwork URL
+        if not pwork and not args.learn_voice and not args.sync:
+            ups = args.upstream
+            if not ups:
+                ups = cser.db.upstream_get_default()
+            pwork = _setup_patchwork(
+                cser, pwork, ups, args.patchwork_url)
 
         return review_mod.do_review(args, pwork, cser)
     finally:
@@ -480,7 +487,7 @@  def do_patman(args, test_db=None, pwork=None, cser=None):
         elif args.cmd == 'patchwork':
             patchwork(args, test_db, pwork)
         elif args.cmd == 'review':
-            do_review(args, test_db, pwork, cser)
+            ret_code = do_review(args, test_db, pwork, cser) or 0
         elif args.cmd == 'workflow':
             do_workflow(args, test_db)
     except Exception as exc:  # pylint: disable=W0718
diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py
index e272a5839fc..3af9ef19eab 100644
--- a/tools/patman/cseries.py
+++ b/tools/patman/cseries.py
@@ -7,11 +7,13 @@ 
 
 import asyncio
 from collections import OrderedDict, defaultdict
+import os
 import pygit2
 
 from u_boot_pylib import cros_subprocess
 from u_boot_pylib import gitutil
 from u_boot_pylib import terminal
+from u_boot_pylib import tools
 from u_boot_pylib import tout
 
 from patman import patchstream
@@ -861,6 +863,40 @@  class Cseries(cser_helper.CseriesHelper):
         if dry_run:
             tout.info('Dry run completed')
 
+    def save_notes(self, series, notes_file='review-notes.txt'):
+        """Save review-handling notes for the current series version
+
+        Args:
+            series (str): Series name, or None for current branch
+            notes_file (str): Path to the notes file
+        """
+        if not os.path.exists(notes_file):
+            raise FileNotFoundError(f"Notes file not found: {notes_file}")
+
+        notes = tools.read_file(notes_file, binary=False).strip()
+        ser, version = self._parse_series_and_version(series, None)
+        svid = self.get_series_svid(ser.idnum, version)
+        self.db.ser_ver_set_notes(svid, notes)
+        self.commit()
+        tout.notice(f"Saved notes for '{ser.name}' v{version}")
+
+    def show_notes(self, series):
+        """Show review-handling notes from all versions of a series
+
+        Args:
+            series (str): Series name, or None for current branch
+        """
+        ser, _ = self._parse_series_and_version(series, None)
+        all_notes = self.db.ser_ver_get_all_notes(ser.idnum)
+        if not all_notes:
+            tout.notice(f"No review notes for '{ser.name}'")
+            return
+        for version, notes in all_notes:
+            terminal.tprint(f'\n--- v{version} ---',
+                            colour=terminal.Color.YELLOW)
+            print(notes)
+            print()
+
     def show_info(self, series):
         """Show detailed information about a series and all its versions