Skip to content

Instantly share code, notes, and snippets.

@mecograph
Created January 16, 2024 17:34
Show Gist options
  • Save mecograph/c41da87b350b11e2ed1b8814e4070296 to your computer and use it in GitHub Desktop.
Save mecograph/c41da87b350b11e2ed1b8814e4070296 to your computer and use it in GitHub Desktop.
A script for conveniently cleaning up both local and remote branches, helping to keep the working directory organized and uncluttered
#!/bin/bash
# Define ANSI color codes
green="\033[32m"
bright_black="\033[90m"
red="\033[31m"
blue="\033[34m"
reset="\033[0m"
# Utility Functions
format_repo_name() {
local repo_name=${1//[-\/]/ }
echo $repo_name | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2));}1'
}
capitalize() {
echo "$1" | awk '{ for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2)); }1'
}
clear_lines() {
local count=$1
while [ $count -gt 0 ]; do
echo -en "\033[1A\033[2K"
((count--))
done
}
get_formatted_date() {
local branch=$1
local reflog_output=$(git reflog --date=local "$branch" | head -n 1)
local extracted_date=$(echo "$reflog_output" | awk -F'[@{ ]+' '{print $3, $4, $5, $6}')
if [[ -z "$extracted_date" || "$extracted_date" =~ ^[[:space:]]*$ ]]; then
echo "more than 90 days ago"
else
local formatted_date=$(echo "$extracted_date" | awk '{print $2, $3, $5}')
echo $(date -jf "%b %d %Y" "$formatted_date" "+%d.%m.%Y")
fi
}
handle_branch_deletion() {
local choice=$1
local branch=$2
local remote=$3
if [[ $choice == "skip" ]]; then
return
fi
read -p "you selected $choice. Are you sure? (y/n) " confirm_choice
if [[ $confirm_choice != [Yy]* ]]; then
return
fi
case $choice in
local)
git branch -d "$branch"
echo "Deleted local branch $branch"
;;
remote)
if [ -n "$remote" ]; then
git push origin --delete "$branch"
echo "Deleted remote branch $branch"
else
echo "No remote branch for $branch"
fi
;;
both)
if [ -n "$remote" ]; then
git branch -d "$branch"
git push origin --delete "$branch"
echo "Deleted both local and remote branches for $branch"
else
echo "No remote branch to delete for $branch"
fi
;;
*)
echo "Invalid choice. Skipping $branch"
;;
esac
}
select_option() {
# little helpers for terminal print control and key input
ESC=$( printf "\033")
cursor_blink_on() { printf "$ESC[?25h"; }
cursor_blink_off() { printf "$ESC[?25l"; }
cursor_to() { printf "$ESC[$1;${2:-1}H"; }
print_option() { printf " $1 "; }
print_selected() {
# ANSI Red color start
local red="\033[31m"
local blue="\033[34m"
# ANSI Color reset (to return to default after printing)
local reset="\033[0m"
printf "${blue} β–Έ ${red}$1${reset} "
}
get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
key_input() { read -s -n3 key 2>/dev/null >&2
if [[ $key = $ESC[A ]]; then echo up; fi
if [[ $key = $ESC[B ]]; then echo down; fi
if [[ $key = "" ]]; then echo enter; fi; }
# initially print empty new lines (scroll down if at bottom of screen)
for opt; do printf "\n"; done
# determine current screen position for overwriting the options
local lastrow=`get_cursor_row`
local startrow=$(($lastrow - $#))
# ensure cursor and input echoing back on upon a ctrl+c during read -s
trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
cursor_blink_off
local selected=0
while true; do
# print options by overwriting the last lines
local idx=0
for opt; do
cursor_to $(($startrow + $idx))
if [ $idx -eq $selected ]; then
print_selected "$opt"
else
print_option "$opt"
fi
((idx++))
done
# user key control
case `key_input` in
enter) break;;
up) ((selected--));
if [ $selected -lt 0 ]; then selected=$(($# - 1)); fi;;
down) ((selected++));
if [ $selected -ge $# ]; then selected=0; fi;;
esac
done
# cursor position back to normal
cursor_to $lastrow
printf "\n"
cursor_blink_on
return $selected
}
draw_screen() {
clear
echo -e "\n✨ Starting branch cleanup for $formatted_name..."
echo -e "πŸ”Ž Found $local_branch_count local branches\n"
for info in "${branch_info[@]}"; do
echo -e "$info"
done
}
# Main
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <target_git_repo_directory>"
exit 1
fi
repo_path="$1"
cd "$repo_path" || exit 1
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo "βœ‹ Not a git repo, exiting."
exit 1
fi
clear
formatted_name=$(format_repo_name "$repo_path")
formatted_name=$(capitalize "$formatted_name")
echo -e "\n✨ Starting branch cleanup for $formatted_name..."
local_branches=$(git branch -l | grep -v "master")
local_branch_count=$(echo "$local_branches" | wc -l | tr -d ' ')
local_branches_array=($local_branches)
echo -e "πŸ”Ž Found $local_branch_count local branches\n"
options=("skip" "local branch" "remote branch" "both")
current_branch=0
# Iterate over the local branches
for branch in "${local_branches_array[@]}"; do
branch_info=()
current_branch=$((current_branch + 1))
branch_name=$(echo "$branch" | sed 's/\* //')
remote=$(git for-each-ref --format='%(upstream:short)' refs/heads/"$branch_name")
behind=$(git rev-list --count "$branch_name".."$remote")
ahead=$(git rev-list --count "master..$branch_name")
formatted_date=$(get_formatted_date "$branch_name")
# Check if branch is already merged
is_merged=""
if git branch --merged | grep -q "$branch_name"; then
is_merged="${green}βœ“ merged${bright_black} | "
fi
# Check if branch exists only on this machine
remote_exists=""
if ! git show-ref --verify --quiet "refs/remotes/$remote"; then
remote_exists="${bright_black}(this machine only)${reset}"
fi
# Accumulate branch details
branch_detail="Branch ($current_branch/$local_branch_count): ${green}$branch_name${reset} ${remote_exists}"
action_detail="${is_merged}${bright_black}Created $formatted_date | behind by $behind | ahead by $ahead${reset}"
branch_info+=("$branch_detail\n$action_detail")
# Redraw the screen with updated information after each action
draw_screen
echo "Delete?"
select_option "${options[@]}"
selected_option=$?
selected_text=${options[$selected_option]}
handle_branch_deletion "$selected_text" "$branch_name" "$remote"
done
read -p "🌿 Do you want to prune? (y/n) " prune_choice
if [[ $prune_choice == [Yy]* ]]; then
git fsck
git prune
echo -e "πŸš€ ${blue}Pruning complete!${reset}"
else
echo -e "${bright_black}Skipping pruning.${reset}"
fi
echo -e "\nβœ… Script completed successfully."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment