lumberjack-cli
v0.1.1
Published
A minimal CLI tool for cleaning up stale git branches and worktrees
Downloads
537
Maintainers
Readme
🪓 Lumberjack
A minimal CLI tool for cleaning up stale git branches and worktrees.
npm install -g lumberjack-clichopWhy?
If you use git worktrees or work on a team with squash-merge workflows, you've probably got a graveyard of stale local branches and worktrees. Lumberjack helps you clean them up safely.
Quick Start
# Preview what would be deleted (always safe)
chop --gone --dry-run
# Clean up branches where the remote has been deleted
chop --branch --gone
# Clean up worktrees where the remote branch was deleted
chop --tree --gone
# Clean up both branches and worktrees
chop --goneUsage
chop [options]At least one detection flag is required. When run without --branch or --tree, both branches and worktrees are processed.
Target Flags
Specify what to clean up. If neither is provided, both are targeted.
| Flag | Short | Description |
|------|-------|-------------|
| --branch | -b | Target local branches. |
| --tree | -t | Target git worktrees. |
chop --branch --gone # Only branches
chop --tree --gone # Only worktrees
chop --branch --tree --gone # Both (explicit)
chop --gone # Both (implicit)Detection Flags
Specify the conditions for cleanup. Multiple flags use OR logic - items matching ANY condition will be flagged.
| Flag | Description | Branches | Worktrees |
|------|-------------|:--------:|:---------:|
| --gone | Upstream remote has been deleted. Best for squash-merge workflows where branches are auto-deleted. | ✅ | ✅ |
| --merged[=<branch>] | Has been merged into the specified branch, configured mergeBase, or current branch. | ✅ | ✅ |
| --squashed[=<branch>] | Changes are present in the target branch (tree comparison). Works for squash-merge even when remote still exists. | ✅ | ✅ |
| --stale[=<days>] | No commits in the specified number of days. Default: 30 (or config value). | ✅ | ✅ |
| --all | Shorthand for --gone --merged --squashed --stale. | ✅ | ✅ |
Examples
# Branches where remote is gone
chop --branch --gone
# Worktrees where remote branch was deleted
chop --tree --gone
# Branches with no commits in 60 days
chop --branch --stale=60
# Branches merged into main (explicit)
chop --branch --merged=main
# Branches whose changes are in main (works for squash-merge)
chop --branch --squashed=main
# Branches that are gone OR stale (OR logic)
chop --branch --gone --stale=30
# Both branches and worktrees, gone OR merged
chop --gone --merged
# Kitchen sink - branches and worktrees matching any condition
chop --allGlobal Options
| Flag | Short | Description |
|------|-------|-------------|
| --dry-run | -d | Preview what would be deleted without actually deleting anything. |
| --force | -f | Skip confirmation prompts. Use with caution. |
| --protect <patterns> | -p | Comma-separated branch patterns to never delete (in addition to defaults). |
| --no-protect | | Disable default protected branches. Not recommended. |
| --no-fetch | | Skip the git fetch --prune step. Useful if you've just fetched. |
| --keep-branch | | When removing worktrees, don't delete the associated branch. |
| --json | | Output results as JSON (useful for scripting). |
| --verbose | -v | Show detailed information about why each item was flagged. |
| --help | -h | Show help. |
| --version | -V | Show version. |
Configuration
Lumberjack can be configured via a .lumberjackrc file in your home directory or project root, or via a lumberjack key in package.json.
Config File
// ~/.lumberjackrc or ./.lumberjackrc
{
"protect": ["main", "master", "develop", "staging", "production"],
"stale": 30,
"fetch": true,
"mergeBase": "main",
"defaultCommand": "--branch --gone"
}Package.json
{
"lumberjack": {
"protect": ["main", "master", "develop"],
"stale": 30
}
}Config Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| protect | string[] | ["main", "master", "develop"] | Branch patterns to never delete. Supports glob patterns. |
| stale | number | 30 | Default number of days for --stale flag. |
| fetch | boolean | true | Run git fetch --prune before detection. Set to false to always skip. |
| mergeBase | string | null | Default branch for --merged check. If not set, uses current branch. |
| defaultCommand | string | null | If set, chop without arguments runs this command. |
Config Precedence
- CLI flags (highest)
- Project
.lumberjackrc - Home directory
~/.lumberjackrc package.jsonlumberjack key- Defaults (lowest)
Dry Run Output
$ chop --branch --gone --dry-run
DRY RUN - no changes will be made
Would delete 3 branches:
feat/auth-flow
└─ remote gone (upstream: origin/feat/auth-flow)
feat/user-settings
└─ remote gone (upstream: origin/feat/user-settings)
hotfix/typo
└─ remote gone (upstream: origin/hotfix/typo)
Run without --dry-run to delete.JSON Output
For scripting and automation:
$ chop --branch --gone --json --dry-run{
"dryRun": true,
"branches": [
{
"name": "feat/auth-flow",
"reason": "gone",
"details": {
"upstream": "origin/feat/auth-flow",
"lastCommit": "2025-01-15T10:30:00Z",
"lastCommitAge": 12
}
},
{
"name": "feat/user-settings",
"reason": "gone",
"details": {
"upstream": "origin/feat/user-settings",
"lastCommit": "2025-01-24T14:20:00Z",
"lastCommitAge": 3
}
}
],
"worktrees": [],
"summary": {
"branchesFound": 2,
"branchesDeleted": 0,
"worktreesFound": 0,
"worktreesDeleted": 0,
"protected": 0,
"skipped": 0
}
}Protected Branches
By default, Lumberjack will never delete these branches:
mainmasterdevelop
You can add to this list via config or CLI:
# Add staging and production to protected list
chop --branch --gone --protect staging,production
# Protect branches matching a pattern
chop --branch --gone --protect "release/*"To see which branches are protected:
chop --verboseCommon Workflows
Squash-Merge Cleanup
If your team uses squash-merge and auto-deletes branches on GitHub/GitLab:
# Best option - relies on remote branch being deleted
chop --branch --goneIf your workflow doesn't auto-delete remote branches:
# Detects if changes are in main regardless of merge strategy
chop --branch --squashed=mainWeekly Maintenance
# Clean up everything that's clearly safe to remove
chop --all --dry-run
# If it looks good
chop --allBefore Starting a New Feature
# Quick cleanup of stale worktrees
chop --tree --gone --staleCI/Automation
# JSON output for logging
chop --branch --gone --force --json >> cleanup.logConfigure a Default
If you always run the same cleanup, set it as default:
// .lumberjackrc
{
"defaultCommand": "--branch --gone"
}Now chop with no arguments runs chop --branch --gone.
How Detection Works
--gone Detection
- Runs
git fetch --prune(unless--no-fetch) - Runs
git branch -vvto list branches with tracking info - Identifies branches where upstream shows as
[origin/branch: gone]
This is the most reliable method for squash-merge workflows.
--merged Detection
- Determines the base branch:
- If
--merged=<branch>is provided, uses that branch - Else if
mergeBaseis set in config, uses that - Else uses the current branch
- If
- Runs
git branch --merged <base> - Returns all branches whose commits are reachable from the base branch
Note: This doesn't work for squash-merged branches since the commits are rewritten. Use --gone or --squashed for squash-merge workflows.
# Check against explicit branch
chop --branch --merged=main
# Check against configured mergeBase (or current if not set)
chop --branch --merged--squashed Detection
- Determines the base branch:
- If
--squashed=<branch>is provided, uses that branch - Else if
mergeBaseis set in config, uses that - Else uses the current branch
- If
- For each branch, simulates a merge using
git merge-tree --write-tree - Compares the resulting tree hash with the base branch's current tree
- If the trees are identical, the branch adds no new changes - it's already in base
This approach correctly detects squash-merged branches even when:
- The branch had multiple commits that were squashed into one
- The base branch has moved forward since the squash merge
Note: Requires Git 2.38+ for the merge-tree --write-tree feature.
# Check if branch changes are already in main
chop --branch --squashed=main
# Check against configured mergeBase (or current if not set)
chop --branch --squashed--stale Detection
- Determines the threshold:
- If
--stale=<days>is provided, uses that value - Else if
staleis set in config, uses that - Else uses 30 days
- If
- For each branch, gets the date of the last commit
- Flags branches with no commits within the threshold
# Check for branches stale for 60+ days
chop --branch --stale=60
# Check against configured stale threshold (or 30 if not set)
chop --branch --staleComparison with Alternatives
| Tool | Branches | Worktrees | Squash Detection |
|------|----------|-----------|------------------|
| Lumberjack | ✅ | ✅ | ✅ |
| git branch -d | ✅ | ❌ | ❌ |
| git worktree prune | ❌ | ✅ (partial) | ❌ |
| git-sweep | ✅ | ❌ | ❌ |
| git-trim | ✅ | ❌ | ❌ |
Requirements
- Node.js 18+
- Git 2.20+ (Git 2.38+ required for
--squasheddetection)
License
MIT
Contributing
Issues and PRs welcome at github.com/jameshole/lumberjack.
