A few months back I was searching for a way to audit stale branches and ran across a really useful script on Stack Overflow. The script lists the relative date of the last commit on each branch and how far ahead or behind its commits are from the main branch. I’ve been tinkering with and adding some small enhancements to the script since then and wanted to share the results.
Why Use This?
I’ve been using this for various tasks, but the most common one is to identify stale old branches that are great candidates for deletion. When I run git branch --all
, I don’t want to parse a cluttered branch list littered with branches from yesteryear that will likely never be merged or used again. Now this doesn’t really apply to long supported or archived release branches, such as those exhibited in the ruby
repository example below; There are good reasons to keep those around, however ancient feature branches can often just be deleted. When feature branches are months to years old and far behind the main branch commit history, it’s highly improbably that the work will ever be merged in. I tend towards deleting these branches to reduce your cognitive load and focus your attention.
Summary of Changes
- fixed a bug where, at least on Mac OS, the
column
utility wasn’t properly aligning the non-colored headers with the rows of information. I believecolumn
was processing the color codes as part of the width of the text. To fix this, I added color codes to the header column title strings. - adjusted the column colors to my liking
- added colors to the “ahead” and “behind” column as well as some markers before the numerical values
- script will attempt to automatically detect the default branch for the git repository
- added option arguments for easier customization
- added option to look at either local or remote branches on origin
- added option to reverse the order
Modified Script
I've included the script below. There's also a GitHub Gist available online.
#!/bin/bash
# function definition to print out help text
showHelp() {
cat << EOF
Usage: ./recent_branches -b <target-branch> -n <number-of-branches> [-hrl]
Analyzes the most recent (or oldest branches) in a repository, as well as how
many commits ahead or behind it is from a target branch
-h Display help
-n Number of branches to output (default: 10)
-b Target branch to compare against (default is default branch)
-r Reverse sort order (default: most recent first)
-l Use local branches instead of remote origin
EOF
}
# get the default branch
defaultbranch=$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')
# set some defaults...
refbranch=$defaultbranch
count=10
sort="-committerdate"
pattern="refs/remotes/origin/"
strip=3
# parse the option arguments
while getopts ":hn::b::orl" option; do
case $option in
h) # display Help
showHelp
exit
;;
n) # enter a number
count=$OPTARG
;;
b) # enter a branch
refbranch=$OPTARG
;;
r) # reverse
sort="committerdate"
;;
l) # use local branches
pattern="refs/heads/"
strip=2
;;
*)
echo "Internal error!"
exit 1
;;
esac
done
# string that will be interpolated by the git for-each-ref below
# this forms a large fragment of the final output
format="%(refname:short)|"
format+="%(color:yellow)%(refname:lstrip=$strip)|"
format+="%(color:cyan)%(committerdate:relative)|"
format+="%(color:magenta)%(authorname)|"
format+="%(color:blue)%(subject)%(color:reset)"
# a header string
header="\033[37mBEHIND|"
header+="\033[37mAHEAD|"
header+="\033[37mBRANCH|"
header+="\033[37mLASTCOMMIT|"
header+="\033[37mAUTHOR|"
header+="\033[37mMESSAGE"
echo -e "━━━━━━━━━━━━━━━━━━"
echo -e "COMPARING AGAINST: \033[31m$refbranch\033[0m"
echo -e "━━━━━━━━━━━━━━━━━━"
# loop over and process the output information on each git ref (branch)
# THEN append the headers
# THEN format the lines as columns
git for-each-ref \
--sort=${sort} \
${pattern} \
--format="${format}" \
--color=always \
--count=${count} \
| while read line; do
# parse the first refname to determine the current branch
branch=$(echo "$line" | awk 'BEGIN { FS = "|" }; { print $1 }' | tr -d '*');
# parse how far ahead or behind the current branch is
behind="\033[31m--$(git rev-list --count "${branch}..${refbranch}")"
ahead="\033[32m++$(git rev-list --count "${refbranch}..${branch}")"
# remove the first refname from the line information
colorline=$(echo "$line" | sed 's/^[^|]*|//');
# combine into a single line
# also enforce a max lenght of 70 characters for the git message column
echo -e "$behind|$ahead|$colorline" | awk -F'|' -vOFS='|' '{if (length($6)>70) $6=substr($6,1,70) "\033[0m"}1';
done \
| ( echo -e "$header\n" && cat) \
| column -ts'|'
Original Script (Reformatted)
I’ve also included a formatted version of the original script below for comparison
refbranch=$1
count=$2;
git for-each-ref --sort=-committerdate refs/heads \
--format='%(refname:short)|%(HEAD)%(color:yellow)%(refname:short)|%(color:bold green)%(committerdate:relative)|%(color:blue)%(subject)|%(color:magenta)%(authorname)%(color:reset)' \
--color=always --count=${count:-20} \
| while read line; do \
branch=$(echo "$line" | awk 'BEGIN { FS = "|" }; { print $1 }' | tr -d '*'); \
ahead=$(git rev-list --count "${refbranch:-origin/master}..${branch}"); \
behind=$(git rev-list --count "${branch}..${refbranch:-origin/master}"); \
colorline=$(echo "$line" | sed 's/^[^|]*|//'); \
echo "$ahead|$behind|$colorline" | awk -F'|' -vOFS='|' '{$5=substr($5,1,70)}1' ;
done \
| ( echo "ahead|behind|branch|lastcommit|message|author\n" && cat) \
| column -ts'|'