[PATCH b4 v2] prep: add --cleanup-older-than option to clean up stale branches

Breno Leitao <[email protected]>
Newsgroups org.kernel.linux.tools
Message-ID <[email protected]>
B4 is so useful that I have too many branches now, create a way to
easily remove old branches.

Add a new --cleanup-older-than DAYS option that archives and removes
prep-tracked branches whose latest commit is older than the specified
number of days. Each matching branch goes through the normal per-branch
confirmation prompt.

Signed-off-by: Breno Leitao <[email protected]>
---
Changes in v2:
- Rebased onto master; option moved into exclusive group, help
  clarified.
- Age no longer truncated to whole days
- Documented in prep.rst.
- Link to v1: https://patch.msgid.link/[email protected]
---
 docs/contributor/prep.rst | 18 ++++++++++++++++++
 src/b4/command.py         |  7 +++++++
 src/b4/ez.py              | 43 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+)

diff --git a/docs/contributor/prep.rst b/docs/contributor/prep.rst
index 20b34d7..bc470d4 100644
--- a/docs/contributor/prep.rst
+++ b/docs/contributor/prep.rst
@@ -354,6 +354,16 @@ patches, cover letters, and tracking information from your series.
 Afterwards, b4 deletes the branch(es) and all related tags from your local
 repository.
 
+If prep-managed branches have piled up over time, you can select them by
+age instead of by name::
+
+    b4 prep --cleanup-older-than 180
+
+This finds every prep-managed branch whose latest commit is older than the
+given number of days, skipping the one you currently have checked out. Each
+matching branch is then archived and deleted through the same confirmation
+prompt as above, so nothing goes away without your say-so.
+
 .. _prep_flags:
 
 Prep command flags
@@ -522,6 +532,14 @@ modifying defaults for some of these flags.
 
   .. versionadded:: v0.13
 
+``--cleanup-older-than DAYS``
+  Same as ``--cleanup``, but picks the branches by age rather than by
+  name: every prep-managed branch whose latest commit is older than
+  DAYS days is archived and deleted, one confirmation prompt at a time.
+  The currently checked out branch is always left alone.
+
+  .. versionadded:: v0.16
+
 ``--claim [BRANCH]``
   Re-stamps the cover letter and every patch on a prep branch under
   your current git identity (defaults to the current branch). B4
diff --git a/src/b4/command.py b/src/b4/command.py
index b8f7646..8a8c96f 100644
--- a/src/b4/command.py
+++ b/src/b4/command.py
@@ -1010,6 +1010,13 @@ def setup_parser() -> argparse.ArgumentParser:
         nargs='*',
         help='Archive and remove prep-tracked branches and all associated sent/ tags',
     )
+    spp_g.add_argument(
+        '--cleanup-older-than',
+        metavar='DAYS',
+        type=int,
+        default=None,
+        help='Archive and remove prep-tracked branches older than DAYS days',
+    )
     spp_g.add_argument(
         '--claim',
         metavar='BRANCH',
diff --git a/src/b4/ez.py b/src/b4/ez.py
index ac3949a..483ebac 100644
--- a/src/b4/ez.py
+++ b/src/b4/ez.py
@@ -3625,6 +3625,46 @@ def _cleanup_branch(branch: str) -> None:
     logger.info('Wrote: %s', tarpath)
 
 
+def _get_branch_latest_commit_age_days(branch: str) -> Optional[float]:
+    """Return the age in days of the latest commit on *branch*, or None on error."""
+    lines = b4.git_get_command_lines(None, ['log', '-1', '--format=%ct', branch])
+    if not lines:
+        return None
+    try:
+        commit_ts = int(lines[0])
+    except ValueError:
+        return None
+    return (time.time() - commit_ts) / 86400
+
+
+def cleanup_older_than(older_than_days: int) -> None:
+    """Find prep-tracked branches whose latest commit exceeds *older_than_days* and clean them up.
+
+    The currently checked-out branch is always skipped.  Each matching
+    branch is passed to ``_cleanup_branch``, which prompts the user for
+    confirmation before archiving and deleting it.
+    """
+    mybranches = get_prep_managed_branches(None)
+    if not mybranches:
+        logger.info('No b4-tracked branches found')
+        sys.exit(0)
+
+    curbranch = b4.git_get_current_branch()
+    old_branches = []
+    for branch in mybranches:
+        if branch == curbranch:
+            logger.debug('Skipping currently checked out branch: %s', branch)
+            continue
+        age = _get_branch_latest_commit_age_days(branch)
+        if age is not None and age > older_than_days:
+            old_branches.append(branch)
+    if not old_branches:
+        logger.info('No branches older than %d days found', older_than_days)
+        return
+    for branch in old_branches:
+        _cleanup_branch(branch)
+
+
 def cleanup(branches: List[str]) -> None:
     if not branches:
         # Show all b4-tracked branches
@@ -4076,6 +4116,9 @@ def cmd_prep(cmdargs: argparse.Namespace) -> None:
     if cmdargs.show_info:
         return show_info(cmdargs.show_info)
 
+    if cmdargs.cleanup_older_than is not None:
+        return cleanup_older_than(cmdargs.cleanup_older_than)
+
     if cmdargs.cleanup is not None:
         return cleanup(cmdargs.cleanup)
 

---
base-commit: 62c5a3c2f841dd6533a9009e6ab4fee104bf3b04
change-id: 20260323-cleanup-4dea45140971

Best regards,
--  
Breno Leitao <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.