Skip to content

Instantly share code, notes, and snippets.

@attiks
Forked from gparlakov/delete-stale-branches.sh
Last active April 16, 2021 09:16
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 attiks/31a3f9b174543650472289287f08397c to your computer and use it in GitHub Desktop.
Save attiks/31a3f9b174543650472289287f08397c to your computer and use it in GitHub Desktop.
Delete Stale Branches
#!/usr/bin/env bash
usage() {
echo "Usage: $0 [-y] [-l]" 1>&2;
echo "Uses dry run by default, specify -y to actually delete" 1>&2;
echo "Use -l to delete local branches" 1>&2;
echo "Assumes the main branch is called 'main'" 1>&2;
echo "Set main branch using git config core.main-branch 'main'" 1>&2;
exit 1;
}
DRYRUN="--dry-run"
DELETELOCAL=""
while getopts "ylh" opt; do
case $opt in
y) DRYRUN="";;
l) DELETELOCAL="y";;
h) usage;;
esac
done
# Prune local "cache" of remote branches first.
git fetch --prune origin
# Get main branch name.
MAIN=$(git config --get core.main-branch)
if [ -z "$MAIN" ]; then
MAIN="main"
fi
echo -e "Main brach is \033[1;33m$MAIN\033[0m"
MAINEXISTS=$(git ls-remote --heads origin $MAIN)
if [[ -z "$MAINEXISTS" ]]; then
echo -e "\033[0;31mMain branch not found on remote\033[0m"
usage
fi
if [ -n "$DRYRUN" ]; then
echo -e "\033[1;33mDry run is active\033[0m"
fi
# Delete merged to main branches.
mergedBranches=$(git branch -r --merged origin/$MAIN | egrep -v "(^\\*|main|master|develop|dev|staging|sandbox)" | cut -d/ -f2-)
if [ -n "${mergedBranches}" ]; then
echo -e "\033[0;32mDeleting merged branches ...\033[0m"
git push $DRYRUN --delete origin ${mergedBranches}
fi
# Get branches with last (cherry picked) commit older than 5 months.
echo -e "\033[0;32mSearching for stale branches ...\033[0m"
staleTimestamp=$(date -d "now - 5 months" +"%s")
maybeStaleTimestamp=$(date -d "now - 2 weeks" +"%s")
notMergedBranches=$(git branch -r --no-merged origin/$MAIN | grep "^ origin/" | egrep -v "(^\\*|main|master|develop|dev|staging|sandbox)")
branchesToDelete=""
branchesToReview=""
for branch in ${notMergedBranches}; do
lastCommitInfo=$(git cherry origin/$MAIN ${branch} | grep -v "^-" | cut -d" " -f2 | xargs git show --format="%H|%ct|%cr|%an" --quiet | grep -v "^$(git rev-parse HEAD)" | tail -1)
lastCommitTimestamp=$(echo "${lastCommitInfo}" | cut -d"|" -f2)
if [ -z "${lastCommitTimestamp}" ] || [ ${lastCommitTimestamp} -lt ${staleTimestamp} ]; then
branchesToDelete+=" ${branch#origin/}"
elif [ ${lastCommitTimestamp} -lt ${maybeStaleTimestamp} ]; then
branchesToReview+="${branch#origin/}|${lastCommitInfo}"$'\n'
fi
echo -n .
done
echo # for new line after dots
# Delete branches with last (cherry picked) commit older than 5 months.
if [ -n "${branchesToDelete}" ]; then
echo -e "\033[0;32mDeleting stale branches ...\033[0m"
git push $DRYRUN --delete origin ${branchesToDelete}
else
echo -e "\033[0;32mNo stale branches found.\033[0m"
fi
# List stale branches.
echo -e "\033[1;33mBranches to review (may be stale):\033[0m"
echo "${branchesToReview}" | sort -t"|" -k5 | awk -F"|" 'NF {print $5 " changed branch \"" $1 "\" in project \"'${PWD##*/}'\" " $4}'
# Delete local merged branches.
if [ -n "$DELETELOCAL" ]; then
if [ -n "$DRYRUN" ]; then
echo -e "\033[1;33mWill delete local merged branches\033[0m"
git branch --merged | egrep -v "(^\\*|main|master|develop|dev|staging|sandbox)"
else
echo -e "\033[1;33mDelete local merged branches\033[0m"
git branch --merged | egrep -v "(^\\*|main|master|develop|dev|staging|sandbox)" | xargs git branch -d #
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment