← Recipes

阅读中文版本

Prune Stale Git Branches with a Date Cutoff

List local and remote branches older than a cutoff, confirm none are linked to open PRs, and delete them in one scripted pass with a dry-run first.

~15 mingitmaintenancebranches

Task

Remove every branch that has not been touched in 60 days and is not linked to an open pull request.

When to use this

The repo has 140 branches and nobody knows which are alive. Engineers cannot find their own branches in the list. The team wants a monthly prune that is safe and auditable.

Prerequisites

  • git 2.40 or later
  • A way to list open pull requests by branch (gh CLI)
  • A dry-run habit before destructive commands

Steps

1. List every branch with its last commit date in a machine-readable format.

git for-each-ref --format="%(refname:short) %(committerdate:short)" refs/heads refs/remotes

Expected output: A list of branch names with dates, one per line, covering both local and remote refs.

2. Filter to branches older than 60 days and not linked to an open PR.

const open = await gh.prsByBranch();
const stale = branches.filter(b => daysSince(b.date) > 60 && !open.has(b.name));

Expected output: A list of branch names that are both stale and unlinked, safe to delete.

3. Dry-run the deletion and print every command, then run for real once the list is confirmed.

node tools/prune-branches.mjs --dry-run && node tools/prune-branches.mjs

Expected output: The dry run prints git branch -D and git push origin --delete lines; the real run executes them and reports the count.

Troubleshooting

Problem

A branch is the default branch of an open PR the script missed

Solution

Query the PR API by head ref, not by name, and abort the prune if any match; a missed PR is a sign the filter is wrong.

Problem

A branch is referenced by a tag or a release

Solution

Check git for-each-ref refs/tags before deleting, and skip any branch that is the target of a tag.

Limitations

  • The prune is destructive; a deleted remote branch needs a force push to restore, so keep the dry run as the default.
  • A branch older than the cutoff may still be in active review; always cross-check with the PR API.

List, filter, delete

List every branch with its last commit date, filter to those older than 60 days, cross-check against open pull requests, and delete the rest. Always dry-run first.

git for-each-ref --format="%(refname:short) %(committerdate:short)" refs/heads refs/remotes

Cross-check with PRs

A stale branch may still be the head of an open PR. Query the PR API by head ref and skip any branch that matches. A branch referenced by a tag or a release is also off limits.

node tools/prune-branches.mjs --dry-run

Expected outcome

The dry run prints every deletion command, the real run executes them and reports the count, and no branch linked to an open PR or a tag is removed. Run the prune monthly so the branch list stays navigable.