[PATCH b4 v2 0/7] shazam: stop --resolve from silently dropping commits

Christian Brauner <[email protected]> Wed, 24 Jun 2026 10:51:52 +0200
Newsgroups org.kernel.linux.tools
Message-ID <[email protected]>
b4 shazam --resolve exists to help land a series that doesn't apply
cleanly. The problem that motivated this series is that, while resolving,
it can silently drop whole commits from the merge it produces: you end up
with a merge that is quietly missing changes, and nothing in the output
points at what went missing.

Why they vanish

When git am can't apply the series, the old --resolve path merged the
patches that did apply, then replayed each remaining patch with
"git apply --3way" and staged the result with "git add -u".

git apply --3way can only do its three-way merge if the blob IDs recorded
in the patch's "index <old>..<new>" lines are present in your object
store. For patches that arrived over email those blobs usually aren't
there -- which is frequently the very reason git am -3 failed to begin
with. In that case git apply --3way falls back to a plain apply, fails
with "patch does not apply", and exits non-zero having written nothing at
all: no conflict markers, no unmerged index entries.

The replay loop assumed any non-zero exit meant "conflict markers are in
the tree." It advanced past the patch and asked you to resolve conflicts
that did not exist, so b4 shazam --continue happily resumed at the next
patch. The skipped patch's changes never entered the merge -- they were
simply gone, with no warning. A patch that only adds a new file can
disappear the same way when it rides along with a change that can't be
applied.

How this fixes it

Stop replaying patches with git apply entirely. On a conflict, b4 already
leaves the in-progress git am parked in a throwaway worktree; keep it
there and finish it natively:

    cd <worktree printed by b4>
    # resolve the conflicted files
    git am --continue        # or: git am --skip

git am drives the whole remaining series through git's own machinery,
which stops on every patch it cannot apply and never silently skips one.
Once it is done, b4 shazam --continue fetches the fully-applied series and
merges it exactly once -- the same merge a clean b4 shazam makes. Because
every patch now goes through git am, nothing can vanish. This also brings
--resolve in line with how the review TUI already resolves conflicts.

Since both now resolve am conflicts the same way, the series starts by
lifting the worktree plumbing the TUI already had -- detecting an
in-progress git am, and fetching the finished result out of the worktree
before dropping it -- into b4 core, and shazam reuses it rather than
growing its own copy.

Resolving conflicts in subdirectory files

One gap hid behind that guarantee. The series is applied in a sparse
worktree where only root-level files are materialized, and git's 3-way
merge refuses to touch skip-worktree paths. So a conflict in a
subdirectory file -- the common case for real series -- still made
git am -3 abort with a clean index: no markers, nothing to resolve. The
user was pointed at a conflict that was not there, git am --continue
dead-ended, and git am --skip silently dropped the patch. Only root-file
conflicts, which the tests happened to use, ever worked.

git_fetch_am_into_repo now rebuilds a full worktree on a conflict before
handing it back -- abort the partial am, disable sparse-checkout, replay --
so the conflict is recorded with real markers to resolve. b4 shazam
--resolve and the review TUI both apply through this one function, so both
are fixed, and a regression test drives a subdirectory conflict end to end.

The same commit hardens the rest of the flow: b4 shazam --abort no longer
crashes on a corrupt state file, --continue refuses up front on a dirty
working tree (keeping its state so it stays re-runnable), a failed
worktree fetch leaves the resolved am in place to retry, and the docs now
note that --continue merges with -M and leaves FETCH_HEAD with -H.

Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
Changes in v2:
- Fixed b4 shazam conflict resolutin and aligned it with TUIs behavior.
- Link to v1: https://patch.msgid.link/[email protected]

---
Christian Brauner (7):
      review-tui: lift worktree am-conflict helpers into b4 core
      shazam: resolve conflicts via native git-am, never dropping patches
      shazam: test native-am conflict resolution keeps every patch
      shazam: resolve conflicts in subdirectory files, not just the repo root
      shazam: note subshell alternative to the --resolve two-phase model
      shazam: resolve --resolve conflicts inline via a subshell
      shazam: test the inline --resolve subshell flow

 docs/maintainer/am-shazam.rst      |  46 ++--
 src/b4/__init__.py                 | 231 ++++++++++++++++++-
 src/b4/command.py                  |  14 --
 src/b4/mbox.py                     | 414 ++++++---------------------------
 src/b4/review_tui/_tracking_app.py | 109 +--------
 src/b4/tui/_common.py              |  58 +----
 src/tests/test_three_way_merge.py  | 455 +++++++++++++++++++++----------------
 src/tests/test_tui_tracking.py     |   4 +-
 8 files changed, 590 insertions(+), 741 deletions(-)
---
base-commit: 0a1954d329ab57f7aab40743fad7dc6e3c45d35d
change-id: 20260623-shazam-resolve-native-am-c56fe868c360