Skip to content

Instantly share code, notes, and snippets.

@non7top
Forked from jdhoek/git-branch-cleaner
Created May 10, 2023 18:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save non7top/0a0e226badb9bc3a7c8c31bb1c528149 to your computer and use it in GitHub Desktop.
Save non7top/0a0e226badb9bc3a7c8c31bb1c528149 to your computer and use it in GitHub Desktop.
Bash script for cleaning up old git branches from local and remote repositories
#!/bin/bash
ME=$(basename $0)
function showHelp {
cat << EOF
Clean-up old git branches
Usage: $ME local show-last [AGE_IN_WEEKS]
$ME remote show-last REMOTE [AGE_IN_WEEKS]
$ME [-d] local remove-old AGE_IN_WEEKS
$ME [-d] remote remove-old REMOTE AGE_IN_WEEKS
$ME -h
Options:
AGE_IN_WEEKS Only include branches this many weeks old, or older
REMOTE Local alias for the remote repository
-h Show this help message and exit
-d Dry-run. Show what will be done if this flag is omitted
Examples:
Show who the last committer of each branch was. Here 'origin' is the
local alias for the remote repository. For a list of remote repositories
aliased to your local repository, run `git remote -v`:
$ME remote origin show-last
Note that this shows the committer, not the author. These can differ if
someone cherry-picked or merged a commit.
Remove local branch references for braches older than a year:
$ME local remove-old 52
Show the remote branches that will be removed with the dry-run flag:
$ME remote -d remove-old origin 80
Actually remove remote branches older than 80 weeks:
$ME remote remove-old origin 80
EOF
}
function showTooFewArguments {
cat << EOF
ERROR:
Arguments missing. Which operation do you want to perform?
EOF
showHelp
}
function ageInWeeks {
local NOW=$(date +%s)
local SECONDS=$(( $NOW - $1 ))
local DAYS=$(( $SECONDS / 604800 ))
eval "$2"="$DAYS"
}
DRY_RUN=false
# Parse the short flags.
while getopts "h?d" opt; do
case $opt in
"h")
showHelp
exit
;;
"?")
showHelp
exit
;;
"d")
DRY_RUN=true
;;
esac
done
# Done with the short flags, shift over to the remaining arguments.
shift $(($OPTIND - 1))
# Parse the command line arguments.
if [ $# -eq 0 ]; then
# Called without arguments, show help and exit.
showHelp
exit
fi
# Is the current dir a git repo?
git status > /dev/null || exit
TARGET=$1
ACTION=$2
MIN_AGE=
REMOTE=
if [ "$TARGET" == "local" ]; then
MIN_AGE=$3
if [ $# -lt 2 ]; then
showTooFewArguments
exit 1;
fi
echo "Working on local branches only."
elif [ "$TARGET" == "remote" ]; then
REMOTE=$3
MIN_AGE=$4
if [ $# -lt 3 ]; then
showTooFewArguments
exit 1;
fi
echo "Working on remote branches only."
else
showHelp
exit 1
fi
echo
echo "Last committer of each branch:"
echo
if [ -z "$MIN_AGE" ]; then
MIN_AGE=0
fi
# The following lists the branches. This is the same for show-last and remove-old.
BRANCHES=
if [ "$TARGET" == "local" ]; then
BRANCHES=$(git branch --no-color --no-abbrev -v)
else
BRANCHES=$(git ls-remote -h $REMOTE)
fi
BRANCH_LIST=''
BRANCH_NAMES=();
while read -r BRANCH
do
NAME=
SHA=
if [ "$TARGET" == "local" ]; then
NAME=$(echo "$BRANCH" | sed -e 's/* / /' | awk '{ print $1 }')
else
NAME=$(echo "$BRANCH" | sed -e 's^refs/heads/^^' | awk '{ print $2 }')
fi
if [[ $NAME =~ .*/HEAD ]]; then
continue
fi
SHA=
if [ "$TARGET" == "local" ]; then
SHA=$(echo "$BRANCH" | sed -e 's/* / /' | awk '{ print $2 }')
else
SHA=$(echo "$BRANCH" | awk '{ print $1 }')
fi
AGE=""
ageInWeeks $(git log -n 1 --format=format:"%ct" $SHA) AGE
if [ $AGE -lt $MIN_AGE ]; then
continue
fi
BRANCH_NAMES+=("$NAME")
BRANCH_LINE="$(printf '%-60s %4s %-12s' "${NAME}:" $AGE 'weeks') $(git log -n 1 --format=format:"%cN <%cE>" $SHA)"
BRANCH_LIST+="${BRANCH_LINE}\n"
done <<< "$BRANCHES"
if [ ${#BRANCH_NAMES[@]} -eq 0 ]; then
echo "No branches matching these criterea found."
exit
fi
# Sort by age.
printf "$BRANCH_LIST" | sort -k2 -n
if [ "$ACTION" == "remove-old" ]; then
echo
if [ $DRY_RUN == true ]; then
if [ "$TARGET" == "local" ]; then
echo "These local branches will be removed if you leave out the -d flag."
elif [ "$TARGET" == "remote" ]; then
echo "These remote branches will be removed from the remote repo if you leave out the -d flag."
fi
else
# Read one character from console.
read -p "Remove these branches older than $MIN_AGE weeks. Are you sure? [y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo "Removing branches..."
for BNAME in "${BRANCH_NAMES[@]}"; do
echo "${BNAME}..."
if [ "$TARGET" == "local" ]; then
git branch -D $BNAME
elif [ "$TARGET" == "remote" ]; then
git push $REMOTE :$BNAME
fi
done
else
echo "Aborting."
fi
fi
echo
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment