git-sync
Easily synchronize your local branches and worktrees.
A command-line tool that detects branches merged into your main branch(es) and offers to delete them -- both locally and on configured remotes. Also handles orphaned worktree cleanup.
Features
- Delete local and remote branches that have been merged
- Worktree cleanup: removes worktrees for deleted branches and orphaned worktrees
- Glob pattern support for protected branches (e.g.
release/*) - Per-branch protection via git config (
branch.<name>.sync-protected) - Multiple merge detection strategies (fast merge and rebase-aware via
git cherry) - Optional worktrunk integration for worktree removal (triggers pre/post-remove hooks)
- Interactive setup wizard on first run
- Configuration stored in git config (
[sync]section) - Safety-first:
--force-with-leasefor remote deletions
Installation
The crate is named git-synchronizer but installs a binary called git-sync,
making it available as the git sync subcommand.
Usage
# Interactive mode (prompts for confirmation at each step)
# Auto-confirm everything
# Dry run (show what would be done)
# Show git commands being executed
# Skip fetching/pruning
# Only clean local or remote branches
# Skip worktree cleanup
# Use worktrunk for worktree removal (triggers pre/post-remove hooks)
# Disable worktrunk even if configured or detected
Configuration management
# Display current configuration
# Re-run the interactive setup wizard
# Add/remove protected branch patterns
# Protect/unprotect individual branches
# Add/remove remotes to operate on
Configuration
Configuration is stored in the [sync] section of your git config
(local or global):
[sync]
protected = main
protected = master
protected = release/*
remote = origin
worktrunk = true
| Key | Type | Description |
|---|---|---|
protected |
multi-value | Glob patterns for branches that should never be deleted |
remote |
multi-value | Remotes to delete branches from (omit for all remotes) |
worktrunk |
bool | Enable/disable worktrunk for worktree removal. When omitted, auto-detects |
Individual branches can also be protected via the standard [branch]
config namespace:
[branch "develop"]
sync-protected = true
A per-branch protected branch is excluded from deletion candidates and also serves as a merge target (branches merged into it are flagged for cleanup).
First run
On first run (when no [sync] config section exists), an interactive
setup wizard runs automatically:
- Auto-detects local branches and pre-selects well-known ones (
main,master) - Asks for additional protected patterns (e.g.
release/*) - Lists available remotes and asks which ones to operate on
- If worktrunk (
wt) is detected on$PATH, asks whether to use it for worktree removal
How it works
The cleanup runs in four sequential phases, each of which can be skipped via CLI flags:
-
Fetch & prune remotes -- runs
git remote update --pruneon configured (or all) remotes to sync remote-tracking branches. Skipped with--no-fetch. -
Delete merged local branches -- identifies branches merged into any protected branch (both glob-pattern and per-branch protected) using two complementary strategies:
- Standard detection:
git branch --merged <target>catches fast-forward and regular merges. - Rebase-aware detection:
git cherry <target> <branch>catches squash-merged and rebased branches by checking whether every commit has already been applied upstream.
Per-branch protected branches also serve as merge targets, so branches merged into them are detected as candidates too. The user selects which branches to delete. Associated worktrees are removed first, then branches are deleted with
git branch -d. Skipped with--remote-only. - Standard detection:
-
Delete merged remote branches -- for each configured remote, identifies merged remote-tracking branches with
git branch -r --merged <target>. The user selects which to delete, and they are removed withgit push --delete --force-with-leasefor safety. Skipped with--local-only. -
Clean orphan worktrees -- finds worktrees whose branch no longer exists locally and offers to remove them. When worktrunk is enabled (via
--worktrunkflag,sync.worktrunkconfig, or auto-detection), removal is delegated towt removeso that pre/post-remove hooks are triggered. Otherwise falls back togit worktree remove. Skipped with--no-worktrees.
flowchart TD
Start([git sync]) --> LoadConfig[Load configuration]
LoadConfig --> FirstRun{First run?}
FirstRun -- Yes --> Setup[Interactive setup wizard]
Setup --> FetchCheck
FirstRun -- No --> FetchCheck
FetchCheck{--no-fetch?}
FetchCheck -- No --> Fetch[Fetch & prune remotes]
Fetch --> LocalCheck
FetchCheck -- Yes --> LocalCheck
LocalCheck{--remote-only?}
LocalCheck -- No --> FindLocal[Find merged local branches]
FindLocal --> Merged[Standard detection\ngit branch --merged]
FindLocal --> Cherry[Rebase-aware detection\ngit cherry]
Merged --> SelectLocal[User selects branches]
Cherry --> SelectLocal
SelectLocal --> RemoveWT1[Remove associated worktrees]
RemoveWT1 --> DeleteLocal[Delete local branches\ngit branch -d]
DeleteLocal --> RemoteCheck
LocalCheck -- Yes --> RemoteCheck
RemoteCheck{--local-only?}
RemoteCheck -- No --> FindRemote[For each remote:\nfind merged remote branches]
FindRemote --> SelectRemote[User selects branches]
SelectRemote --> DeleteRemote[Delete remote branches\ngit push --delete --force-with-lease]
DeleteRemote --> WTCheck
RemoteCheck -- Yes --> WTCheck
WTCheck{--no-worktrees?}
WTCheck -- No --> FindOrphan[Find orphan worktrees]
FindOrphan --> ConfirmWT[User confirms removal]
ConfirmWT --> UseWT{Worktrunk\nenabled?}
UseWT -- Yes --> WTRemove[Remove worktrees\nwt remove]
UseWT -- No --> RemoveOrphan[Remove worktrees\ngit worktree remove]
WTRemove --> Done
RemoveOrphan --> Done
WTCheck -- Yes --> Done
Done([Done])
Development
This project uses mise for task management:
License
MIT