Created
January 16, 2024 17:34
-
-
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
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 | |
# 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