Created
July 28, 2025 15:36
-
-
Save s1moe2/62668353913c283dc04f508a206ba4b4 to your computer and use it in GitHub Desktop.
Stale branch cleanup script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Script to delete merged remote branches except those in the no-delete list | |
# Usage: | |
# ./delete_merged_git.sh [target-branch] [--delete] [--old-days N] | |
# Default target branch is "develop" | |
# Use --delete to actually delete branches; otherwise it's a dry run | |
# Use --old-days N to also delete branches older than N days | |
TARGET_BRANCH="develop" | |
PERFORM_DELETE=false | |
OLD_DAYS="" | |
for arg in "$@"; do | |
if [[ "$arg" == "--delete" ]]; then | |
PERFORM_DELETE=true | |
elif [[ "$arg" == "--old-days" ]]; then | |
# Get the next argument as the number of days | |
shift | |
OLD_DAYS="$1" | |
elif [[ "$arg" != --* ]] && [[ "$arg" != "$OLD_DAYS" ]]; then | |
TARGET_BRANCH="$arg" | |
fi | |
done | |
# Branches to never delete (add your protected branches here) | |
NO_DELETE_LIST=( | |
"main" | |
"master" | |
"develop" | |
"staging" | |
"production" | |
"release" | |
) | |
# Function to check if branch is older than N days | |
is_branch_old() { | |
local branch="$1" | |
local days="$2" | |
if [ -z "$days" ]; then | |
return 1 # Don't consider old if no days specified | |
fi | |
local last_commit_date=$(git log -1 --format="%ct" "origin/$branch" 2>/dev/null) | |
if [ -z "$last_commit_date" ]; then | |
return 1 # Can't determine age | |
fi | |
local cutoff_date=$(date -d "$days days ago" +%s 2>/dev/null || date -v-${days}d +%s 2>/dev/null) | |
[ "$last_commit_date" -lt "$cutoff_date" ] | |
} | |
echo "๐ Finding branches to delete..." | |
if [ -n "$OLD_DAYS" ]; then | |
echo " - Merged branches in origin/$TARGET_BRANCH" | |
echo " - OR branches older than $OLD_DAYS days" | |
else | |
echo " - Merged branches in origin/$TARGET_BRANCH" | |
fi | |
# Fetch latest remote refs | |
git fetch origin --prune | |
# Get list of merged remote branches, excluding HEAD | |
MERGED_BRANCHES=$(git branch -r --merged origin/$TARGET_BRANCH | grep -v 'origin/HEAD' | sed 's/origin\///' | xargs) | |
# Get all remote branches if we're checking for old branches | |
if [ -n "$OLD_DAYS" ]; then | |
ALL_BRANCHES=$(git branch -r | grep -v 'origin/HEAD' | sed 's/origin\///' | sed 's/^ *//' | xargs) | |
fi | |
echo "" | |
echo "๐๏ธ Branches to delete:" | |
BRANCHES_TO_DELETE=() | |
PROCESSED_BRANCHES=() | |
# Process merged branches | |
for branch in $MERGED_BRANCHES; do | |
# Skip if branch is in no-delete list | |
if [[ " ${NO_DELETE_LIST[@]} " =~ " ${branch} " ]]; then | |
continue | |
fi | |
BRANCHES_TO_DELETE+=("$branch") | |
PROCESSED_BRANCHES+=("$branch") | |
echo " - $branch (merged into $TARGET_BRANCH)" | |
done | |
# Process old branches if --old-days specified | |
if [ -n "$OLD_DAYS" ] && [ -n "$ALL_BRANCHES" ]; then | |
for branch in $ALL_BRANCHES; do | |
# Skip if already processed as merged | |
if [[ " ${PROCESSED_BRANCHES[@]} " =~ " ${branch} " ]]; then | |
continue | |
fi | |
# Skip if branch is in no-delete list | |
if [[ " ${NO_DELETE_LIST[@]} " =~ " ${branch} " ]]; then | |
continue | |
fi | |
# Check if old | |
if is_branch_old "$branch" "$OLD_DAYS"; then | |
BRANCHES_TO_DELETE+=("$branch") | |
last_commit=$(git log -1 --format="%ar" "origin/$branch" 2>/dev/null || echo "unknown") | |
echo " - $branch (last commit: $last_commit)" | |
fi | |
done | |
fi | |
if [ ${#BRANCHES_TO_DELETE[@]} -eq 0 ]; then | |
echo "โ No branches to delete (all are protected or don't meet criteria)" | |
exit 0 | |
fi | |
if $PERFORM_DELETE; then | |
read -p "โ ๏ธ Really delete these ${#BRANCHES_TO_DELETE[@]} branches? (y/N): " -n 1 -r | |
echo | |
if [[ $REPLY =~ ^[Yy]$ ]]; then | |
echo "๐ฎ Deleting branches..." | |
for branch in "${BRANCHES_TO_DELETE[@]}"; do | |
echo "Deleting origin/$branch..." | |
git push origin --delete "$branch" | |
done | |
echo "" | |
echo "โ Deleted ${#BRANCHES_TO_DELETE[@]} branches" | |
echo "โ Done!" | |
else | |
echo "โ Cancelled" | |
fi | |
else | |
read -p "โ ๏ธ[DRY RUN] Really delete these ${#BRANCHES_TO_DELETE[@]} branches? (y/N): " -n 1 -r | |
echo | |
for branch in "${BRANCHES_TO_DELETE[@]}"; do | |
echo "[DRY RUN] Deleting origin/$branch..." | |
done | |
echo "๐ก Dry run: Use '--delete' to actually remove these branches." | |
echo "๐งช Nothing was deleted." | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment