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