[Concept,06/32] patman: Auto-detect upstream for series during migration

Message ID 20260226200106.1727176-7-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>

When the database is upgraded to v5, try to auto-detect the upstream
for each series that has a NULL upstream. This queries the git branch's
remote tracking configuration and sets the upstream if a real remote
(not local tracking with '.') is found.

Series where auto-detection fails are still reported as a warning so the
user can set them manually.

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

 tools/patman/cser_helper.py  | 35 +++++++++++++++++++++++++++++++----
 tools/patman/test_cseries.py | 32 ++++++++++++++++++++++----------
 2 files changed, 53 insertions(+), 14 deletions(-)
  

Patch

diff --git a/tools/patman/cser_helper.py b/tools/patman/cser_helper.py
index 0b29d52bc00..74bccfcfbc6 100644
--- a/tools/patman/cser_helper.py
+++ b/tools/patman/cser_helper.py
@@ -118,12 +118,39 @@  class CseriesHelper:
             self.db.open_it()
 
     def _check_null_upstreams(self):
-        """Warn about series that have no upstream set"""
+        """Detect and warn about series that have no upstream set
+
+        For each series without an upstream, try to detect it from the
+        git branch's remote tracking configuration. Any series that
+        cannot be auto-detected are reported as a warning.
+        """
         names = self.db.series_get_null_upstream()
-        if names:
+        if not names:
+            return
+
+        still_null = []
+        git_dir = self.gitdir or '.git'
+        for name in names:
+            remote_name = None
+            if gitutil.check_branch(name, git_dir=self.gitdir):
+                us_ref, _ = gitutil.get_upstream(git_dir, name)
+                if us_ref and '/' in us_ref and not us_ref.startswith(
+                        'refs/'):
+                    remote_name = us_ref.split('/')[0]
+            if remote_name:
+                idnum = self.db.series_find_by_name(name)
+                self.db.series_set_upstream(idnum, remote_name)
+                tout.progress(f"Set upstream for series '{name}' to "
+                              f"'{remote_name}'", trailer='')
+            else:
+                still_null.append(name)
+
+        if len(still_null) < len(names):
+            self.db.commit()
+        if still_null:
             tout.warning(
-                f'{len(names)} series without an upstream:')
-            for name in names:
+                f'{len(still_null)} series without an upstream:')
+            for name in still_null:
                 tout.warning(f'  {name}')
 
     def close_database(self):
diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py
index cf0d8cfdab6..23527cfa8f0 100644
--- a/tools/patman/test_cseries.py
+++ b/tools/patman/test_cseries.py
@@ -3367,11 +3367,20 @@  Date:   .*
 
     def test_migrate_upstream_warning(self):
         """Test that migrating to v5 warns about series without upstream"""
+        self.make_git_tree()
+
+        # Set 'first' branch to track a remote-style upstream so that
+        # auto-detection can find it
+        self.repo.config.set_multivar('branch.first.remote', '', 'origin')
+        self.repo.config.set_multivar('branch.first.merge', '',
+                                      'refs/heads/main')
+
         db = database.Database(f'{self.tmpdir}/.patman2.db')
         with terminal.capture():
             db.open_it()
 
-        # Create a v4 database with some series
+        # Create a v4 database with some series; 'first' has a matching
+        # branch with a detectable remote, 'second' and 'third' do not
         with terminal.capture() as (out, _):
             db.migrate_to(4)
         self.assertEqual(
@@ -3390,10 +3399,9 @@  Date:   .*
         db.commit()
         db.close()
 
-        # Now open via CseriesHelper which triggers migration and check
-        self.make_git_tree()
         cser = cseries.Cseries(self.tmpdir, terminal.COLOR_NEVER)
         cser.topdir = self.tmpdir
+        cser.gitdir = self.gitdir
 
         # Point at our v4 database
         database.Database.instances = {}
@@ -3403,18 +3411,22 @@  Date:   .*
             old_version = cser.db.start()
         self.assertEqual(4, old_version)
 
-        # Set upstream on one series so only two are reported
-        idnum = cser.db.series_find_by_name('second')
-        cser.db.series_set_upstream(idnum, 'us')
-        cser.db.commit()
-
-        with terminal.capture() as (_, err):
+        # 'first' should be auto-detected, 'second' and 'third' have no
+        # matching branch with a remote upstream
+        with terminal.capture() as (out, err):
             cser._check_null_upstreams()
+        self.assertIn("Set upstream for series 'first' to 'origin'",
+                      out.getvalue())
         lines = err.getvalue().strip().splitlines()
         self.assertEqual('2 series without an upstream:', lines[0])
-        self.assertEqual('  first', lines[1])
+        self.assertEqual('  second', lines[1])
         self.assertEqual('  third', lines[2])
 
+        # Check that 'first' was actually updated in the database
+        slist = cser.db.series_get_dict()
+        self.assertEqual('origin', slist['first'].upstream)
+        self.assertIsNone(slist['second'].upstream)
+
         cser.db.close()
 
     def test_series_scan(self):