From: Christian Brauner <brauner@kernel.org>
To: "Kernel.org Tools" <tools@kernel.org>
Cc: Christian Brauner <christian@amutable.com>,
Konstantin Ryabitsev <konstantin@linuxfoundation.org>,
Christian Brauner <brauner@kernel.org>,
"Claude Opus 4.6" <noreply@anthropic.com>
Subject: [PATCH b4 1/3] shazam: refactor git_fetch_am_into_repo for deterministic worktree
Date: Fri, 06 Mar 2026 12:52:24 +0100 [thread overview]
Message-ID: <20260306-master-v1-1-5a4b9cbe11d7@kernel.org> (raw)
In-Reply-To: <20260306-master-v1-0-5a4b9cbe11d7@kernel.org>
Replace the git_temp_worktree context manager with a deterministic
worktree path (<git-common-dir>/b4-shazam-worktree) and manual
lifecycle management. This is preparation for conflict resolution
support where the worktree needs to persist after a failed git-am.
Add AmConflictError(RuntimeError) exception so callers can
distinguish am failures from other errors. Since it inherits from
RuntimeError, existing callers that catch RuntimeError continue to
work unchanged.
Add an am_flags parameter to allow callers to pass additional flags
to git-am (e.g., -3 for three-way merge).
Clean up any stale worktree from a previous run at the start of
each invocation. On am failure, the worktree is intentionally left
behind (for future conflict resolution use); the stale-worktree
cleanup handles this on the next run.
Update the shazam caller in make_am to explicitly catch
AmConflictError, clean up the worktree, and log the failure,
preserving the current user-visible behavior.
Also fix a minor str(gwt) -> gwt in the FETCH_HEAD origin rewrite
since gwt is already a str.
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>
---
src/b4/__init__.py | 51 +++++++++++++++++++++++++++++++++++++++------------
src/b4/mbox.py | 8 ++++++++
2 files changed, 47 insertions(+), 12 deletions(-)
diff --git a/src/b4/__init__.py b/src/b4/__init__.py
index eab290b..0baaec8 100644
--- a/src/b4/__init__.py
+++ b/src/b4/__init__.py
@@ -73,6 +73,13 @@ __VERSION__ = '0.15-dev'
PW_REST_API_VERSION = '1.2'
+class AmConflictError(RuntimeError):
+ def __init__(self, worktree_path: str, output: str):
+ self.worktree_path = worktree_path
+ self.output = output
+ super().__init__(output)
+
+
def _dkim_log_filter(record: logging.LogRecord) -> bool:
# Hide all dkim logging output in normal operation by setting the level to
# DEBUG. If debugging output has been enabled then prefix dkim logging
@@ -4721,31 +4728,48 @@ def git_revparse_obj(gitobj: str, gitdir: Optional[str] = None) -> str:
def git_fetch_am_into_repo(gitdir: Optional[str], ambytes: bytes, at_base: str = 'HEAD',
- origin: Optional[str] = None, check_only: bool = False) -> None:
+ origin: Optional[str] = None, check_only: bool = False,
+ am_flags: Optional[List[str]] = None) -> None:
if gitdir is None:
gitdir = os.getcwd()
topdir = git_get_toplevel(gitdir)
- with git_temp_worktree(topdir, at_base) as gwt:
+ common_dir = git_get_common_dir(topdir)
+ if not common_dir:
+ raise RuntimeError('Unable to determine git common dir')
+ gwt = os.path.join(common_dir, 'b4-shazam-worktree')
+
+ # Clean up any stale worktree from a previous run
+ if os.path.exists(gwt):
+ git_run_command(topdir, ['worktree', 'remove', '--force', gwt])
+
+ gitargs = ['worktree', 'add', '--detach', '--no-checkout', gwt]
+ if at_base:
+ gitargs.append(at_base)
+ ecode, out = git_run_command(topdir, gitargs, logstderr=True)
+ if ecode > 0:
+ raise RuntimeError('Failed to create worktree: %s' % out.strip())
+
+ cleanup = True
+ try:
logger.info('Magic: Preparing a sparse worktree')
- ecode, out = git_run_command(gwt, ['sparse-checkout', 'set'], logstderr=True)
+ ecode, out = git_run_command(gwt, ['sparse-checkout', 'set'], logstderr=True, rundir=gwt)
if ecode > 0:
logger.critical('Error running sparse-checkout set')
logger.critical(out)
raise RuntimeError
- ecode, out = git_run_command(gwt, ['checkout', '-f'], logstderr=True)
+ ecode, out = git_run_command(gwt, ['checkout', '-f'], logstderr=True, rundir=gwt)
if ecode > 0:
logger.critical('Error running checkout into sparse workdir')
logger.critical(out)
raise RuntimeError
- ecode, out = git_run_command(gwt, ['am'], stdin=ambytes, logstderr=True)
+ amargs = ['am']
+ if am_flags:
+ amargs.extend(am_flags)
+ ecode, out = git_run_command(gwt, amargs, stdin=ambytes, logstderr=True, rundir=gwt)
if ecode > 0:
- logger.critical('Unable to cleanly apply series, see failure log below')
- logger.critical('---')
- logger.critical(out.strip())
- logger.critical('---')
- logger.critical('Not fetching into FETCH_HEAD')
- raise RuntimeError
+ cleanup = False
+ raise AmConflictError(gwt, out.strip())
if check_only:
return
logger.info('---')
@@ -4758,6 +4782,9 @@ def git_fetch_am_into_repo(gitdir: Optional[str], ambytes: bytes, at_base: str =
logger.critical('Unable to fetch from the worktree')
logger.critical(out.strip())
raise RuntimeError
+ finally:
+ if cleanup:
+ git_run_command(topdir, ['worktree', 'remove', '--force', gwt])
if not origin:
return
@@ -4772,7 +4799,7 @@ def git_fetch_am_into_repo(gitdir: Optional[str], ambytes: bytes, at_base: str =
with open(fhf.rstrip(), 'r') as fhh:
contents = fhh.read()
mmsg = 'patches from %s' % origin
- new_contents = contents.replace(str(gwt), mmsg)
+ new_contents = contents.replace(gwt, mmsg)
if new_contents != contents:
with open(fhf.rstrip(), 'w') as fhh:
fhh.write(new_contents)
diff --git a/src/b4/mbox.py b/src/b4/mbox.py
index 3d12f8e..132cbb9 100644
--- a/src/b4/mbox.py
+++ b/src/b4/mbox.py
@@ -389,6 +389,14 @@ def make_am(msgs: List[EmailMessage], cmdargs: argparse.Namespace, msgid: str) -
else:
logger.info(' Base: %s (use --merge-base to override)', base_commit)
b4.git_fetch_am_into_repo(topdir, ambytes=ambytes, at_base=base_commit, origin=linkurl)
+ except b4.AmConflictError as cex:
+ b4.git_run_command(topdir, ['worktree', 'remove', '--force', cex.worktree_path])
+ logger.critical('Unable to cleanly apply series, see failure log below')
+ logger.critical('---')
+ logger.critical(cex.output)
+ logger.critical('---')
+ logger.critical('Not fetching into FETCH_HEAD')
+ sys.exit(1)
except RuntimeError:
sys.exit(1)
--
2.47.3
next prev parent reply other threads:[~2026-03-06 11:52 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-06 11:52 [PATCH b4 0/3] shazam: conflict resolution support for b4 shazam -H Christian Brauner
2026-03-06 11:52 ` Christian Brauner [this message]
2026-03-06 16:10 ` [PATCH b4 1/3] shazam: refactor git_fetch_am_into_repo for deterministic worktree Konstantin Ryabitsev
2026-03-24 11:33 ` Christian Brauner
2026-03-06 11:52 ` [PATCH b4 2/3] shazam: enable three-way merge for b4 shazam -H Christian Brauner
2026-03-06 11:52 ` [PATCH b4 3/3] shazam: enable merge conflict resolution for b4 shazam -H --resolve Christian Brauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260306-master-v1-1-5a4b9cbe11d7@kernel.org \
--to=brauner@kernel.org \
--cc=christian@amutable.com \
--cc=konstantin@linuxfoundation.org \
--cc=noreply@anthropic.com \
--cc=tools@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox