public inbox for tools@linux.kernel.org
 help / color / mirror / Atom feed
* [PATCH b4 v4] ez: allow cleaning multiple branches at once
@ 2026-02-26 10:05 Antonin Godard
  2026-02-26 16:38 ` Konstantin Ryabitsev
  0 siblings, 1 reply; 2+ messages in thread
From: Antonin Godard @ 2026-02-26 10:05 UTC (permalink / raw)
  To: Kernel.org Tools; +Cc: Konstantin Ryabitsev, Thomas Petazzoni, Antonin Godard

Allow "b4 prep --cleanup" to cleanup multiple branches in one go. If an
error occurs (branch not known/empty branch/currently checked out), just
return to continue cleaning up branches. Give the user multiple options:
"y" to cleanup, "n" to skip, "q" to abort the cleanup, "s" to show
the branch log with diffs, and "?" to print help about these options.

Passing nothing to --cleanup still shows the available branches to
cleanup.

Signed-off-by: Antonin Godard <antonin.godard@bootlin.com>
---
Changes in v4:
- Address feedback from Konstantin (%-formatting)
- Link to v3: https://patch.msgid.link/20260225-multiple-prep-cleanup-v3-1-db77b14dfdd1@bootlin.com

Changes in v3:
- Address feedback from Konstantin
- Link to v2: https://patch.msgid.link/20260203-multiple-prep-cleanup-v2-1-50d8ccefe25c@bootlin.com

Changes in v2:
- return instead of failing if branch is not prep-managed
- show branch log instead of general information on the branch
- Link to v1: https://patch.msgid.link/20251211-multiple-prep-cleanup-v1-1-c180f87248c4@bootlin.com
---
 docs/contributor/prep.rst | 10 ++---
 src/b4/command.py         |  4 +-
 src/b4/ez.py              | 99 +++++++++++++++++++++++++++++++++--------------
 3 files changed, 77 insertions(+), 36 deletions(-)

diff --git a/docs/contributor/prep.rst b/docs/contributor/prep.rst
index e6b45c7..9e9da65 100644
--- a/docs/contributor/prep.rst
+++ b/docs/contributor/prep.rst
@@ -342,15 +342,15 @@ up the prep-managed branch, together with all of its sent tags::
 
     b4 prep --cleanup
 
-This command lists all prep-managed branches in your repository. Pick a
-branch to clean up, make sure it's not currently checked out, and run
-the command again::
+This command lists all prep-managed branches in your repository. Pick one or
+more branches to clean up, make sure it's not currently checked out, and run the
+command again::
 
-    b4 prep --cleanup b4/my-topical-branch
+    b4 prep --cleanup b4/my-topical-branch ...
 
 After you confirm your action, this should create a tarball with all the
 patches, cover letters, and tracking information from your series.
-Afterwards, b4 deletes the branch and all related tags from your local
+Afterwards, b4 deletes the branch(es) and all related tags from your local
 repository.
 
 .. _prep_flags:
diff --git a/src/b4/command.py b/src/b4/command.py
index 0e685cd..54c32a5 100644
--- a/src/b4/command.py
+++ b/src/b4/command.py
@@ -344,8 +344,8 @@ def setup_parser() -> argparse.ArgumentParser:
                        help='Mark current revision as sent and reroll (requires cover letter msgid)')
     spp_g.add_argument('--show-info', metavar='PARAM', nargs='?', const=':_all',
                        help='Show series info in a format that can be passed to other commands.')
-    spp_g.add_argument('--cleanup', metavar='BRANCHNAME', nargs='?', const='_show',
-                       help='Archive and remove a prep-tracked branch and all its sent/ tags')
+    spp_g.add_argument('--cleanup', metavar='BRANCHNAME', nargs='*',
+                       help='Archive and remove prep-tracked branches and all associated sent/ tags')
 
     ag_prepn = sp_prep.add_argument_group('Create new branch', 'Create a new branch for working on patch series')
     ag_prepn.add_argument('-n', '--new', dest='new_series_name',
diff --git a/src/b4/ez.py b/src/b4/ez.py
index 42379a7..9dcad6d 100644
--- a/src/b4/ez.py
+++ b/src/b4/ez.py
@@ -2520,47 +2520,41 @@ def get_prep_managed_branches(gitdir: Optional[str] = None) -> List[str]:
     return mybranches
 
 
-def cleanup(param: str) -> None:
-    if param == '_show':
-        # Show all b4-tracked branches
-        mybranches = get_prep_managed_branches(None)
-        if not len(mybranches):
-            logger.info('No b4-tracked branches found')
-            sys.exit(0)
+def _cleanup_branch(branch: str) -> None:
 
-        logger.info('Please specify branch:')
-        for branch in mybranches:
-            logger.info(' %s', branch)
+    if not b4.git_branch_exists(None, branch):
+        logger.error('ERROR: Not a known branch: %s', branch)
         return
 
-    mybranch = param
-    if not b4.git_branch_exists(None, mybranch):
-        logger.critical('Not a known branch: %s', mybranch)
-        sys.exit(1)
-    is_prep_branch(mustbe=True, usebranch=mybranch)
-    base_commit, start_commit, end_commit = get_series_range(usebranch=mybranch)
+    if not is_prep_branch(usebranch=branch):
+        logger.error('ERROR: %s is not a prep-managed branch', branch)
+        return
+
+    base_commit, start_commit, end_commit = get_series_range(usebranch=branch)
     # start commit and end commit can't be the same
     if start_commit == end_commit:
-        logger.critical('CRITICAL: %s appears to be an empty branch', mybranch)
-        sys.exit(1)
+        logger.error('ERROR: %s appears to be an empty branch', branch)
+        return
+
     # Refuse to clean up the currently checked out branch
     curbranch = b4.git_get_current_branch()
-    if curbranch == mybranch:
-        logger.critical('CRITICAL: %s is currently checked out, cannot cleanup', mybranch)
-        sys.exit(1)
-    cover, tracking = load_cover(usebranch=mybranch)
+    if curbranch == branch:
+        logger.error('ERROR: %s is currently checked out, cannot cleanup', branch)
+        return
+
+    cover, tracking = load_cover(usebranch=branch)
     # Find all tags
     ts = tracking['series']
     tags = list()
-    logger.info('Will archive and delete all of the following:')
+    logger.info('\nWill archive and delete all of the following:')
     logger.info('---')
-    logger.info('branch: %s', mybranch)
+    logger.info('branch: %s', branch)
     if 'history' in ts:
         for rn, links in ts['history'].items():
             tagname, revision = get_sent_tagname(ts.get('change-id'), SENT_TAG_PREFIX, rn)
             tag_commit = b4.git_revparse_tag(None, tagname)
             if not tag_commit:
-                tagname, revision = get_sent_tagname(mybranch, SENT_TAG_PREFIX, rn)
+                tagname, revision = get_sent_tagname(branch, SENT_TAG_PREFIX, rn)
                 tag_commit = b4.git_revparse_tag(None, tagname)
             if not tag_commit:
                 logger.debug('No tag matching revision %s', revision)
@@ -2575,7 +2569,37 @@ def cleanup(param: str) -> None:
             tags.append((tagname, base_commit, tag_commit, revision, cover))
     logger.info('---')
     try:
-        input('Press Enter to confirm or Ctrl-C to abort')
+        resp = None
+        while resp is None:
+            resp = input('Proceed? [y/s/q/N/?] ')
+            if resp == "?":
+                logger.info(textwrap.dedent(
+                    """
+                    Possible answers:
+                    y: cleanup the branch
+                    s: show branch log
+                    q or Ctrl-C: abort cleanup
+                    n (default): do not cleanup this branch
+                    ?: show this help message
+                    """))
+                resp = None
+            elif resp in ("show", "s"):
+                ecode, out = b4.git_run_command(None, ["log",
+                                                       "--patch",
+                                                       "--color=always",
+                                                       f"{start_commit}~..{end_commit}"])
+                if ecode > 0:
+                    logger.critical('ERROR: unable to show git log between %s and %s',
+                                    start_commit, end_commit)
+                    sys.exit(130)
+                logger.info(out)
+                logger.info('')
+                resp = None
+            elif resp == "q":
+                sys.exit(130)
+            elif resp != "y":
+                return
+
     except KeyboardInterrupt:
         logger.info('')
         sys.exit(130)
@@ -2597,13 +2621,13 @@ def cleanup(param: str) -> None:
         write_to_tar(tfh, f'{change_id}/tracking.js', mnow, ifh)
         ifh.close()
         # Add the current series
-        logger.info('Archiving branch %s', mybranch)
+        logger.info('Archiving branch %s', branch)
         patches = b4.git_range_to_patches(None, start_commit, end_commit)
         ifh = io.BytesIO()
         b4.save_git_am_mbox([patch[1] for patch in patches], ifh)
         write_to_tar(tfh, f'{change_id}/patches.mbx', mnow, ifh)
         ifh.close()
-        deletes.append(['branch', '--delete', '--force', mybranch])
+        deletes.append(['branch', '--delete', '--force', branch])
 
         for tagname, base_commit, tag_commit, revision, cover in tags:
             logger.info('Archiving %s', tagname)
@@ -2638,6 +2662,23 @@ def cleanup(param: str) -> None:
     logger.info('Wrote: %s', tarpath)
 
 
+def cleanup(branches: List[str]) -> None:
+    if not branches:
+        # Show all b4-tracked branches
+        mybranches = get_prep_managed_branches(None)
+        if not len(mybranches):
+            logger.info('No b4-tracked branches found')
+            sys.exit(0)
+
+        logger.info('Please specify branch:')
+        for branch in mybranches:
+            logger.info(' %s', branch)
+        return
+
+    for branch in branches:
+        _cleanup_branch(branch)
+
+
 def show_info(param: str) -> None:
     # is param a name of the branch?
     mybranch: Optional[str] = None
@@ -3029,7 +3070,7 @@ def cmd_prep(cmdargs: argparse.Namespace) -> None:
     if cmdargs.show_info:
         return show_info(cmdargs.show_info)
 
-    if cmdargs.cleanup:
+    if cmdargs.cleanup is not None:
         return cleanup(cmdargs.cleanup)
 
     if cmdargs.format_patch:

---
base-commit: 858baa2b2eeb93ea1b0e1b57adfb77e4a126043e
change-id: 20250407-multiple-prep-cleanup-b60966ba8bf5


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH b4 v4] ez: allow cleaning multiple branches at once
  2026-02-26 10:05 [PATCH b4 v4] ez: allow cleaning multiple branches at once Antonin Godard
@ 2026-02-26 16:38 ` Konstantin Ryabitsev
  0 siblings, 0 replies; 2+ messages in thread
From: Konstantin Ryabitsev @ 2026-02-26 16:38 UTC (permalink / raw)
  To: Kernel.org Tools, Antonin Godard; +Cc: Thomas Petazzoni


On Thu, 26 Feb 2026 11:05:35 +0100, Antonin Godard wrote:
> ez: allow cleaning multiple branches at once

Applied, thanks!

[1/1] ez: allow cleaning multiple branches at once
      commit: f57ceb524a15e55daa7573751101d67ccd50a7f9

Best regards,
-- 
KR



^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-02-26 16:38 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-26 10:05 [PATCH b4 v4] ez: allow cleaning multiple branches at once Antonin Godard
2026-02-26 16:38 ` Konstantin Ryabitsev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox