Among the 1 debian patch available in version 0.15.2-2 of the package, we noticed the following issues:
commit 94faf00be1843629295b47091e7c33f8d1a324dc
Author: Héctor Orón Martínez <zumbi@debian.org>
Date: Thu Aug 20 17:29:18 2026 +0200
d/changelog: prepare next upstream release
Signed-off-by: Héctor Orón Martínez <zumbi@debian.org>
commit 9db2e85e62c45fefd3fb3bd418f571b4fc92af11
Merge: ec8f20a 218d6c0
Author: Héctor Orón Martínez <zumbi@debian.org>
Date: Thu Aug 20 17:28:19 2026 +0200
Merge branch 'upstream/0.16.x' into debian/unstable
commit 218d6c0eb9f8192a2b59b9add275d09ad47986ba
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Aug 13 21:07:21 2026 +0000
Raise BadCharsError from make_fake_am_range() too
The unicode control-character guard in get_am_message() raises
BadCharsError instead of exiting, but make_fake_am_range() called it
with no allowbadchars plumbing and no exception handling at all. Any
series that needed the fake-am 3-way fallback (mismatched blobs) and
also carried the same Cf characters crashed the review TUI a second
time, immediately after the reviewer had already cleared the first
guard -- exactly what Mark Brown hit on his follow-up report.
Thread allowbadchars through make_fake_am_range() to the inner
get_am_message() call, and give the review TUI's three call sites
(new-series checkout, revision upgrade, range-diff fake-am) a
confirm-and-retry loop against a shared terminal prompt, since all
three run with the TUI suspended. b4 am's threeway path picks up
cmdargs.allowbadchars for free; b4 diff has no such flag and stays
protected by the CLI dispatcher's existing BadCharsError handler.
Key changes:
- LoreSeries.make_fake_am_range() takes allowbadchars, forwarded to
get_am_message()
- mbox.py's cmd_am threeway path forwards cmdargs.allowbadchars
- _confirm_badchars_tty() moved to the shared b4.tui._common so both
review_tui._tracking_app and review_tui._common can reuse it
- _do_checkout(), the revision-upgrade apply step, and
fetch_fake_am_range() retry once the reviewer confirms past the
guard on the terminal
- regression tests in test_cwd_safety.py building a real fake-am range
over a body containing a ZERO WIDTH NON-JOINER
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit 9a0f4a9564089f974ecfbe750190a3db31eb50c5)
commit fa35f33a4d42688d2a7a2646733454bc191c0018
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Aug 13 17:48:44 2026 +0000
review: let the reviewer decide about unicode control characters
Reporting the control-character finding without a crash still left the
reviewer stuck: b4 review has no --allow-unicode-control-chars, so a
series carrying such characters could not be reviewed at all.
Add a BadCharsScreen modal that shows the offending line with a caret
under the character, plus what the character is, and offers to proceed
anyway. Accepting is remembered per message-id for the rest of the
session, so the same series is not queried again on every step of the
workflow. Cancel is the default: Enter and Escape both decline, and
only an explicit 'y' proceeds.
The take flow runs with the TUI suspended, so it asks on the terminal
instead. TargetBranchScreen prepares patches only for its applicability
preview, so it inherits an acceptance already given rather than asking
again on its own.
Key changes:
- BadCharsScreen modal and _confirm_badchars() retry helper
- per-session, per-message-id acceptance in TrackingApp._badchars_ok
- checkout and update-revision workers return the guard error rather
than raising, so their callbacks can offer the choice
- _confirm_badchars_tty() for the suspended take flow
- tests for the modal, the cancel default, retry-and-remember, and the
worker handing the error back instead of letting it escape
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit f5a9ba47fc1453f1d20f3cd0a23bbd719bcc9a5e)
commit 48ad66c78e7ecf3dac57262b62eb1025da72d17d
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Aug 13 17:37:41 2026 +0000
Raise BadCharsError instead of exiting from get_am_message()
The unicode control-character guard in LoreMessage.get_am_message()
called sys.exit(1) directly. That works for the command-line tools, but
b4 review runs the same code inside a Textual thread worker, where
SystemExit is a BaseException that sails past the worker's error
handling and tears down the whole TUI with a traceback. Mark Brown hit
this on a series whose changelog contains U+200C.
Turn the finding into a BadCharsError exception carrying the subject,
line, offset and character, and let callers decide how to present it.
Command-line behaviour is unchanged: b4's top-level dispatcher catches
the error, prints the same block and exits 1. It now also names
--allow-unicode-control-chars instead of vaguely pointing at "the right
flag", for subcommands that have one.
Key changes:
- b4.BadCharsError with a details() helper for the display block
- catch at the command.py dispatcher for all command-line paths
- review TUI: report through the existing worker error paths rather than
crashing; the take flow prints the block and waits for Enter
- tests covering the raise, --allow-unicode-control-chars, and the
non-latin case that must not be flagged
Reported-by: Mark Brown <broonie@kernel.org>
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit ed72ed36f92c7eca19d5a3beeb4042aeb5e3109b)
commit 70db55c27aa002e561c2a3542c2c16ec292ba45a
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 18:49:05 2026 +0000
docs: smtp-timeout ships in 0.16.1, not 0.17
The b4.smtp-timeout knob was documented as arriving in v0.17, but the
commit introducing it has been backported to the stable branch and will
be part of the 0.16.1 release. Correct the versionadded marker.
Assisted-by: LLM [codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit d57d184c92eaf0fd58d937dc7ad5e2bbb87dd405
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 18:42:00 2026 +0000
distro-matrix: full-upgrade the arch lane before installing
The arch recipe ran pacman -Sy, which refreshes the package database and
installs the newest packages without upgrading the rest of the system --
the classic Arch partial-upgrade footgun. Because the archlinux:latest
image is a point-in-time snapshot, the lane installed a python built
against a newer glibc than the image ships, and the interpreter died at
import time with "GLIBC_2.44 not found".
Use pacman -Syu so the base system is brought up to date along with the
packages we install.
Assisted-by: LLM [analysis, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit d1e611a525487f7c1f18d5ae826a9b9271e68f95)
commit 1cd4dd3c1f17262927670b695466e2ccf7b1d28f
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 17:57:38 2026 +0000
bugs: route TUI lore fetches through the chokepoint
The bugs TUI predates the lore-fetch chokepoint and still launched its
three fetch workers bare, leaving it exposed to the whole class of
sticky-cancel-flag bugs the chokepoint exists to prevent:
- the reply fetch ran with the default exit_on_error=True, so a fetch
failure tore down the entire TUI via WorkerFailed instead of showing
a notification;
- none of the three sites (thread import, reply fetch, update-all
refresh) cleared the sticky cancel flag first, so a cancel left
behind by a sibling app's shutdown made them fail instantly with
OperationCancelledError;
- BugListApp lacked LoreNodeShutdownMixin, so quitting while a worker
was blocked in a fetch stalled the interpreter at shutdown until the
request returned on its own.
Convert all three sites to run_lore_worker(), add the mixin to the
app, and give BugDetailScreen a worker-state handler so a failed reply
fetch now surfaces as an error toast.
Key changes:
- ImportScreen, BugDetailScreen.action_reply, UpdateBugsScreen.on_mount
all launch via run_lore_worker()
- BugListApp gains LoreNodeShutdownMixin
- new BugDetailScreen.on_worker_state_changed notifies on reply_fetch
failure
- tests assert the chokepoint contract (reset-before-launch ordering,
thread=True, exit_on_error=False) for each site, the mixin on the
app, and the new error toast
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit e2cd00bfe21d0b8b55a5f2b2e2f17438a0e4c442)
commit 56409274c4821bcee6f1c611e95910d6b68898b2
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 17:50:05 2026 +0000
tui: promote the lore-fetch chokepoint to b4.tui
Move lore_request(), run_lore_worker(), LoreNodeShutdownMixin and the
_WorkerHost protocol from b4.review_tui._common to b4.tui._common, the
canonical home for utilities shared across all b4 Textual apps. The
review TUI keeps working via the usual explicit re-exports, and the
three public names are added to the b4.tui lazy-loading surface.
The chokepoint helpers guard against the shared lore node's sticky
cancel flag, but so far only the review-side apps route through them.
The bugs TUI has the same class of fetch sites and needs the same
protection; giving the helpers a home outside review_tui lets it (and
any future TUI) import them without reaching into a sibling package.
Pure code motion, no behaviour change.
Assisted-by: LLM [analysis, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit 5b2be23795277ad0ac5eb2d82f49b5d98c69a74a)
commit dbe103b7ecb0444d0dbae017df3ee14d7be94af6
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 16:45:26 2026 +0000
tui: deduplicate the limit-pattern matching engine
The three list TUIs (review tracking, patchwork, bugs) each carried
their own near-identical _matches_limit: lowercase the pattern, split
on whitespace, AND all tokens, dispatch prefixed tokens to a field and
match bare tokens against a default set of fields. Only the field
wiring differed.
Move the engine to b4.tui._common as matches_limit(), parameterized by
a prefix-to-matcher mapping and a bare-token matcher, alongside a
limit_substring_matcher() factory for the common case-insensitive
any-of-dict-fields match. Each app's _matches_limit staticmethod
survives as its field wiring only: tracking keeps s:/t:/up: (including
the up: boolean semantics), patchwork keeps s:/d:, and bugs keeps its
enum-status s: and any-label l: matchers. Future limit prefixes now
only need a matcher, not another copy of the tokenizer.
The engine gets its own tests in test_tui_common.py; the existing
per-app tests pass unchanged, pinning that the rewiring preserves
behavior.
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit df7fe589932165eeb6c8024107c00e9297aae774)
commit 82a87863574b670fbae09f583c595238ba4825a7
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 16:29:47 2026 +0000
tests: strengthen assertions too weak to catch real breakage
Final step of the test-suite cleanup: tighten tests whose assertions
could keep passing after the behavior they guard breaks.
Key changes:
- test_track_generates_change_id_without_change_id: the mocks make the
synthesized change-id fully deterministic, so assert exact equality
instead of startswith/substring checks that tolerate a broken slug or
fingerprint segment.
- TestBuildReplyFromComments truncation tests: assert the exact reply
text instead of substring presence, so broken quoting prefixes,
truncation boundaries, or comment placement can no longer pass.
- TestLinkRevisionWrapper: the not-found result now compares the whole
result dict, covering the revision/absorbed/promoted fields.
- TestFetchSeriesForLink: add the two missing error paths — a fetch
that raises and a fetch returning non-patch messages both yield None.
- test_detail_panel_lifecycle: assert the rendered panel content
(#detail-subject, #detail-from) for both the initial highlight and
after navigation, not just internal selection state.
- TestRecordDiscoveredCoverSubject: merge the heal and manual-link
tests into one parametrized test with identical setup differing only
in source provenance, proving the heuristic/manual distinction.
Assisted-by: LLM [analysis, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit a0725e8269439ac4651f89350b471bd3c7485ba8)
commit 9dbd2094d2ec2732676671d9cafcfb7a59cfaf5e
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 16:05:29 2026 +0000
review-tui: tolerate missing Footer during list refresh
_refresh_list mounted the rebuilt list widgets with
before=self.query_one(Footer). Under load, a queued refresh callback
can run while the Footer is not yet (or no longer) mounted mid screen
transition, and query_one then raises NoMatches, crashing the refresh.
This was the root cause of the long-standing intermittent failures in
the TestTargetBranch tests during full-suite runs.
Resolve the Footer once at the top of the refresh and fall back to
None when it is absent. The Footer is docked to the bottom edge, so
it sits outside normal layout flow and DOM order does not affect
rendering; appending the widgets produces the same visual result as
inserting before it. Falling back is preferable to bailing out early
because _check_db_changed records the database mtime before scheduling
the refresh, so an aborted refresh would never be retried and could
leave the list permanently unbuilt.
Add a regression test that removes the Footer and drives a refresh
directly, verifying the list still rebuilds.
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit 8acfd8def73f3fda7a55f3a605582eb0eeed97d9)
commit b963853d339e4a2a305ae0c28c55f64f59f102cf
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 15:53:19 2026 +0000
tests: consolidate TUI pilot sessions and parametrize modal key tests
Each Textual run_test() session costs over a second of wall time, so
merge tests that exercise sequential keypresses on identically-seeded
apps into shared sessions: the four limit/filter tests become one
flow, the three detail-panel tests become one lifecycle test, and the
quit and startup-listing pairs each become a single test.
Convert the pure-function _matches_limit and _effective_tier tests
into parametrized input/expected tables with descriptive case ids,
and parametrize the mechanical key-to-result modal tests (HelpScreen,
NoteScreen, RevisionChoiceScreen, ActionScreen shortcuts,
LinkRevisionConfirmScreen, TakeScreen default-method matrix).
Key changes:
- test_tui_tracking.py: 12 pilot sessions merged into 5
- test_tui_modals.py: 18 key/result tests folded into 6 tables
- No behavioral coverage lost; per-case failure output improves
Assisted-by: LLM [analysis, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit c740a9e54616f44dab9c1cec55e342d6f3124779)
commit 593d00045daf79920f54991caafa567fb8ef0099
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 15:37:16 2026 +0000
tests: parametrize and merge trivial tests in test_review_tracking.py
Merge the three nonexistent-db one-assert tests into a single
test_queries_against_nonexistent_db, and the empty-db revision
queries into a single test_revision_queries_empty_db, which also
gains empty-db coverage for get_all_revision_counts.
Parametrize TestFormatSnoozeUntil and TestFormatAttestation into
input/expected tables. The countdown cases now assert with
startswith(), which is stronger than the previous substring and
split-based checks. Fold the two cmd_enroll bad-path tests into one
parametrized test.
Assisted-by: LLM [analysis, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit a167324078763d941a802ecf44045b68b870a68b)
commit 9361e7a006417bf454aa2b9346bd25b36d403880
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 15:28:47 2026 +0000
tests: consolidate _build_review_email tests in test_review.py
Merge the three TestBuildReviewEmail* classes (Headers, Bcc,
ToCcEdited) into a single TestBuildReviewEmail class. An autouse
fixture supplies the reviewer identity and signature mocks, replacing
42 stacked @mock.patch decorators, and _make_series()/_make_review()
helpers replace the repeated series/review dict literals.
Parametrize the Bcc header and subject Re:-prefix cases, and drop
test_from_is_series_author_when_no_reply_to, which duplicated
test_default_to_is_author. No behavioral coverage is lost; all
remaining assertions are unchanged or strengthened.
Assisted-by: LLM [analysis, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit ba3a9dd7a9637b1b22ba07e27a1827a78b579dd9)
commit 3ed62a33e6422348df5e2369c02fb9e4bb117edb
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 15:17:05 2026 +0000
tests: drop assertions that cannot catch real bugs
A comprehensive audit of the test suite found a small number of tests
whose assertions cannot fail on a real regression: checks that a
module-level constant has a particular value, that Textual renders
text passed to a widget, that private caches and dict keys exist
after startup, or that a method was never called on a mock. These
break on harmless refactors while catching nothing, and four of them
each spent a full TUI app boot to do so.
Key changes:
- test_review_tracking.py: drop the two SCHEMA_VERSION >= N constant
asserts and the TestReviewTargetBranch DEFAULT_CONFIG literal check
- test_review_checks.py: drop TestStatusOrder (_STATUS_ORDER constant
ordering)
- test_tui_modals.py: drop test_content_rendered and
test_body_lines_rendered (framework rendering tautologies)
- test_tui_tracking.py: drop title-text, cache-populated,
revisions-stashed and snoozed_until dict-key checks; the behavior
they support is covered by the invalidation and workflow tests
- test___init__.py: drop test_does_not_inject_session
(assert_not_called on a mock is an implementation detail)
Tests flagged by the audit but deliberately kept: test_note_only
(documents that private notes never flip patch state), the
attestation fallback tests (real code branches), and
test_caches_survive_db_poll_no_change (only guard against cache
rebuilds on every poll).
Assisted-by: LLM [analysis, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit ea1eb425f2be06f2d96b134ce1edd20a8b2ebb21)
commit f781bc9b3aea0e38d0a9dd5856c536ebda1c381e
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 12 14:59:24 2026 +0000
deps: bump liblore to 0.8.2
Refresh the lock file to pull in liblore 0.8.2 and move the liblore
submodule pointer to the v0.8.2 tag. The pyproject.toml floor stays at
liblore>=0.8 since nothing here requires the newer release.
Assisted-by: LLM [dep bump, ci verification]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit f8c10bb542e03d430b3f36afdf5124a16df1b2e8)
commit 39bc1176577cf63fa22b26a0f93ee7479431d0fd
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Tue Aug 11 22:12:23 2026 +0000
send: sign messages before opening the SMTP connection
get_smtp() used to dial out and authenticate before returning, so the
socket was already open by the time send_mail() started signing. Signing
is the one step that can block indefinitely: for a PGP key patatt shells
out to gpg, which may be sitting on a pinentry passphrase prompt the user
cannot see, for instance when GPG_TTY points at a different terminal.
The result was a send that appeared to hang on the network, because
"Connecting to <server>:<port>" was the last thing printed before the
process went quiet. That sends anyone debugging it straight to the wrong
layer, and it holds an idle connection open for however long gpg takes.
Defer the connection until every message is signed and ready to go out,
and announce the signing step so a stall is attributed correctly.
While here, give the connection a timeout. Python's smtplib blocks
forever by default, so an unresponsive server produced the same silent
hang. git-send-email never asks for a timeout either, but Net::SMTP
applies 120 seconds when it doesn't, so match that.
Key changes:
- Add SMTPConnector, a deferred connection that get_smtp() returns in
place of a live socket. It connects at most once and reuses the
session, so callers that send one message at a time in a loop still
get a single connection.
- Move the "Connecting to" message inside the connector, so it prints
when a connection is actually being made.
- Log the patatt signing step in send_mail() before the signing loop.
- Wrap connection failures in RuntimeError so callers keep reporting
them as a broken smtp setup rather than leaking a traceback, and
guard the previously unwrapped send_mail() call in b4 ty.
- Add b4.smtp-timeout, defaulting to 120 seconds. Set it to 0 to keep
waiting forever, which omits the argument and restores the old
behaviour. The value is validated in get_smtp(), alongside the port,
so a typo is reported before anything gets signed.
Assisted-by: LLM [diagnosis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit 95544ba72b9c10db654381dd4dfc1982e2abddf9)
commit b53ed276a02d90afc4d5309c5ba2871bd235794d
Author: Mark Brown <broonie@kernel.org>
Date: Fri Aug 7 19:02:09 2026 +0100
review-tui: Provide progress indications when upgrading a series
Upgrading a series, especially a large series, can sometimes be a slow
operation. It can take a while to figure out the base commit to use, and
network operations can be slow. At the moment we just have a "Fetching new
revision" interstitial which can be displayed for many tens of seconds with
no indication as to what it's actually doing.
Add progress messages to the interstitial indicating what is currently
happening, helping reassure users that progress is being made and providing
hints about the source of any problems that are seen.
Signed-off-by: Mark Brown <broonie@kernel.org>
Link: https://patch.msgid.link/20260807180213.599509-1-broonie@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit cde64701deebd7a60af31ce44d322ce402937bdd)
commit 298c8a8fbed567b3a698dabcf3518e5f66ab2256
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Tue Aug 11 20:43:27 2026 +0000
trailers: don't flag suggested-but-already-present trailers as mismatched
A follow-up message can end up restating a trailer that a patch
already carries -- for example when a reviewer quotes it for context,
or repeats it while suggesting a different trailer order. b4 was
treating any such restatement as the replier newly asserting that
trailer themselves, and since the replier's own address rarely matches
a Suggested-by/Reviewed-by/Acked-by/Signed-off-by that belongs to
someone else, this surfaced as a spurious "from/email mismatch"
warning even though nothing was actually wrong with the patch.
Reported by Uwe Kleine-König against a real thread where Miguel Ojeda
suggested reordering Francis Laniel's existing trailers; the same
trailers were also carried over across revisions via the codereview
patch-id map, so both mismatch-recording sites needed the same check.
A candidate trailer is now checked against the patch's own commit
message trailers (via get_body_parts) before being recorded as a
mismatch: if it's already there, restating it isn't new information
from the follow-up message.
Reported-by: Uwe Kleine-König <ukleinek@kernel.org>
Link: https://lore.kernel.org/r/anqsTVj5QGYGiEQ9@monoceros
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit 2dcc5278601b355300bf593297b3dfe3a717c648)
commit cabdc9c3c1e26b966ebbaba7d7c9d69eaa47821d
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Tue Aug 11 20:00:07 2026 +0000
review: warn when a tracked thread is incomplete
The patch count reported after tracking comes from the series' own
declaration (the n in [PATCH 0/n]), not from what was actually imported.
A series that lost patches on the way in therefore printed the same
reassuring line as one that came in whole, which is how a cover-only
import went unnoticed.
Warn when the series is incomplete, naming the patch numbers that did not
make it, so a partial import is visibly different from a successful one.
While here, derive the mock series' completeness in the cmd_track tests
from its patch list instead of leaving it a truthy Mock.
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit fd876859303b9b24fb57dd19383ee3dba9f3e6c8)
commit 8e5de5af8c7694765afefcd9e9d618f06f68b231
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Tue Aug 11 20:03:02 2026 +0000
submodule: update liblore to 2221455 (0.8.1+)
Picks up the thread-walking fix for messages whose In-Reply-To/References
headers point at their own Message-ID. Such a self-reference was treated
as a parent, so breaking the thread at a self-referencing cover letter
seeded the ignore set with the cover's own Message-ID and discarded every
patch replying to it -- a series imported as the cover alone.
The PyPI requirement stays at >=0.8 and the hashed requirements still
resolve liblore 0.8.1, so installed builds only pick this up once liblore
cuts a release and the lockfile is refreshed.
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit 7090130020c530f10eab213b665da9a84b49eb71)
commit 04c46697c2de2857afe274b0d09935afa3ddc99f
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 5 17:26:11 2026 +0000
Bump version to 0.16.1-dev
Assisted-by: LLM [codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit e9e45ea03e9531c20da742ccb653757219c05c37
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 5 16:51:48 2026 +0000
pyproject: adopt PEP 639 license metadata
uv build warns that both the license TOML table and the license
classifier are deprecated by setuptools, with removal planned for
2027. Switch to an SPDX license expression with license-files, drop
the redundant classifier, and require setuptools>=77, where PEP 639
support first landed.
The built wheel now carries License-Expression: GPL-2.0-or-later and
bundles COPYING under dist-info/licenses/.
Assisted-by: LLM [codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
(cherry picked from commit 362b87aa4d036884c36e4bfb9cdbdee626aba3bf)
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 3e1186817a49eab59bb633e3bbc8e3e386db8e63
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 5 16:36:05 2026 +0000
Bump version to 0.16.0
Promote the unreleased changelog section to v0.16, update the man
page header to 0.16 and regenerate it with rst2man, and record the
new version in the lockfile.
Assisted-by: LLM [codegen, docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 0ea98adf07b2880691bcef6585e15d6deaa97a5c
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 5 16:35:55 2026 +0000
Fix new ruff and ty findings from the tool refresh
The dependency refresh brought in ruff 0.16 and ty 0.0.66, both of
which flag things their predecessors did not.
Ruff 0.16 expanded its default rule set, which our extend-select
inherited wholesale (~3500 new findings). Switch to an explicit
select list pinning the previous defaults (E4, E7, E9, F) plus our
own additions, so upstream default churn can no longer change what
b4 enforces.
Fix the two legitimate ty findings: get_config_from_git now narrows
the multival entry to a list before appending instead of calling
.append() on a str|list union, and b4 send narrows send-series-to/cc
config values to str before adding them to the recipient sets (a
list-valued config would previously have crashed with an unhashable
type). The remaining nine findings are the long-standing monkeypatch
assignments in test_tui_tracking.py, which get ty ignores paired
with their existing mypy ignores.
Assisted-by: LLM [triage, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 642f6d786f3770295bc46ed768b0789241a88cd3
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Aug 5 15:37:21 2026 +0000
deps: refresh lockfile and hashed requirements for 0.16
Run uv lock --upgrade and regenerate requirements.txt and
requirements-tui.txt with uv pip compile --upgrade --generate-hashes
in preparation for the 0.16 release. Notable runtime bumps: requests
2.34.2, urllib3 2.7.0, pygit2 1.19.3, rich 15.0.0 (major), textual
8.2.8, tree-sitter 0.26.0. Dev tooling moves to ruff 0.16.1, ty
0.0.66, mypy 2.3.0, pyright 1.1.411.
Verified with ci-matrix.sh: all interpreter lanes (3.11-3.14) and the
floor lane pass 1452 tests.
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 100ca517a451ba3bd4e1c4a01d4dd6a4e63c0721
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Tue Aug 4 19:44:34 2026 +0000
tests: skip TUI wrapper tests when textual is unavailable
TestMarkOutgoingSeenHelper imports b4.review_tui._common, which pulls
in rich/textual, but test_messages.py has no TUI guard the way the
test_tui_* modules do. On no-TUI environments (e.g. the AlmaLinux
distro-matrix lane, which packages no textual) the three helper tests
fail with ModuleNotFoundError instead of skipping.
Guard just that class with an autouse importorskip fixture so the rest
of test_messages.py keeps running on no-TUI lanes. Verified: alma10
lane now reports 754 passed, 71 skipped.
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 5ef6ceda8218833e555932fe0d9d9142341a0802
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Tue Aug 4 18:49:45 2026 +0000
plan: add review-agent methodology hook to 0.17
Record Juri Lelli's RFC for separating the maintainer-owned analysis
methodology from the b4-owned output-format contract in the review
agent prompt.
Assisted-by: LLM [analysis, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 69f3df3954f387b408bab999d36452cafcc5120d
Author: Breno Leitao <leitao@debian.org>
Date: Tue Aug 4 05:24:58 2026 -0700
prep: add --cleanup-older-than option to clean up stale branches
B4 is so useful that I have too many branches now, create a way to
easily remove old branches.
Add a new --cleanup-older-than DAYS option that archives and removes
prep-tracked branches whose latest commit is older than the specified
number of days. Each matching branch goes through the normal per-branch
confirmation prompt.
Signed-off-by: Breno Leitao <leitao@debian.org>
Link: https://patch.msgid.link/20260804-cleanup-v2-1-8c8df5d66e8f@debian.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 62c5a3c2f841dd6533a9009e6ab4fee104bf3b04
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Mon Aug 3 21:18:33 2026 +0000
review-tui: hide the D binding when no other revisions are known
The range-diff binding was always shown in the review screen footer,
even for series where the tracking database knows only the revision
being reviewed. Pressing D in that situation could only produce the
"No other known revisions of this series" notification, so showing
the binding was just noise.
Query the tracking database once at app construction and gate the
binding on the result via check_action(), the same way the P (prior
review) binding is gated on prior-review-context. This also makes D
correctly disappear in email preview mode, where it previously
lingered even though it is a review-mode action.
Link: https://lore.kernel.org/alpine.LSU.2.21.2605041306360.26178@pobox.suse.cz # 0de7703
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit eb829375ad1105dbebbed98113df209bd92dc5ef
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Mon Aug 3 20:57:47 2026 +0000
review-tui: clear the stale lore cancel flag in fetch_fake_am_range
The shared lore node's cancel flag is sticky, so a range-diff that
needs to fetch a revision from lore inherits the cancel left behind by
a previously aborted operation and crashes the TUI with
OperationCancelledError. Wrap the fetch in lore_request(), the
sanctioned way to begin a lore fetch. This was a latent bug in the
tracking app's range-diff as well, before the machinery was moved to
_common.py.
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 2905dac4392eee3cd5fb50b85b8a78bde187a37f
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Mon Aug 3 20:45:26 2026 +0000
review-tui: add per-patch range-diff to the review screen
Reviewers looking at a single patch had to quit back to the tracking
screen (or use a second terminal) to see how that patch changed from a
previous revision. Add a D binding to the review screen that prompts
for a revision, computes the range-diff, and pages only the block for
the currently selected patch. Selecting the cover letter shows the
full-series range-diff, and if the current patch cannot be matched in
the output, the full range-diff is shown instead of nothing.
To make the existing machinery reusable, move the tracking app's
fake-am reconstruction and range-diff computation into shared
functions in review_tui/_common.py. The block filter matches the
current commit on either side of the pair, since comparing against a
newer revision puts the current revision on the left.
Suggested-by: Miroslav Benes <mbenes@suse.cz>
Link: https://lore.kernel.org/alpine.LSU.2.21.2605041306360.26178@pobox.suse.cz # 0de7703
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit b46ff38a110c3f830798a969a966b65fbddada62
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Mon Aug 3 19:23:37 2026 +0000
tests: cover shazam -M with stdin at EOF
Reproduce the reported crash: run the shazam merge path without
--no-interactive while sys.stdin is an exhausted non-tty stream, and
verify the merge completes with the expected history instead of dying
with EOFError at the confirmation prompt.
Link: https://lore.kernel.org/tools/2aczixw5erzdkkbnfflkzzyu6pkumodss7uqisvj2lldllsd2x@rd7shjtvz5me/ # bdb9c9b
Assisted-by: LLM [tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit a68abeaee97923d8b6c6a073027d3fb53f0f57fb
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Mon Aug 3 19:23:37 2026 +0000
shazam: fall back to non-interactive merge when stdin is not a tty
When the mbox arrives on stdin (b4 shazam -m-), stdin is at EOF by the
time the -M merge confirmation prompt runs, and input() dies with an
unhandled EOFError traceback. This is a common setup when b4 is invoked
from a mail client pipe (e.g. neomutt piping through notmuch).
Detect a non-tty stdin in the merge path and proceed as if
--no-interactive was given: skip the confirmation prompt and use
--no-edit, since the editor cannot work without a terminal either.
Also treat EOFError at the prompt itself (Ctrl-D from a real terminal)
as an abort, same as Ctrl-C.
Reported-by: Matthias Beyer <mail@beyermatthias.de>
Fixes: https://lore.kernel.org/tools/2aczixw5erzdkkbnfflkzzyu6pkumodss7uqisvj2lldllsd2x@rd7shjtvz5me/ # bdb9c9b
Assisted-by: LLM [rca, codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit e2b462ac574ef330caf4aac7dd566c50fb1f9d1f
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Mon Aug 3 19:10:36 2026 +0000
plan: add Chuck Lever's "file" cover strategy to the 0.17 roadmap
Record the proposed "file" cover letter strategy for b4 prep in the
0.17 section of plan.otl. The patch stores cover, changelog, and
recipients as separate plain text files under .git/b4-prep/<change-id>/
and needs review plus a rebase against current master.
Link: https://msgid.link/20260324152805.88424-1-cel@kernel.org
Assisted-by: LLM [triage, plan entry]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 6c033b2c47928ae51eb1e6c8532d1659be456410
Merge: 3cf7167 150e1cd
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Mon Aug 3 14:40:24 2026 +0000
Merge patch series "Stop the editor branch guard from eating review replies"
Christian Brauner <brauner@kernel.org> says:
Stop the editor branch guard from eating review replies
Reported from a live "b4 review tui" session: a reply editor left
open across a branch switch dumped the reply into /tmp and took the
TUI down with a RuntimeError. Digging into that turned up four editor
problems, one patch each:
- The branch guard in edit_in_editor() exists for b4 prep, which
writes to the branch HEAD is on, but it fired for callers that
write to an explicit ref. It is opt-in now.
- edit_in_editor() worked in the process cwd rather than the tree the
edit belongs to. Callers name the tree now, "b4 ty -g" included.
- An editor exception inside app.suspend() tore the whole session
down. The call sites share one non-fatal helper.
- The TUI checked its start branch back out even when the user was
the one who moved HEAD. Both restore paths only undo b4's own
checkout.
The patches before those are follow-up fixes to last round's review
tracker work: outgoing mail is marked read on every send path, the
review databases get consistent closing and timeouts, archiving
reports failures instead of raising into the send path, post-send
bookkeeping is kept out of the send error handlers, --email-dry-run
no longer records sends that never happened, a take whose record
could not be written is told apart from one that never ran, and the
queued-thanks publish check runs in the repository the commit landed
in and warns when the remote's tips are entirely absent from it.
The last group puts the worktree back wherever the TUI moved it.
Restores record a position rather than a branch name so a detached
HEAD works too, the tracking loop restores however it ends,
create_review_branch() cleans up on every failure, the SystemExit
that b4 helpers exit with is caught where only Exception was, and
deleting the branch HEAD is on lands the user back where the session
started.
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-0-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 150e1cd2b89de45ee6ac19b39b188e9e1b226657
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:25 2026 +0200
tests: cover the branch delete leaving the worktree on a branch
Abandoning and archiving both get a case. A session that started
detached gets its own commit back, and deleting a branch HEAD was
never on moves nothing.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-44-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 9b4b96f9a4277de1c81285178eb61934d02aadb0
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:24 2026 +0200
review-tui: put the user back when the branch they are on is deleted
Deleting the branch HEAD sits on detaches to its parent commit, the
only destination delete_review_branch() can name without knowing the
caller. The exit restore leaves a detached HEAD alone by design, so
abandoning or archiving a series parked the user on a commit they
never asked for.
We know where the session started. Put the user back there, at the
two places that ask for the detaching.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-43-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 884061a3a5b7f43c803a1604e94f9311d925e85f
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:23 2026 +0200
tests: cover a failed checkout leaving the tracking list standing
Both routes into create_review_branch() report a failure and leave
the app standing. Backing out of the conflict shell must still create
nothing.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-42-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 5ca28d0a0716ee4758247efb18cbc19b25c9eb0f
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:22 2026 +0200
review-tui: catch the exit a failed checkout reports itself with
create_review_branch() exits rather than raises, and SystemExit is
not an Exception, so a failed checkout unwound past the handler and
took the session down. This is the path every series enters review
through. Catch the exit and report "see above"; the reason is already
on the screen we are suspended into.
The conflict handler also created the branch a second time from
inside the except block, out of reach of the handler beside it. Move
the resolve around the am alone; the create is then written once and
covered.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-41-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 6d45cb9e2e50d9813d5f790573b6a2d960996835
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:21 2026 +0200
tests: cover the status sync closing its database
A write that raises must leave the connection closed anyway.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-40-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 4ae34e00412023396e0b8bca3a24f8a9a776a388
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:20 2026 +0200
review-tui: close the tracking database when the status sync fails
The same leak as the auto-mark block and the archive: the close sits
in the last statement of the try. This one runs once per reviewed
series in a process that stays around. Close it in a finally.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-39-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit c94d19a70d00a17601cefe3e1ce8e4bc83af68ce
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:19 2026 +0200
tests: cover the tracking TUI restoring the branch on the way out
Three ways the restore was missed: a tracking app that moves HEAD and
quits, the same from a detached HEAD, and a status sync whose tracking
read exits rather than raising.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-38-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit dd49f6cb850f6e8462ff78bf19d81843a8f47ad4
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:18 2026 +0200
review-tui: do not let a failed tracking load skip the branch restore
load_tracking() exits rather than raises on a branch with no tracking
commit, and SystemExit is not an Exception, so it unwound out of
run_tracking_tui(). Catch it next to Exception; it is worth a warning,
not the session.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-37-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit d56c99b310acaeeb28b1e199a72e49b1b83267fb
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:17 2026 +0200
review-tui: restore the original branch however the tracking loop ends
The restore sat at the tail of the loop body, so quitting the tracking
list skipped it. That started to matter when the list grew actions
that move HEAD, like the revision upgrade. Lift the restore into a
helper and run it in a finally.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-36-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit f0309ce2ca0db188b25d56cb197c20711ed3f9b7
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:16 2026 +0200
tests: cover the revision upgrade's branch handling
A finished upgrade puts the worktree back on the branch, or on the
commit when it started detached. A create that exits leaves neither
the upgrade branch nor the checkout behind. An abandoned apply removes
an earlier attempt's leftover upgrade branch.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-35-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit cdf5b1f06f12b52e3d77a2c8409a296ccbfd3b1e
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:15 2026 +0200
review-tui: put the branch back after a revision upgrade
A revision upgrade builds a temporary branch and renames it onto the
review branch, and HEAD follows the rename. Nothing put it back, so
the worktree was left on the review branch whether the upgrade
finished or not. Restore on every exit path with a context manager
that takes git_head_restore_args(), like the rebase action beside it.
The error path could not run at all: create_review_branch() exits
rather than raises, and SystemExit is not an Exception, so a failed
apply left the half-built branch behind and unwound into the UI.
Catch it, delete the branch once HEAD is off it, and report "see
above" since the reason is already on screen. The conflict-resolve
path now falls through to the same create call and the same handler.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-34-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 94cdfa81ed2d789c42a096466f4f3693d8de26f1
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:14 2026 +0200
tests: cover create_review_branch cleaning up a half-built branch
Both starting points with the patch range failing to read: from a
branch and from a detached HEAD. The detached case starts at the tip
rather than the base, so restoring to the base would not pass.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-33-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit dd969668b5b9615d33db4cd3245b9a93cbf62835
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:13 2026 +0200
review: clean up a review branch that cannot be finished
create_review_branch() checks the new branch out before building it.
Two of the three failures after that point put the caller back and
delete the branch; the one that cannot read the patch range just
exits. A detached start got no cleanup at all, and the leftover branch
made the next attempt fail with "Branch already exists".
Route all three through one cleanup helper that restores via
git_head_restore_args(), so a detached start has somewhere to go back
to.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-32-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 1e6691c86a458dd96d4d33050792086318bd1853
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:12 2026 +0200
review: close the tracking database when archiving fails
Same leak as the auto-mark block: the connection is closed in the last
statement of the try, so a raising update_series_status() skips the
close. Close it in a finally.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-31-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit be0531bf1dfe307fafd49efb567d36042d9baefc
Author: Christian Brauner <brauner@kernel.org>
Date: Fri Jul 31 23:59:11 2026 +0200
tests: cover the review send's post-send bookkeeping
A tracking write that does not land is reported, a bookkeeping failure
never reads as "Send failed", and a dry run records nothing.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260731-work-b4-editor-branch-guard-v2-30-243fd19d322d@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 3cf7167db2c84a54efba04e9f570f156c5897043
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Fri Jul 31 22:55:39 2026 +0000
send: support sendemail.sendmailCmd for local mail submission
Git itself has supported sendemail.sendmailCmd since version 2.33 as
the preferred way to submit mail via a local sendmail-compliant
command, such as msmtp. Until now, b4 only recognized the historical
spelling where a path to the binary is given as the smtpServer value,
so users with sendmailCmd-based identities were silently routed to
the web endpoint, and got a confusing "No sendemail configs found"
message even though their sendemail config was found and loaded.
Add a get_sendemail_localcmd() helper that returns the configured
local command, giving sendmailCmd precedence over a path-like
smtpServer value to match git's own behavior, and use it in
get_smtp(), the web-endpoint fallback gate, and the pre-flight
display and reflect-mode guard in b4 send. Like git, a bare command
name without slashes is accepted and resolved via PATH.
Key changes:
- New b4.get_sendemail_localcmd() with sendmailCmd > smtpServer
precedence; get_smtp() now uses it for the local-command path
- b4 send only falls back to the web endpoint when neither
smtpserver nor sendmailcmd is set
- The b4-really-reflect-via guard applies to sendmailCmd as well
- Clarify the misleading "No sendemail configs found" debug messages
- Document sendmailCmd in contributor/send.rst and add tests
Reported-by: Tan Siewert <tan@siewert.io>
Link: https://msgid.link/2c6bc94e-29ab-4620-85fe-010b02942040@siewert.io
Assisted-by: LLM [codegen, tests, docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit f2f85d0fbf59b43b213c3a0900cea9c108bdb388
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Fri Jul 31 17:06:26 2026 +0000
review: never touch a checked-out review branch from maintenance
A background cron sweep (and the TUI's update-all, which shares the
same code path) rewrites the tip of a review branch to refresh its
tracking commit. When that branch is currently checked out, the
maintainer is working on it — possibly mid-rebase — and moving the
ref under them can corrupt their work. The same applies to the other
maintenance writers: snooze wake-up rewrites the tracking commit, and
queue delivery both writes a 'thanked' status into it and may archive
(delete) the branch.
Add git_branch_checked_out(), which detects a branch checked out in
the main repository or any linked worktree, and make every
maintenance path leave such a branch strictly alone:
- update_series_tracking() skips the tracking-commit refresh (the
database-side updates still run) and reports the skip; cron logs
it at debug level and the TUI update-all summary mentions it
- snooze wake-up defers the series entirely, since waking only the
database side would contradict the tracking commit and the next
rescan would re-snooze it
- queue delivery records 'thanked' in the database only, skipping
the tracking-commit write, and declines to archive
In all cases the skipped work self-heals on a later run, after the
maintainer has moved off the branch.
Reported-by: Mark Brown <broonie@kernel.org>
Assisted-by: LLM [investigation, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 1af191c5e5bf6503deda3ae91df873c390d7e6f5
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Fri Jul 31 16:27:35 2026 +0000
review: hint at "b4 review forget" when a series is already tracked
When cmd_track matches an already-tracked series it either refuses
outright or folds the posting in as a new revision and suggests
upgrading in the review TUI. For an archived series that advice
points at nothing actionable: the TUI does not show archived series
at all, which is exactly the trap the forget subcommand exists to
escape.
Print a "b4 review forget <change-id>" hint on the refusal path, and
replace the upgrade advice with it when the matched series is
archived.
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 0325393bace65b7b071cc54edc9f91beb2bb84fa
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Fri Jul 31 16:19:54 2026 +0000
review: add "b4 review forget" to erase a tracked series
A series that has been archived (e.g. through the thank-and-archive
flow) is no longer shown in the tracking TUI, but its records still
match new postings: tracking a new version of a partially-applied
archived series reports "already tracked" and points at a series the
maintainer can no longer see or act on, with no way out.
Add a "b4 review forget <series_id>" subcommand that completely
erases all records of a tracked series regardless of its status. The
series may be identified by its change-id, a lore URL, or any
message-id ever recorded for it (primary message, discovered/linked
revision, or individual patch). Before erasing, print a summary of
the matched series and request confirmation. A leftover review
branch is deleted along with the records, unless it is currently
checked out, in which case refuse and ask the maintainer to switch
away first.
Key changes:
- new tracking.cmd_forget() with confirmation prompt and branch
cleanup, wired into "b4 review" argument parsing and dispatch
- new tracking.find_tracked_change_id() resolver covering the
series, revisions, and series_patches tables
- delete_series() now also clears series_patches rows, which were
previously left behind
- man page section and tests for the new command
Reported-by: Mark Brown <broonie@kernel.org>
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit af86560d1c2fb7476e2a9d33925a6eaf0292100a
Merge: 3e016ed 6991ac9
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Fri Jul 31 05:08:08 2026 +0000
Merge patch series "Keep the user's git config out of b4's scratch worktrees"
Christian Brauner <brauner@kernel.org> says:
Keep the user's git config out of b4's scratch worktrees
With submodule.recurse=true in the user's config b4 cannot apply
anything in a repo that carries submodules. The scratch worktree's
sparse checkout recurses, looks for the per-worktree submodule clones a
just-created worktree cannot have, and dies:
fatal: not a git repository: ../../worktrees/b4-shazam-worktree/modules/ezgb
fatal: could not reset submodule index
That takes review-branch creation, b4 shazam and the TUI's test applies
with it. The b4 repo itself is affected through its vendored
patatt/liblore/ezgb submodules.
Everything b4 does in those worktrees is thrown away when the call
returns, so they can run with overrides of their own: no submodule
recursion and no gpg signing. SCRATCH_GIT_OPTS carries both, since each
is inert where the other matters, and every scratch worktree switches
over to it: the shazam apply, the TUI's five hand-rolled test applies,
the fake-am staging worktree and the sent-tag worktree in b4 send. The
commits that outlive their worktree keep the user's signing config.
Link: https://patch.msgid.link/20260729-work-b4-scratch-worktrees-v1-0-e96995158d4a@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 6991ac96a05cd410c1734a0cd15307481f11860b
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 12:44:33 2026 +0200
tests: exercise the scratch worktrees under submodule.recurse
An active but unpopulated submodule reproduces the recursing-checkout
fatal: a gitlink and .gitmodules in the tree plus a configured url, and
no clone anywhere. That is what every fresh linked worktree sees. The
fixture is built with plumbing so nothing is signed and master can be
advanced in place, and submodule.recurse goes on only once it is done.
git_fetch_am_into_repo() runs end to end that way, and so do the four
modal test-apply probes, the take flow's cherry-pick probe and
reroll()'s sent-tag worktree, which has to come back with the tag it
was asked for.
The fake-am test drives a series that bumps a gitlink, because that is
what puts a submodule into the synthesized tree, and poisons gpg on top
of it, so the reset and the am are covered at once.
The git_run_command() fixup gets its own test. With log.abbrevCommit=true
and a -c override in front of the subcommand the sha has to come back in
full.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-scratch-worktrees-v1-6-e96995158d4a@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 0abeddbb46c2f86b052de4e31816912246bd9d23
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 12:44:32 2026 +0200
send: use the scratch-worktree overrides when tagging a sent series
When the cover lives in a commit, which is the default cover strategy,
reroll() builds the sent tag out of a scratch worktree: sparse
checkout, cherry-pick the series onto the base, fetch the result back.
That checkout is the one fixed in git_fetch_am_into_repo() and it dies
the same way with submodule.recurse=true.
Nothing tells the user. The whole block sits under a try that logs
"Error tagging the revision" and carries on, so b4 send finishes
normally and the sent tag for the revision is simply never created.
The cherry-pick keeps the user's signing config. Its commits are what
the tag points at, so they outlive the worktree.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-scratch-worktrees-v1-5-e96995158d4a@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit cecec5d1844fdbcc4cfa90e56b10f6358eb4948d
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 12:44:31 2026 +0200
fake-am: use the scratch-worktree overrides in the staging worktree
make_fake_am_range() has a throwaway worktree of its own. It stages the
blobs the series expects to find, commits that tree, resets onto it and
ams the patches on top to get a commit range b4 can diff against. b4
diff goes through it, and so do the review TUI's range-diff and its
three-way merge prep.
The reset recurses like any other. A series that touches a submodule
puts a gitlink into the synthesized tree, and the reset then dies in
the same fresh worktree with no submodule clones. The am is worse.
Those commits exist to compute a diff range and nothing ever references
them, but git still reaches for the user's signing key, and with a card
that is not plugged in the whole range fails.
Both get SCRATCH_GIT_OPTS.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-scratch-worktrees-v1-4-e96995158d4a@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit c9a21b8111ddd79485a4e8d787ff43158ee49aff
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 12:44:30 2026 +0200
review-tui: use the shared scratch-worktree overrides for test applies
The test-apply paths build the same sparse scratch worktree in five
places, the four modal am probes and the take flow's cherry-pick probe.
Each of them passes -c commit.gpgsign=false on the apply by hand and
nothing at all on the checkout.
That checkout is the bug just fixed in git_fetch_am_into_repo(). With
submodule.recurse=true the probe's checkout -f dies in any repo that
carries submodules, so every test apply reported a failure that had
nothing to do with the patches.
Let's switch all five sites to SCRATCH_GIT_OPTS. gpgsign means nothing
to a checkout and recursion means nothing to am and cherry-pick, so the
one list serves every command and the applies keep behaving as before.
The per-site "# No signing" comments go with it.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-scratch-worktrees-v1-3-e96995158d4a@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit ea34dcdac92304fa422ee90ab9b541931d7e527e
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 12:44:29 2026 +0200
shazam: ignore the user's submodule.recurse in the scratch worktree
With submodule.recurse=true in the user's config every apply through
git_fetch_am_into_repo() fails in a repo that carries submodules. That
covers b4 shazam and review-branch creation in the review TUI:
Magic: Preparing a sparse worktree
Error running checkout into sparse workdir
fatal: not a git repository: ../../worktrees/b4-shazam-worktree/modules/ezgb
fatal: could not reset submodule index
git gives every linked worktree its own submodule clones under
.git/worktrees/<name>/modules/ and a just-created worktree has none, so
the recursing sparse checkout dies looking for them. The b4 repo itself
is affected through its vendored patatt/liblore/ezgb submodules.
Everything b4 does in that worktree is thrown away when the call
returns, so the user's checkout conveniences have no business running
there. Let's add SCRATCH_GIT_OPTS with the two overrides a git command
in a scratch worktree needs, no submodule recursion and no gpg signing,
the same override the TUI's test applies already pass by hand. The
sparse-checkout and the checkout get it.
The fetch out of the worktree takes nothing. It runs in the user's own
repo rather than in the scratch worktree, so the user's config is the
right one there, and a recursing fetch does not trip over the missing
clones anyway.
The apply itself keeps the user's config. Its commits are the real
series and not scratch throwaways, so signing stays in force. The
replay-on-full-worktree fallback needs nothing. Neither git-am nor
git-sparse-checkout consults submodule.recurse (checked on git 2.53).
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-scratch-worktrees-v1-2-e96995158d4a@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 97e09203a66f7849500cd0258d83864c15535461
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 12:44:28 2026 +0200
git_run_command: look past -c overrides for the subcommand
git_run_command() counteracts a local log.abbrevCommit by inserting
--no-abbrev-commit after the subcommand, which it expects at args[0].
That holds only as long as no caller passes git-level options first.
The next patch adds a list of -c overrides that callers prefix to their
arguments. With it args[0] is '-c', the fixup stops firing and nothing
says so. b4 parses full shas out of git-log output in several places,
so a user with log.abbrevCommit=true would get short ones back.
Skip over leading -c key=value pairs when looking for the subcommand.
While we are here, stop inserting into the caller's list in place.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-scratch-worktrees-v1-1-e96995158d4a@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 3e016ed6c78ff2d0389fceb412a7d46dd64a4626
Merge: 217605b dbcb250
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Fri Jul 31 04:59:00 2026 +0000
Merge patch series "b4 review: cover-letter titling on the update path"
Christian Brauner <brauner@kernel.org> says:
b4 review: cover-letter titling on the update path
Commit 83551c65f68d ("review: identify discovered revisions by their
cover letter") fixed how discovered revisions are identified, but the
copy of the recording loop in update_series_tracking() -- what
pressing 'u' in the tracking TUI runs -- was left as is. Newly
discovered revisions were still identified by their first present
patch, so upgrading a series re-titled it after patch 1/N, and since
series.subject is only written on track or upgrade, the row kept that
title forever.
Patch 1 routes the update path through the shared helper, re-derives a
stale series title from the cover letter on update (never from the
first-patch fallback, which would mis-title coverless series), and
makes the upgrade dialog use the resolved series' own title instead of
the catalog's.
Patch 2 adds the missing coverage: integration tests driving real
messages through a real LoreMailbox, unit tests pinning
realign_series_subject() to its narrow contract, and TUI tests for the
upgrade dialog title.
Link: https://patch.msgid.link/20260729-work-b4-cover-subject-v1-0-718507ed317b@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit dbcb2505b76d50476aef902aabdbf0b05411f509
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 10:58:18 2026 +0200
b4 review: test cover-letter titling on the update path
Cover the second revision-identification path, which had no tests of
its own: update_series_tracking() must name a newly discovered
revision after its cover letter, and must repair a series row that an
upgrade left titled after patch 1/N.
The integration tests drive the real code with real messages instead
of a mocked LoreMailbox -- a mock cannot tell a raw LoreSeries from a
get_series()'d one, and that distinction is the bug. Split a
_series_msgs() helper out of _build_lmbx() so the same series shape
feeds both a mailbox and a plain message list.
The unit tests pin realign_series_subject() to the contract that makes
it safe to run on every update: re-title from a cover letter, leave a
prefix-only formatting difference alone, and never write back the
first-patch fallback when the fetched thread has no cover -- that
would mis-title every coverless series.
At the TUI layer, assert the upgrade dialog shows and stores the
resolved series' own title rather than the stale catalog subject, and
falls back to the catalog when the resolved series never learned its
title and reports '(untitled)'.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-cover-subject-v1-2-718507ed317b@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit d0b501a40a210a3136587f5ab4b55c56f73e1073
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 10:58:17 2026 +0200
b4 review: identify updated revisions by their cover letter too
Commit 83551c65f68d ("review: identify discovered revisions by their
cover letter") fixed revision identification in
_record_discovered_revisions(), but update_series_tracking() carries a
hand-rolled copy of the same loop, and that copy is what pressing 'u'
in the tracking TUI runs. The fix never reached the path that matters.
The mechanism is the same: LoreMailbox files cover letters into its
parse-time covers dict and only get_series() injects one into
patches[0]. update_series_tracking() runs get_series() for the tracked
revision alone, so every other discovered revision is a raw LoreSeries
whose patches[0] is None, and picking the first present patch lands on
patch 1/N. That subject is what gets stored, what an upgrade carries
into the series row, and what the series list shows -- upgrading a
series re-titles it after its first patch.
Route the loop through _record_discovered_revisions() so both paths
identify a revision the same way. The shared helper also stores the
content fingerprint and keeps the sticky rethread flag intact; the
hand-rolled copy did neither.
That alone does not repair an already-upgraded series. The revisions
catalog heals on every re-add, but series.subject is only written when
a series is tracked or upgraded, so a row that took its title from
patch 1/N keeps it forever. Re-derive it on update via
realign_series_subject(), and only ever write back a cover-derived
title: the first-patch fallback is no better than what is already
stored, and would mis-title every correctly titled row whose cover
letter is missing from the fetched thread, coverless series first
among them.
The upgrade path stops taking the title from the catalog entirely: by
the time the base selection dialog opens it holds the resolved
LoreSeries, which knows its own title, so use that for the dialog
heading and the stored subject. The exception is a series that saw
neither its cover nor patch 1 and is stuck with the '(untitled)'
placeholder -- get_am_ready() skips missing patches, so such a series
still am-preps. Keep the catalog title there.
Fixes: 83551c65f68d ("review: identify discovered revisions by their cover letter")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-cover-subject-v1-1-718507ed317b@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 217605bbbe922aa5c0fbd82f6f63696c7590cc84
Merge: b1c280b 682920f
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Fri Jul 31 04:52:27 2026 +0000
Merge patch series "b4 tui: preserve the list viewport across rebuilds"
Christian Brauner <brauner@kernel.org> says:
b4 tui: preserve the list viewport across rebuilds
Updating a series with 'u' in the review tracking TUI made the list
visibly jump towards the top of the screen while the cursor stayed on
the updated entry. The tracker-style lists rebuild their ListView
wholesale on every refresh, so the scroll offset died with the
replaced widget; restoring the cursor index alone only scrolls the
minimum needed to make that row visible again, which pins the view to
the top of the list or glues the cursor row to the bottom edge.
Link: https://patch.msgid.link/20260729-work-b4-tui-scroll-preserve-v1-0-42be22445eaf@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 682920f6d1d34b1620f402d22fda7876d42a90d8
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 11:58:20 2026 +0200
tests: cover scroll preservation across list rebuilds
Drive the cursor mid-viewport, reload the way the apps do after an
update, and assert the rebuilt list keeps the exact scroll offset,
cursor index, scrollbar thumb position, and selection.
The tracking test reloads via _load_series the way _on_update_complete
does after 'u'. The bugs test drives BugListApp against a stubbed
repo (no git-bug or network) and reloads through the real
load_bugs-worker path the way _check_cache_changed does.
The cursor deliberately sits mid-viewport rather than at the bottom
edge: a plain rebuild's minimal scroll-into-view happens to reproduce
the bottom-edge offset exactly, which would let the test pass without
the fix.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-tui-scroll-preserve-v1-2-42be22445eaf@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 072c6f32cadee39fc66761421bd42fcb689a70fe
Author: Christian Brauner <brauner@kernel.org>
Date: Wed Jul 29 11:58:19 2026 +0200
b4 tui: preserve list scroll position across ListView rebuilds
The tracking and bugs TUIs rebuild their list wholesale on every
refresh. The old ListView is removed and a freshly constructed one is
created so the scroll position dies with the old widget. Restoring
the cursor index alone only scrolls the minimum needed to reveal that
row and so after every rebuild (updating a series with 'u', a status
change, the db-mtime poll) the viewport visibly jumped towards the top
while the cursor stayed on the right entry.
Add ReplacementListView to b4.tui and use it for both tracker-style
lists. It captures the predecessor's scroll offset and seeds the
replacement's scroll state on mount, before the first paint, so the
swap is invisible:
- set_scroll() seeds scroll_y: scroll_to() would clamp against the
zero virtual size of the not-yet-laid-out widget. The first reflow
re-validates the value, clamping it if the new list is shorter.
- scroll_target_y is seeded as well: wheel and page scrolling compute
from it, so leaving it at 0 would snap the view back to the top on
the first scroll tick.
- The scrollbar thumb is positioned manually: nothing syncs it when
the first reflow's re-validation does not change scroll_y.
There is deliberately no initial_index. ListView's default of 0
schedules a scroll-into-view for row 0 on mount, which would fire
after the restore and undo it. The index is assigned explicitly after
mounting instead. That schedules a minimal scroll-into-view which is
a no-op when the restored offset already shows the row, and otherwise
keeps the cursor visible (e.g. after a re-sort moved it).
The patchwork and lite lists rebuild without any cursor restore, and
the bug-detail comment list jumps to the top or end by design, so
those keep constructing plain ListViews.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Link: https://patch.msgid.link/20260729-work-b4-tui-scroll-preserve-v1-1-42be22445eaf@kernel.org
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit b1c280b9565d00fcbb7af93190d9cc15c30419dc
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Jul 30 20:30:09 2026 +0000
plan: add 0.17 b4 pr section for outgoing pull requests
Record the planned producer counterpart to b4 pr: compose and send
git-request-pull-style pull requests to upstream, with the body taken
from the signed tag message and contributors auto-cc'd from the range.
Include two pre-flight checks: verify the tag is pushed to and matches
the advertised pull URL, and verify the tag carries a valid
cryptographic signature -- refusing to send when either fails.
Assisted-by: LLM [brainstorm, plan]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit a8614c943b6ad5991b1df5df4205b81d433be78c
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Jul 30 20:14:54 2026 +0000
plan: add 0.17 workbook entry for batch agent review
Record the planned workbook feature under the b4 review improvements
for 0.17: multi-select serieses in the tracking TUI, arrange them into
an ordered queue stacked onto a target branch in a worktree, and have
the review agent work through the whole queue in a separate terminal
with cumulative context.
Fold the standalone "batch trailer selection" entry into the workbook
item, since that action will apply to workbooks specifically.
Assisted-by: LLM [brainstorm, plan]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 9f3c7fdb8defda905d63afa1fdaf6695e45ac90a
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Jul 30 19:17:00 2026 +0000
docs: add missing release-notes entries for two v0.16 features
The user-configurable reply templates (bc24995) and the
b4.review-apply-base pre-fill knob (b049e3e) both landed with config
and workflow documentation but without an entry in the unreleased
section of the release notes. Add both, matching the style of the
surrounding feature blocks.
Assisted-by: LLM [docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit be12565e4aba3b0ec5e43323fcb5e86407f4e27f
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Jul 30 18:32:34 2026 +0000
ty: verify queued thanks against the branch's remote, not just the mask
The ref-reachability check for queued thank-you messages could only
determine which repository to query from b4.thanks-check-repo or by
parsing the thanks-commit-url-mask value. Maintainers who use the
git.kernel.org /username/c/ short-URL masks got neither, silently
falling back to the HTTP status check -- which shared object storage
defeats, so a push to a CI tree still released thanks for commits that
were never published in the subsystem tree. The single flat
thanks-check-repo option also cannot help a maintainer who pushes to
several subsystem trees from one review repository.
The thanks message itself already declares what "pushed" means: the
"Applied to <tree> <branch>" line is generated from the remote that the
target branch tracks. Use that as the check target. The repository to
query is now resolved in priority order: remote.<name>.b4-check-repo on
the branch's remote (new, for per-remote overrides), the flat
b4.thanks-check-repo option, the branch remote's URL, and only then the
mask-derived URL. Presence of the commit on the branch's remote is
sufficient to qualify as pushed, so no distinction is made between push
and public URLs.
Delivery also now verifies reachability from the specific branch named
in the message rather than any advertised head: queue entries record an
X-Check-Branch header alongside X-Check-Repo, and ls-remote results are
filtered down to that branch when the remote advertises it. Entries
without the new headers keep the previous behavior.
Key changes:
- ty: add get_check_repo_for_branch() with the four-step resolution
order; it resets the BRANCH_INFO cache so long-lived TUI sessions
never reuse another branch's remote
- ty: commit_reachable_on_remote() takes an optional branch and checks
only that head when advertised, falling back to all heads otherwise
- ty: queue_message() accepts explicit checkrepo/checkbranch and
records X-Check-Branch; process_queue() threads it into the check
- review TUI: resolve the check repo and branch at thank time and
carry them through the preview/edit/queue flow
- docs: config.rst, review.rst queue section, and a release-notes
entry covering the reachability verification work
Reported-by: Mark Brown <broonie@kernel.org>
Link: https://lore.kernel.org/linux-spi/178541244855.81171.17307971925800575584.b4-ty@b4/
Assisted-by: LLM [analysis, codegen, tests, docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 799a98f4cf1bc164b07af5864dda42c31567a1a6
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Jul 30 17:38:51 2026 +0000
tui: send mail with Ctrl-y, keep S as an unlisted legacy alias
Every important confirmation dialog in the TUIs commits with Ctrl-y,
but sending mail was bound to capital S — an annoying inconsistency
for a keypress with real-world consequences. Make Ctrl-y the official
send binding on all four mail-sending preview screens: review emails
(ReviewApp email mode), thank-you previews (ThankScreen), follow-up
replies (FollowupReplyPreviewScreen), and bug replies (b4 bugs
ReplyPreviewScreen).
Capital S still triggers send everywhere, but is hidden from footers,
hints, and help text, so the muscle memory of maintainers used to the
old binding keeps working. The S skip-quoted binding in the thread
viewer is unrelated and unchanged.
Key changes:
- Ctrl-y send bindings added to all four screens; S demoted to
show=False
- footer hints, help overlay, and docs updated to advertise Ctrl-y
- release notes entry documenting the change and the legacy alias
- parametrized test asserting Ctrl-y is shown and S is the hidden
alias on every mail-sending screen
Assisted-by: LLM [codegen, tests, docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 0ce8e38cf2b436c04c57a00f7008de3c5debeb61
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Jul 30 17:26:08 2026 +0000
review: chain thank-you and archive onto a successful take
Maintainers pointed out that the Take -> Thank -> Archive sequence is
a lot of keypresses for what is almost always a single decision. Add
a "thank and archive on delivery" checkbox to the Take dialog (off by
default; set b4.review-thank-and-archive to pre-check it). When
enabled and the take leaves the series fully accepted, the thank-you
preview opens automatically, and a successful delivery of the
thank-you also archives the series.
The archive is tied to the actual delivery, not to the take: a direct
send archives immediately, while a queued message records the intent
as an X-B4-Archive-After-Send header in the queue file and the
archive happens when the message actually goes out (TUI queue
delivery or b4 review cron). Delivery skips the archive and leaves
the series 'thanked' when it is no longer clearly safe: a newer
revision has appeared in the meantime, or the series status drifted
from 'accepted' while the message sat in the queue (e.g. returned to
reviewing). The delivery report annotates the outcome either way.
Key changes:
- extract the TUI's _archive_branch guts into b4.review.archive_series
(idempotent; refuses to delete a checked-out branch unless the
caller allows switching away, so cron never yanks a checkout out
from under the user)
- queue_message grows archive_after; process_queue applies it after
its 'thanked' finalization, guarded as above
- _mark_series_thanked no longer resurrects a series that was
manually archived while its message was queued, and no longer warns
about the deleted review branch of such a series
- the Upgrade action is now offered on accepted/thanked series with a
known newer revision, so a kept-back series is not a dead end
- partial takes never chain; cancelling the thank preview aborts the
chain and leaves the series accepted
Assisted-by: LLM [design, codegen, tests, docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 92a91c74de31460540036e93bfee4431a951934f
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Jul 30 15:21:15 2026 +0000
mbox: log the revision-check narration at INFO, not CRITICAL
In b4's logging convention CRITICAL means "essential output that must
survive -q", but "Checking for newer/older revisions" is progress
narration ahead of a slow lore round-trip, not a result. Log it at
INFO so that -q runs of am/shazam/mbox stay properly silent during
the revision check.
Assisted-by: LLM [codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 740759c2ec34157711573f5127aa46a587c138cc
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Thu Jul 30 15:09:18 2026 +0000
review: suppress CRITICAL-level narration in cron sweeps too
b4 traditionally logs "always show this" narration at CRITICAL level,
so mbox.py's "Checking for newer revisions" sailed straight through
the cron sweep's ERROR-threshold filter, one line per tracked series.
Make _quiet_cron with no threshold drop every record, like the TUI's
_quiet_worker: per-series failures inside the update sweep are
collected and re-reported after the block, and anything fatal escapes
as an exception, so nothing of value is lost. Queue delivery keeps
its WARNING threshold, since its warnings and criticals are genuine
errors rather than narration.
Assisted-by: LLM [codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 99fe195c0aa5a88be5033f87cad22bc32d27cbfe
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Jul 29 06:15:54 2026 +0000
b4 tui: quit on capital Q, warn on bare q
Bare 'q' sits in the same key namespace as the heavily used
navigation keys (j/k/n/p/h/l, space), so a single mistyped key
during normal navigation quit a TUI app outright. In the review
interface that drops the maintainer back to the tracking list
mid-review, losing their place.
Move quitting to capital 'Q' in all four TUI apps (tracking list,
review interface, Patchwork browser, and b4 bugs); bare 'q' now
shows a warning notification pointing at 'Q' instead. The shared
QUIT_BINDINGS pair and the notify_quit_hint() helper live in
b4.tui._common so all apps stay consistent. Modal dialogs and
sub-views (thread viewer, message view, help) keep closing on 'q'
as before.
In the tracking list, the thank-you queue moves from 'Q' to 'T' to
make room, and the deliver keybinding inside the queue viewer moves
with it, so the open-then-deliver sequence stays a double-tap
(T, T) as it was before (Q, Q).
Reported-by: Matthieu Baerts <matttbe@kernel.org>
Fixes: https://lore.kernel.org/tools/bc88076e-2629-45bf-ab20-242f823dde4e@kernel.org/ # 307bf13
Assisted-by: LLM [codegen, tests, docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit b049e3e5329af699dd6d057eeb3da6473a659373
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Jul 29 05:44:28 2026 +0000
b4 review tui: add b4.review-apply-base for the base dialog pre-fill
Matthieu Baerts prefers applying series on top of the current HEAD,
but the base selection dialog always pre-fills the series-specified
base-commit or a find_base() guess, and b4.review-target-branch only
feeds the suggestion list -- its documentation ("default target
branch to use when taking patches") even invites the misreading that
it drives the base.
Add a b4.review-apply-base config option. When set and resolvable to
a commit, it wins the pre-fill over the series base and the guess;
the auto-detection result moves into the dialog hint so no
information is lost. An unresolvable value warns in the hint and
falls back to the automatic chain. The value is only a pre-fill: the
dialog input remains editable and is validated on confirmation, so a
configured HEAD is pinned to a SHA at dialog time rather than
resolved lazily at apply time.
The detection chain was duplicated verbatim between the take and
upgrade flows; extract it into _detect_initial_base() and the
suggestion-list construction into _build_base_suggestions(), shared
by both flows, so the new knob applies uniformly.
Key changes:
- New b4.review-apply-base config knob (base dialog pre-fill override)
- _detect_initial_base() / _build_base_suggestions() shared helpers
- config.rst: new term; review-target-branch reworded to make clear
it selects the take destination, not the apply base
- review.rst: document the base selection dialog and the new knob
Suggested-by: Matthieu Baerts <matttbe@kernel.org>
Link: https://msgid.link/bc88076e-2629-45bf-ab20-242f823dde4e@kernel.org
Assisted-by: LLM [analysis, codegen, tests, docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit bc2499558705abffca5eaf18d762e0963cc63ba7
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Jul 29 05:12:35 2026 +0000
b4 review: add user-configurable reply templates
Add a b4.review-reply-template config option that wraps outgoing
review replies in a user-supplied template, following the same
mechanism as the b4-ty thank-you templates: the option points at a
template file read via read_template() (lines starting with '#' are
removed) and substitution is done with string.Template
safe-substitute semantics.
The template must contain the ${reply} placeholder, which expands to
the composed reply content — the quoted patch context, the
maintainer's comments, and any trailers. The wrap is applied at
email-composition time in _build_review_email(), so the WYSIWYG
reply-buffer semantics and the trailing-quote trim are untouched, all
reply kinds (per-patch, cover-letter, trailer-only) are wrapped
uniformly, and the result is visible in the TUI email preview before
sending. If the rendered reply carries its own signature delimiter,
the automatic signature append is skipped as before.
Key changes:
- _get_reply_template()/_apply_reply_template() in review/_review.py
- review-reply-template added to DEFAULT_CONFIG
- templates/review-reply-template.example documents all placeholders
(reply, fromname, firstname, fromemail, subject, sentdate, msgid,
myname, myemail, signature)
- docs: b4.review-reply-template term in config.rst and a 'Reply
templates' section in maintainer/review.rst
- 7 new tests in test_review.py; plan.otl item checked off
Suggested-by: Matthieu Baerts <matttbe@kernel.org>
Link: https://lore.kernel.org/tools/bc88076e-2629-45bf-ab20-242f823dde4e@kernel.org/
Assisted-by: LLM [design, codegen, tests, docs]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit fd9e3575af9380b977bd472e0d047c4ecad875ad
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Jul 29 05:00:58 2026 +0000
plan: add Matthieu Baerts review-TUI feedback items for v0.16
Add two feature items from Matthieu Baerts' review-TUI feedback
(msgid bc88076e-2629-45bf-ab20-242f823dde4e@kernel.org) under the
v0.16 'b4 review' improvements group:
- Configurable default apply base in the take dialog
- User-configurable reply templates modeled on b4 ty's template files
The two defects from the same report are tracked in git-bug as
5141426 (reply buffer drops --- notes) and 307bf13 (bare 'q' exits
review view).
Assisted-by: LLM [triage, plan entries]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 8b7247929c7450fa8f9fe280026fb3dc434469e1
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Jul 29 04:01:16 2026 +0000
b4 review tui: only sync menu-managed trailers in reply buffers
When a hand-edited reply buffer exists, confirming the trailer menu
synced the buffer's trailers to the menu selection by removing every
trailer-looking line whose name was not among the checked options.
The menu only offers Acked-by, Reviewed-by, Tested-by and NACKed-by,
so any other trailer the maintainer typed into their reply — a Fixes:
suggestion for the submitter, a Cc:, a Suggested-by: — was silently
deleted from the buffer the moment a menu trailer was toggled.
Extract the buffer-sync logic into _sync_reply_trailers() and restrict
it to the names the menu actually manages, which now live in
TRAILER_MENU_NAMES as the single source of truth for both the menu and
the sync. Anything else in the buffer is the maintainer's own content
and stays exactly where they put it. Document the semantics in the
reviewer guide.
Reported-by: Matthieu Baerts <matttbe@kernel.org>
Fixes: https://lore.kernel.org/tools/b2c44799-85a9-4576-9aae-b43eb1092315@kernel.org/ # 9ef6d0d
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit 7eadd344a4337e4292b06ab3addf9e378caaf6cf
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Wed Jul 29 04:00:56 2026 +0000
b4: normalize CRLF line endings from editor round-trips
Mail-oriented editor configurations may save the edit buffer with dos
line endings (for example a vim autocmd forcing fileformat=dos on
*.eml files). b4 stored those bytes verbatim as the review reply
buffer, and every downstream consumer assumes unix line endings: the
reply-buffer helpers split on '\n' and insert LF-only lines, producing
mixed-ending buffers that show up as ^M on every line in the editor
and as stray blank lines in the email preview.
Canonicalize CRLF/CR to LF when reading the edited buffer back in
edit_in_editor, making every editor round-trip immune regardless of
which command consumes it. Reply buffers stored by older versions may
still carry CRLF in tracking data, so also normalize inside the
reply-buffer trailer helpers and when seeding the reply editor, which
heals stored buffers on their next touch.
While restructuring _remove_trailer_from_reply for this, also collapse
the blank gap a removed trailer leaves behind when it sat alone
between blank lines, instead of leaving a doubled empty gap in the
buffer.
Reported-by: Matthieu Baerts <matttbe@kernel.org>
Fixes: https://lore.kernel.org/tools/b2c44799-85a9-4576-9aae-b43eb1092315@kernel.org/ # c635da8
Link: https://lore.kernel.org/tools/b2c44799-85a9-4576-9aae-b43eb1092315@kernel.org/ # 9ef6d0d
Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
commit f44bbae418d3655902c0666f08ef310a18e8e6a3
Author: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Date: Tue Jul 28 21:07:37 2026 +0000
docs: update review docs for the has_newer upgrade rework
The has-newer-first-class merge changed user-visible behavior that the
docs still described the old way: the getting-started guide promised
that [u]pdate brings a waiting series back to active status (the
implicit promotion that was removed), and the reference described
upgrade as available only while reviewing.
Update both to match the shipped behavior:
- Document the '↑' flag in the S column, the up: limit token (plus the
previously undocumented s:/t: tokens), and waiting series surfacing
into the Active group when a newer revision arrives.
- Rewrite the "Upgrading to a newer revision" section: update only
records revisions, upgrade is an explicit action gated purely on a
newer revision existing, works from any state that can hold one, and
lands back in reviewing (branchless series just switch revisions).
- Add Upgrade to the waiting and new action-menu listings, and the
missing Link-a-revision entry for waiting.
- Add a "Reworked revision-upgrade flow" entry to the unreleased
changelog.
Assisted-by: LLM [codegen]
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>