') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - Wian47/git-clean: A terminal utility to scan local Git branches, inspect their merged status & commit age, and batch-clean stale branches interactively. · GitHub
Skip to content

Repository files navigation

⚡ git-clean ⚡

Interactive Git Branch Janitor
A terminal utility to scan local Git branches, inspect their merged status & commit age, and batch-clean stale branches interactively.

Python 3.8+MIT LicenseTwitter


✨ Features

  • Auto-Merge & Squash Detection: Automatically detects directly merged branches, squash-merged branches (git cherry), and gone-upstream branches ([origin/...: gone]).
  • 🌐 Remote Branch Cleanup (--remote): Delete remote tracking branches on origin alongside local branches.
  • 🔍 Live Commit Inspector (--preview): Render dynamic commit log preview trees detailing recent commits and authors for candidate branches before deleting.
  • 🔒 Protected Branch Guards: Built-in protection for main, master, dev, staging, production, and custom patterns (--protect). Protected branches cannot be accidentally selected or deleted.
  • ↺ Branch Recovery (--undo): Automatic session history logging to .git/git_clean_history.json. Deleted branches can be restored anytime with git-clean --undo.
  • 👤 Author Filtering (--only-mine): Filter branches authored by the active git user (user.name/user.email).
  • ⚡ Remote Pruning (--prune): Auto-prune stale remote tracking refs (git fetch --prune) prior to scanning.
  • Smart Filtering: Filter by branch age (--older-than 30d), merge state (--merged-only, --unmerged-only), or non-interactive mode (-y).

⚡ Quick Start & Installation

Option 1: One-Line Installer (Recommended)

curl -fsSL https://raw.githubusercontent.com/Wian47/git-clean/main/install.sh | bash

Option 2: uv Tool / Runner (Instant & Zero Setup)

uv tool install git+https://github.com/Wian47/git-clean.git
# Or run instantly without installing:
uvx --from git+https://github.com/Wian47/git-clean.git git-clean

Option 3: pipx or pip

pipx install git+https://github.com/Wian47/git-clean.git
# Or install locally via pip:
pip install -e .

⚙️ Command Options

usage: git_clean.py [-h] [--path PATH] [--target TARGET] [--force] [--dry-run]
[--undo] [--protect [PROTECT ...]] [--older-than OLDER_THAN]
[--merged-only] [--unmerged-only] [--only-mine] [--remote]
[--prune] [--preview] [--non-interactive]
Interactive Git Branch Cleanup Utility
options:
-h, --help Show this help message and exit
--path, -p PATH Path to Git repository (default: current directory)
--target, -t TARGET Target branch to check merge status against (default: auto-detect main/master)
--force, -f Force delete unmerged branches (uses git branch -D)
--dry-run Simulate deletion without actually removing branches
--undo Restore branches deleted in the previous git-clean session
--protect [PAT ...] Additional branch patterns to protect from deletion
--older-than DURATION Only filter branches with last commit older than duration (e.g. 30d, 2w, 6m)
--merged-only Only candidate merged/squashed/gone branches
--unmerged-only Only candidate unmerged branches
--only-mine Only filter branches authored by current git user
--remote, -r Also delete remote tracking branches on origin
--prune, -P Run 'git fetch --prune' before scanning
--preview Display commit log preview inspector before prompt
--non-interactive, -y Auto-confirm branch deletion

📜 License

MIT © WianS47

About

A terminal utility to scan local Git branches, inspect their merged status & commit age, and batch-clean stale branches interactively.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages