public inbox for tools@linux.kernel.org
 help / color / mirror / Atom feed
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


  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