From: Andrea Righi <arighi@nvidia.com>
To: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Cc: tools@kernel.org
Subject: [PATCH] mbox: add option to save all series revisions
Date: Mon, 28 Sep 2026 09:02:52 +0200 [thread overview]
Message-ID: <20260928070252.3678339-1-arighi@nvidia.com> (raw)
There is currently no way to save a patch series together with all its
previous revisions in one mbox. Sometimes, having the full history in a
single mailbox can be helpful to see how the series evolved.
Add -a/--all-revisions to b4 mbox to find older and newer revisions and
save their discussion threads in revision order. Reuse the revision
search from review tracking and document the 12-month limit on backward
discovery.
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Andrea Righi <arighi@nvidia.com>
---
docs/maintainer/mbox.rst | 12 +++++++
src/b4/command.py | 6 ++++
src/b4/man/b4.1 | 4 +++
src/b4/man/b4.1.rst | 4 +++
src/b4/mbox.py | 35 +++++++++++++++++--
src/tests/test_mbox.py | 73 ++++++++++++++++++++++++++++++++++++++++
6 files changed, 132 insertions(+), 2 deletions(-)
diff --git a/docs/maintainer/mbox.rst b/docs/maintainer/mbox.rst
index c2c5e4b..9289810 100644
--- a/docs/maintainer/mbox.rst
+++ b/docs/maintainer/mbox.rst
@@ -101,6 +101,18 @@ Option flags
later revision is also available and will add these results to the
retrieved thread.
+``-a, --all-revisions``
+ Check for both older and newer revisions of the selected patch series
+ and save the messages grouped by revision, oldest first. Messages
+ within each revision are ordered by date. For example::
+
+ b4 mbox -a -n review.mbox <msgid>
+
+ Revision discovery depends on the available public-inbox archive and
+ may not find revisions whose subject and author changed without a
+ matching ``change-id``. The older-revision search looks back up to
+ 12 months from the newest revision.
+
``-n WANTNAME, --mbox-name WANTNAME``
By default, the resulting mailbox file will use the message-id as the
basis for its filename. This option lets you override this behaviour.
diff --git a/src/b4/command.py b/src/b4/command.py
index c65a5e4..0ef4c6c 100644
--- a/src/b4/command.py
+++ b/src/b4/command.py
@@ -355,6 +355,12 @@ def setup_parser() -> argparse.ArgumentParser:
# b4 mbox
sp_mbox = subparsers.add_parser('mbox', help='Download a thread as an mbox file')
cmd_mbox_common_opts(sp_mbox)
+ sp_mbox.add_argument(
+ '-a',
+ '--all-revisions',
+ action='store_true',
+ help='Find older and newer revisions and save them in revision order',
+ )
sp_mbox.add_argument(
'-f',
'--filter-dupes',
diff --git a/src/b4/man/b4.1 b/src/b4/man/b4.1
index b93e42c..22ad531 100644
--- a/src/b4/man/b4.1
+++ b/src/b4/man/b4.1
@@ -113,6 +113,10 @@ folder.
Stitch multiple unthreaded messages into a single series and save as
an mbox file. Useful when a submitter sends patches without proper
threading.
+.TP
+.B \fBb4 mbox \-a \-n review.mbox [msgid]\fP
+Find older and newer revisions and save their discussion threads in
+revision order to review.mbox.
.UNINDENT
.SS b4 am
.sp
diff --git a/src/b4/man/b4.1.rst b/src/b4/man/b4.1.rst
index 429d3ad..ded7d6e 100644
--- a/src/b4/man/b4.1.rst
+++ b/src/b4/man/b4.1.rst
@@ -83,6 +83,10 @@ For options and their descriptions, see ``b4 mbox --help``.
an mbox file. Useful when a submitter sends patches without proper
threading.
+``b4 mbox -a -n review.mbox [msgid]``
+ Find older and newer revisions and save their discussion threads in
+ revision order to review.mbox.
+
b4 am
~~~~~
diff --git a/src/b4/mbox.py b/src/b4/mbox.py
index e3c1df2..05da1ed 100644
--- a/src/b4/mbox.py
+++ b/src/b4/mbox.py
@@ -1053,6 +1053,16 @@ def _run_shazam_merge(
sys.exit(0)
+def revision_sort_key(msg: EmailMessage) -> Tuple[int, int, int]:
+ lsub = b4.LoreSubject(msg.get('Subject', ''))
+ parsed_date = email.utils.parsedate_tz(msg.get('Date', ''))
+ if parsed_date is None:
+ timestamp = 0
+ else:
+ timestamp = email.utils.mktime_tz(parsed_date)
+ return lsub.revision, timestamp, lsub.counter
+
+
def main(cmdargs: argparse.Namespace) -> None:
# We force some settings
if cmdargs.subcmd == 'shazam':
@@ -1070,7 +1080,8 @@ def main(cmdargs: argparse.Namespace) -> None:
else:
cmdargs.mergebase = False
- if cmdargs.checknewer:
+ all_revisions = cmdargs.subcmd == 'mbox' and cmdargs.all_revisions
+ if cmdargs.checknewer or all_revisions:
# Force nocache mode
cmdargs.nocache = True
@@ -1086,9 +1097,29 @@ def main(cmdargs: argparse.Namespace) -> None:
if not msgs or not msgid:
sys.exit(1)
- if len(msgs) and cmdargs.checknewer and b4.can_network:
+ if (cmdargs.checknewer or all_revisions) and b4.can_network:
msgs = get_extra_series(msgs, direction=1, nocache=cmdargs.nocache)
+ if all_revisions:
+ if b4.can_network:
+ subjects = (b4.LoreSubject(msg.get('Subject', '')) for msg in msgs)
+ latest = max(
+ (
+ sub.revision
+ for sub in subjects
+ if not sub.reply and sub.counter <= 1
+ ),
+ default=1,
+ )
+ if latest > 1:
+ msgs = get_extra_series(
+ msgs,
+ direction=-1,
+ wantvers=list(range(1, latest)),
+ nocache=cmdargs.nocache,
+ )
+ msgs.sort(key=revision_sort_key)
+
if cmdargs.subcmd in ('am', 'shazam'):
make_am(msgs, cmdargs, msgid)
return
diff --git a/src/tests/test_mbox.py b/src/tests/test_mbox.py
index e1df42b..8401e22 100644
--- a/src/tests/test_mbox.py
+++ b/src/tests/test_mbox.py
@@ -1,7 +1,9 @@
import io
+import mailbox
import os
import sys
from email.message import EmailMessage
+from pathlib import Path
from typing import Any, Dict, List
from unittest.mock import patch as mock_patch
@@ -127,6 +129,77 @@ def _make_msg(
return msg
+def test_mbox_all_revisions_from_middle_version(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ monkeypatch.setattr(b4, 'can_network', True)
+ author = 'Author <author@example.com>'
+ v1 = _make_msg(
+ '[PATCH] foo: fix bar',
+ author,
+ 'Mon, 21 Sep 2026 10:00:00 +0000',
+ msgid='<v1@example.com>',
+ )
+ v1_reply = _make_msg(
+ 'Re: [PATCH] foo: fix bar',
+ author,
+ 'Mon, 21 Sep 2026 11:00:00 +0000',
+ msgid='<v1-reply@example.com>',
+ )
+ v2 = _make_msg(
+ '[PATCH v2] foo: fix bar',
+ author,
+ 'Tue, 22 Sep 2026 10:00:00 +0000',
+ msgid='<v2@example.com>',
+ )
+ v3 = _make_msg(
+ '[PATCH v3] foo: fix bar',
+ author,
+ 'Wed, 23 Sep 2026 10:00:00 +0000',
+ msgid='<v3@example.com>',
+ )
+ v3_reply = _make_msg(
+ 'Re: [PATCH v3] foo: fix bar',
+ author,
+ 'Wed, 23 Sep 2026 11:00:00 +0000',
+ msgid='<v3-reply@example.com>',
+ )
+
+ parser = b4.command.setup_parser()
+ cmdargs = parser.parse_args(
+ [
+ '--no-stdin',
+ 'mbox',
+ '-a',
+ '-o',
+ str(tmp_path),
+ '-n',
+ 'all.mbox',
+ 'v2@example.com',
+ ]
+ )
+ with (
+ mock_patch('b4.retrieve_messages', return_value=('v2@example.com', [v2])),
+ mock_patch(
+ 'b4.get_pi_search_results',
+ side_effect=[
+ [v3_reply, v3],
+ [v1_reply, v1],
+ ],
+ ),
+ ):
+ b4.mbox.main(cmdargs)
+
+ output = mailbox.mbox(tmp_path / 'all.mbox')
+ assert [msg['Message-Id'] for msg in output] == [
+ '<v1@example.com>',
+ '<v1-reply@example.com>',
+ '<v2@example.com>',
+ '<v3@example.com>',
+ '<v3-reply@example.com>',
+ ]
+
+
def test_get_extra_series_rejects_prerequisite_change_id() -> None:
"""Series listing a change-id as prerequisite must not be
treated as newer revisions of that series."""
--
2.55.0
next reply other threads:[~2026-09-28 7:03 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 7:02 Andrea Righi [this message]
2026-10-05 9:30 ` [PATCH] mbox: add option to save all series revisions Konstantin Ryabitsev
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260928070252.3678339-1-arighi@nvidia.com \
--to=arighi@nvidia.com \
--cc=konstantin@linuxfoundation.org \
--cc=tools@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox