[PATCH b4] Keep coverless review trailers on patches
Tamir Duberstein <[email protected]> Fri, 05 Jun 2026 08:31:04 -0400
| Newsgroups | org.kernel.linux.tools |
|---|---|
| Message-ID | <[email protected]> |
Trailer consolidation promoted matching per-patch trailers to the series record even when the series had no cover letter. For a single-patch series, this split the trailer and inline comments into two replies to the same message. Only promote trailers when there is a real cover letter, keeping a coverless patch review together in one reply. Assisted-by: Codex gpt-5.5 Signed-off-by: Tamir Duberstein <[email protected]> --- See incorrectly split replies: - https://lore.kernel.org/all/178066138611.3594.6559938011018564470.b4-review@b4/ - https://lore.kernel.org/all/178066138616.3594.4372925551839686675.b4-review@b4/ --- src/b4/review_tui/_review_app.py | 7 +++++- src/tests/test_tui_review.py | 52 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/b4/review_tui/_review_app.py b/src/b4/review_tui/_review_app.py index 8786fbb..38a7ba3 100644 --- a/src/b4/review_tui/_review_app.py +++ b/src/b4/review_tui/_review_app.py @@ -1181,7 +1181,12 @@ class ReviewApp(CheckRunnerMixin, App[None]): preview.pop('trailers', None) b4.review._cleanup_review(patch, self._usercfg) self._refresh_patch_item(pidx + 1) - elif self._selected_idx > 0 and new_trailers and self._patches: + elif ( + self._has_cover + and self._selected_idx > 0 + and new_trailers + and self._patches + ): # Check if all patches now have the same trailer set — # if so, promote to cover letter new_names = {t.split(':', 1)[0].strip().lower() for t in new_trailers} diff --git a/src/tests/test_tui_review.py b/src/tests/test_tui_review.py index fb6fae0..ab6f4dd 100644 --- a/src/tests/test_tui_review.py +++ b/src/tests/test_tui_review.py @@ -10,6 +10,7 @@ cosmetic commit edits (e.g. reworded subjects via git rebase -i). """ from typing import Any, Dict, List, Tuple +from unittest import mock import pytest @@ -179,6 +180,57 @@ def _rewrite_patches( # --------------------------------------------------------------------------- +class TestTrailerConsolidation: + """Tests for consolidating matching trailers onto a cover letter.""" + + @pytest.mark.asyncio + async def test_coverless_patch_keeps_trailer_with_comments( + self, gitdir: str + ) -> None: + """A coverless patch sends its trailer and comments in one email.""" + branch, _patch_shas = _create_review_branch_with_patches( + gitdir, 'coverless-trailer', ['patch 1'] + ) + session = _build_session(gitdir, branch) + session['cover_text'] = ( + 'patch 1\n\nNOTE: No cover letter provided by the author.' + ) + my_email = str(session['usercfg']['email']) + session['patches'][0]['reviews'] = { + my_email: { + 'name': str(session['usercfg']['name']), + 'comments': [{'path': 'file', 'line': 1, 'text': 'Comment'}], + } + } + + app = ReviewApp(session) + async with app.run_test(size=(120, 30)) as pilot: + await pilot.pause() + await pilot.press('t') + await pilot.pause() + await pilot.press('r') + await pilot.press('q') + await pilot.pause() + + assert 'reviews' not in app._series + patch_review = app._patches[0]['reviews'][my_email] + assert patch_review['trailers'] == [f'Reviewed-by: {app._default_identity}'] + assert patch_review['comments'] + + with mock.patch( + 'b4.review._review._build_review_email', + return_value=mock.sentinel.email, + ): + msgs = b4.review.collect_review_emails( + app._series, + app._patches, + app._cover_text, + app._topdir, + app._commit_shas, + ) + assert msgs == [mock.sentinel.email] + + class TestReconcileAfterShell: """Tests for _reconcile_after_shell tracking fixup.""" --- base-commit: d5d981426ead3f490713ef5d2fd1aa3d0f13b005 change-id: 20260605-review-double-review-email-ba1b09cca823 Best regards, -- Tamir Duberstein <[email protected]>