#!/usr/bin/env bash # clean-gone: List local branches whose remote tracking branch is gone. # Outputs one branch name per line, or nothing if none found. set -euo pipefail # Ensure we have current remote state git fetch --prune 2>/dev/null # Find branches marked [gone] in tracking info. # `git branch -vv` output format: # * main abc1234 [origin/main] commit msg # + feature-x def5678 [origin/feature-x: gone] commit msg # old-branch 789abcd [origin/old-branch: gone] commit msg # # The leading column can be: ' ' (normal), '*' (current), '+' (worktree). # We match lines containing ": gone]" to find branches whose remote is deleted. gone_branches=() while IFS= read -r line; do # Skip the currently checked-out branch (marked with '*'). # git branch -D cannot delete the active branch, and attempting it # would halt cleanup before other stale branches are processed. if [[ "$line" =~ ^\* ]]; then continue fi # Strip the leading marker character(s) and whitespace # The branch name is the first non-whitespace token after the marker branch_name=$(echo "$line" | sed 's/^[+* ]*//' | awk '{print $1}') # Validate: skip empty, skip if it looks like a hash or flag, skip HEAD if [[ -z "$branch_name" ]] || [[ "$branch_name" =~ ^[0-9a-f]{7,}$ ]] || [[ "$branch_name" == "HEAD" ]]; then continue fi gone_branches+=("$branch_name") done < <(git branch -vv 2>/dev/null | grep ': gone]') if [[ ${#gone_branches[@]} -eq 0 ]]; then echo "__NONE__" exit 0 fi for branch in "${gone_branches[@]}"; do echo "$branch" done