File 0001-ez-do-a-patch-id-match-when-pulling-in-trailer-updat.patch of Package b4

From: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu, 3 Oct 2024 11:07:20 -0400
Subject: ez: do a patch-id match when pulling in trailer updates
References: dig-support
Git-repo: https://git.kernel.org/pub/scm/utils/b4/b4.git
Git-commit: a9f99a4bda313e21116e7d06c012d2aa35840745
Patch-mainline: yes

Address two important limitations of trailers -u:

- trailer updates were applied even if the local patch has changed
- trailers sent to the cover letter were applied to all patches in the
current series, even if they were sent to a previous revision and the
commits no longer matched

Reported-by: Mark Brown <broonie@kernel.org>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=219342
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
---
 .gitignore                                    |  1 +
 pyproject.toml                                |  4 ++
 src/b4/__init__.py                            | 36 ++++++++++--
 src/b4/ez.py                                  | 57 ++++++++-----------
 ...lers-thread-with-followups-no-match.verify | 42 ++++++++++++++
 src/tests/test_ez.py                          |  2 +-
 6 files changed, 101 insertions(+), 41 deletions(-)
 create mode 100644 src/tests/samples/trailers-thread-with-followups-no-match.verify

diff --git a/.gitignore b/.gitignore
index 85421da2716c..d5e578b42347 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ __pycache__
 .venv
 qodana.yaml
 *.ipynb
+pytest.log
diff --git a/pyproject.toml b/pyproject.toml
index b15873172937..00f8cbdae296 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -45,6 +45,10 @@ b4 = "b4.command:cmd"
 [tool.pytest.ini_options]
 filterwarnings = "ignore:.*(pyopenssl|invalid escape sequence).*:DeprecationWarning"
 norecursedirs = ["tests/helpers", "patatt"]
+log_file = "pytest.log"
+log_file_level = "DEBUG"
+log_file_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
+log_file_date_format = "%Y-%m-%d %H:%M:%S"
 
 [tool.bumpversion]
 current_version = "0.14.3"
diff --git a/src/b4/__init__.py b/src/b4/__init__.py
index 2b7ba1d59797..80a54e2df4ea 100644
--- a/src/b4/__init__.py
+++ b/src/b4/__init__.py
@@ -1286,9 +1286,11 @@ class LoreMessage:
             self.references = list()
 
         if self.msg.get('References'):
