* [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
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