-            for pair in email.utils.getaddresses([str(x) for x in self.msg.get_all('references', [])]):
-                if pair and pair[1].strip() and pair[1] not in self.references:
-                    self.references.append(pair[1])
+            for rbunch in self.msg.get_all('references', list()):
+                for rchunk in LoreMessage.clean_header(rbunch).split():
+                    rmsgid = rchunk.strip('<>')
+                    if rmsgid not in self.references:
+                        self.references.append(rmsgid)
 
         try:
             fromdata = email.utils.getaddresses([LoreMessage.clean_header(str(x))
@@ -1298,7 +1300,8 @@ class LoreMessage:
             if not len(self.fromname.strip()):
                 self.fromname = self.fromemail
         except IndexError:
-            pass
+            self.fromname = ''
+            self.fromemail = ''
 
         msgdate = self.msg.get('Date')
         if msgdate:
@@ -1331,7 +1334,7 @@ class LoreMessage:
 
         trailers, others = LoreMessage.find_trailers(self.body, followup=True)
         # We only pay attention to trailers that are sent in reply
-        if trailers and self.in_reply_to and not self.has_diff and not self.reply:
+        if trailers and self.references and not self.has_diff and not self.reply:
             logger.debug('A follow-up missing a Re: but containing a trailer with no patch diff')
             self.reply = True
         if self.reply:
@@ -4424,7 +4427,9 @@ def map_codereview_trailers(qmsgs: List[email.message.Message],
     qmid_map = dict()
     ref_map = dict()
     patchid_map = dict()
-    seen_msgids = set(ignore_msgids)
+    seen_msgids = set()
+    if ignore_msgids is not None:
+        seen_msgids.update(ignore_msgids)
     for qmsg in qmsgs:
         qmsgid = LoreMessage.get_clean_msgid(qmsg)
         if qmsgid in seen_msgids:
@@ -4438,6 +4443,7 @@ def map_codereview_trailers(qmsgs: List[email.message.Message],
             ref_map[qref].append(qlmsg.msgid)
 
     logger.info('Analyzing %s code-review messages', len(qmid_map))
+    covers = dict()
     for qmid, qlmsg in qmid_map.items():
         logger.debug('  new message: %s', qmid)
         if not qlmsg.reply:
@@ -4459,6 +4465,9 @@ def map_codereview_trailers(qmsgs: List[email.message.Message],
                 if (_qmsg.counter == 0 and (not _qmsg.counters_inferred or _qmsg.has_diffstat)
                         and _qmsg.msgid in ref_map):
                     logger.debug('  stopping: found the cover letter for %s', qlmsg.full_subject)
+                    if _qmsg.msgid not in covers:
+                        covers[_qmsg.msgid] = set()
+                    covers[_qmsg.msgid].add(qlmsg.msgid)
                     break
                 elif _qmsg.has_diff:
                     pqpid = _qmsg.git_patch_id
@@ -4479,4 +4488,19 @@ def map_codereview_trailers(qmsgs: List[email.message.Message],
             # Does it have 'patch-id: ' in the body?
             # TODO: once we have that functionality in b4 cr
 
+    if not covers:
+        return patchid_map
+
+    # find all patches directly below these covers
+    for cmsgid, fwmsgids in covers.items():
+        logger.debug('Looking at cover: %s', cmsgid)
+        for qmid, qlmsg in qmid_map.items():
+            if qlmsg.in_reply_to == cmsgid and qlmsg.git_patch_id:
+                pqpid = qlmsg.git_patch_id
+                for fwmsgid in fwmsgids:
+                    logger.debug('Adding cover follow-up %s to patch-id %s', fwmsgid, pqpid)
+                    if pqpid not in patchid_map:
+                        patchid_map[pqpid] = list()
+                    patchid_map[pqpid].append(qmid_map[fwmsgid])
+
     return patchid_map
diff --git a/src/b4/ez.py b/src/b4/ez.py
index 7c708e52cecd..958f41aa1c7c 100644
--- a/src/b4/ez.py
+++ b/src/b4/ez.py
@@ -1038,7 +1038,7 @@ def update_trailers(cmdargs: argparse.Namespace) -> None:
         sys.exit(1)
 
     ignore_commits = None
-    changeid = None
+    tracking = None
     cover = None
     msgid = None
     end = b4.git_revparse_obj('HEAD')
@@ -1050,7 +1050,6 @@ def update_trailers(cmdargs: argparse.Namespace) -> None:
         limit_committer = None
         start = get_series_start()
         cover, tracking = load_cover(strip_comments=True)
-        changeid = tracking['series'].get('change-id')
         if cmdargs.trailers_from:
             msgid = cmdargs.trailers_from
         else:
@@ -1145,15 +1144,16 @@ def update_trailers(cmdargs: argparse.Namespace) -> None:
         bbox.add_message(msg)
 
     commit_map = dict()
-    by_subject = dict()
+    by_patchid = dict()
     for lmsg in bbox.series[1].patches:
         if not lmsg:
             continue
-        by_subject[lmsg.subject] = lmsg.msgid
+        by_patchid[lmsg.git_patch_id] = lmsg.msgid
         commit_map[lmsg.msgid] = lmsg
 
     list_msgs = list()
-    if changeid and b4.can_network:
+    if tracking and b4.can_network:
+        changeid = tracking['series'].get('change-id')
         logger.info('Checking change-id "%s"', changeid)
         query = f'"change-id: {changeid}"'
         smsgs = b4.get_pi_search_results(query, nocache=True)
@@ -1171,44 +1171,33 @@ def update_trailers(cmdargs: argparse.Namespace) -> None:
         if tmsgs is not None:
             list_msgs += tmsgs
 
-    for list_msg in list_msgs:
-        llmsg = b4.LoreMessage(list_msg)
-        if not llmsg.trailers:
+    mismatches = set()
+    patchid_map = b4.map_codereview_trailers(list_msgs)
+    for patchid, llmsgs in patchid_map.items():
+        if patchid not in by_patchid:
+            logger.debug('Skipping patch-id %s: not found in the current series', patchid)
+            logger.debug('Ignoring follow-ups: %s', [x.subject for x in llmsgs])
             continue
-        if llmsg.subject in by_subject:
-            # Reparent to the commit and add to followups
-            commit = by_subject[llmsg.subject]
-            logger.debug('Mapped "%s" to commit %s', llmsg.subject, commit)
-            plmsg = commit_map[commit]
-            llmsg.in_reply_to = plmsg.msgid
-            bbox.followups.append(llmsg)
-        elif llmsg.counter == 0 and changeid:
-            logger.debug('Mapped "%s" to the cover letter', llmsg.subject)
-            # Reparent to the cover and add to followups
-            llmsg.in_reply_to = 'cover'
-            bbox.followups.append(llmsg)
-        else:
-            # Match by patch-id?
-            logger.debug('No match for %s', llmsg.subject)
-
-    if msgid or changeid:
+        for llmsg in llmsgs:
+            ltrailers, lmismatches = llmsg.get_trailers(sloppy=cmdargs.sloppytrailers)
+            for ltr in lmismatches:
+                mismatches.add((ltr.name, ltr.value, llmsg.fromname, llmsg.fromemail))
+            commit = by_patchid[patchid]
+            lmsg = commit_map[commit]
+            logger.debug('Adding %s to %s', [x.as_string() for x in ltrailers], lmsg.msgid)
+            lmsg.followup_trailers += ltrailers
+
+    if msgid or tracking:
         logger.debug('Will query by change-id')
         codereview_trailers = False
     else:
         codereview_trailers = True
 
     lser = bbox.get_series(sloppytrailers=cmdargs.sloppytrailers, codereview_trailers=codereview_trailers)
-    mismatches = list(lser.trailer_mismatches)
+    mismatches.update(lser.trailer_mismatches)
     config = b4.get_main_config()
     seen_froms = set()
     logger.info('---')
-    # Do we have follow-up tralers sent to the cover?
-    if lser.patches[0] and lser.patches[0].followup_trailers:
-        logger.debug('Applying follow-up trailers from cover to all patches')
-        for pmsg in lser.patches[1:]:
-            logger.debug('  %s (%s)', pmsg.subject, pmsg.msgid)
-            logger.debug('  + %s', [x.as_string() for x in lser.patches[0].followup_trailers])
-            pmsg.followup_trailers += lser.patches[0].followup_trailers
 
     updates = dict()
     for lmsg in lser.patches[1:]:
@@ -1246,7 +1235,7 @@ def update_trailers(cmdargs: argparse.Namespace) -> None:
     if len(mismatches):
         logger.critical('---')
         logger.critical('NOTE: some trailers ignored due to from/email mismatches:')
-        for tname, tvalue, fname, femail in lser.trailer_mismatches:
+        for tname, tvalue, fname, femail in mismatches:
             logger.critical('    ! Trailer: %s: %s', tname, tvalue)
             logger.critical('     Msg From: %s <%s>', fname, femail)
         logger.critical('NOTE: Rerun with -S to apply them anyway')
diff --git a/src/tests/samples/trailers-thread-with-followups-no-match.verify b/src/tests/samples/trailers-thread-with-followups-no-match.verify
new file mode 100644
index 000000000000..e4f2f3cce278
--- /dev/null
+++ b/src/tests/samples/trailers-thread-with-followups-no-match.verify
@@ -0,0 +1,42 @@
+konstantin@linuxfoundation.org
+Minor typo changes imitation
+Life imitatus artem.
+
+Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
+Signed-off-by: Test Override <test-override@example.com>
+---
+konstantin@linuxfoundation.org
+Add some paragraphs to lipsum
+Mostly junk. As expected.
+
+Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
+Signed-off-by: Test Override <test-override@example.com>
+---
+konstantin@linuxfoundation.org
+Add more lines to file 1
+This is a second patch in the series. It needed a paragraph with the
+words of wisdom.
+
+Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
+Signed-off-by: Test Override <test-override@example.com>
+---
+konstantin@linuxfoundation.org
+Remove line 2 from file2
+Etiam in rhoncus lacus. Ut velit nisl, mollis ac commodo vitae, ultrices
+quis felis. Proin varius hendrerit volutpat. Pellentesque nec laoreet
+quam, eu ullamcorper mi. Donec ut purus ac sapien dignissim elementum eu
+ac ante. Mauris sed faucibus orci.
+
+Vivamus eleifend accumsan ultricies. Cras at erat nec mauris iaculis
+eleifend sit amet eu libero. Suspendisse auctor a erat at vestibulum.
+Nullam efficitur quis turpis quis sodales.
+
+Nunc elementum hendrerit arcu eget feugiat. Nulla placerat pellentesque
+metus, nec rutrum nulla porttitor vel. Ut tristique commodo sem, ac
+sollicitudin enim pharetra et. Mauris sed tellus vitae nunc sollicitudin
+fermentum. Phasellus dui elit, malesuada quis metus vel, blandit
+tristique felis. Aenean quis tempus enim.
+
+Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
+Signed-off-by: Test Override <test-override@example.com>
+---
diff --git a/src/tests/test_ez.py b/src/tests/test_ez.py
index 1b02e7bede55..7351f7992079 100644
--- a/src/tests/test_ez.py
+++ b/src/tests/test_ez.py
@@ -25,7 +25,7 @@ def prepdir(gitdir):
      {'shazam-am-flags': '--signoff'}),
     # Test matching trailer updates by subject when patch-id changes
     ('trailers-thread-with-followups', None, (b'vivendum', b'addendum'), [],
-     ['log', '--format=%ae%n%s%n%b---', 'HEAD~4..'], 'trailers-thread-with-followups',
+     ['log', '--format=%ae%n%s%n%b---', 'HEAD~4..'], 'trailers-thread-with-followups-no-match',
      {'shazam-am-flags': '--signoff'}),
     # Test that we properly perserve commits with --- in them
     ('trailers-thread-with-followups', 'trailers-with-tripledash', None, [],
-- 
2.51.0

openSUSE Build Service is sponsored by