-
-
Save sroze/066d5ac092a886d491fe to your computer and use it in GitHub Desktop.
This file contains 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 | |
# git-branch-status | |
# * originally by http://github.com/jehiah | |
# * "s'all good!" message by http://github.com/kd35a | |
# * ANSI colors by http://github.com/knovoselic | |
# * column formatting, filters, and usage by http://github.com/bill-auger | |
# this script prints out pretty git branch sync status reports | |
read -r -d '' USAGE <<-'USAGE' | |
usage: | |
git-branch-status | |
git-branch-status [-a | --all] | |
git-branch-status [-b | --branch] [branch-name] | |
git-branch-status [-d | --dates] | |
git-branch-status [-h | --help] | |
git-branch-status [-v | --verbose] | |
examples: | |
# show only branches for which upstream HEAD differs from local | |
$ git-branch-status | |
| collab-branch | (behind 1) | (ahead 2) | origin/collab-branch | | |
| feature-branch | (behind 0) | (ahead 2) | origin/feature-branch | | |
| master | (behind 1) | (ahead 0) | origin/master | | |
# show all branches - even those with no upstream and those up-to-date | |
$ git-branch-status -a | |
$ git-branch-status --all | |
| local-branch | n/a | n/a | (no upstream) | | |
| master | (behind 1) | (ahead 0) | origin/master | | |
| tracked-branch | (even) | (even) | origin/tracked-branch | | |
# show the current branch | |
$ git-branch-status -b | |
$ git-branch-status --branch | |
| current-branch | (behind 0) | (ahead 2) | origin/current-branch | | |
# show a specific branch | |
$ git-branch-status specific-branch | |
$ git-branch-status -b specific-branch | |
$ git-branch-status --branch specific-branch | |
| specific-branch | (behind 0) | (ahead 2) | origin/specific-branch | | |
# show the timestamp of each HEAD | |
$ git-branch-status -d | |
$ git-branch-status --dates | |
| 1999-12-31 master | (behind 2) | (ahead 0) | 2000-01-01 origin/master | | |
# print this usage message | |
$ git-branch-status -h | |
$ git-branch-status --help | |
"prints this usage message" | |
# show all branches with timestamps (like -a -d) | |
$ git-branch-status -v | |
$ git-branch-status --verbose | |
| 1999-12-31 local | n/a | n/a | (no upstream) | | |
| 1999-12-31 master | (behind 1) | (ahead 0) | 2000-01-01 origin/master | | |
| 1999-12-31 tracked | (even) | (even) | 2000-01-01 origin/tracked | | |
USAGE | |
### helpers ### | |
function get_refs | |
{ | |
git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads 2> /dev/null | |
} | |
function get_status | |
{ | |
git rev-list --left-right ${local}...${remote} -- 2>/dev/null | |
} | |
function current_branch | |
{ | |
git rev-parse --abbrev-ref HEAD | |
} | |
function is_current_branch # (a_branch_name) | |
{ | |
current=$(current_branch) | |
if (($SHOW_DATES)) | |
then this_branch=$(get_head_date $1)$1 ; current=$(get_head_date $current)$current ; | |
else this_branch=$1 | |
fi | |
if [ "$this_branch" == "$current" ] ; then echo 1 ; else echo 0 ; fi ; | |
} | |
function does_branch_exist # (a_branch_name) | |
{ | |
is_known_branch=$(git branch | grep -G "^ $1$") # all but current | |
[ $(is_current_branch $1) -o "$is_known_branch" ] && echo 1 || echo 0 | |
} | |
function set_filter_or_die # (a_branch_name) | |
{ | |
if (($(does_branch_exist $1))) | |
then branch=$1 | |
else echo "no such branch: '$1'" ; exit ; | |
fi | |
} | |
function get_head_date # (a_commit_ref) | |
{ | |
author_date=$(git log -n 1 --format=format:"%ai" $1 2> /dev/null) | |
(($SHOW_DATES)) && [ "$author_date" ] && echo "${author_date:0:10}$JOIN_CHAR" | |
} | |
function get_commit_msg # (a_commit_ref) | |
{ | |
git log -n 1 --format=format:"%s" $1 | |
} | |
### switches ### | |
if [ $1 ] ; then | |
if [ "$1" == "-a" -o "$1" == "--all" ] ; then readonly SHOW_ALL=1 ; | |
elif [ "$1" == "-b" -o "$1" == "--branch" ] ; then | |
if [ $2 ] ; then set_filter_or_die $2 ; else branch=$(current_branch) ; fi ; | |
elif [ "$1" == "-d" -o "$1" == "--dates" ] ; then readonly SHOW_DATES=1 ; | |
elif [ "$1" == "-h" -o "$1" == "--help" ] ; then echo "$USAGE" ; exit ; | |
elif [ "$1" == "-v" -o "$1" == "--verbose" ] | |
then readonly SHOW_ALL=1 ; readonly SHOW_DATES=1 ; | |
else set_filter_or_die $1 | |
fi | |
fi | |
### constants ### | |
readonly SHOW_ALL_LOCAL=$(($SHOW_ALL + 0)) # also show branches that have no upstream | |
readonly SHOW_ALL_REMOTE=$(($SHOW_ALL + 0)) # also show branches that are up to date | |
readonly MAX_COL_W=27 # should be => 12 | |
readonly CWHITE='\033[0;37m' | |
readonly CGREEN='\033[0;32m' | |
readonly CYELLOW='\033[1;33m' | |
readonly CRED='\033[0;31m' | |
readonly CEND='\033[0m' | |
readonly CDEFAULT=$CWHITE | |
readonly CAHEAD=$CYELLOW | |
readonly CBEHIND=$CRED | |
readonly CEVEN=$CGREEN | |
readonly CNOUPSTREAM=$CRED | |
readonly JOIN_CHAR='_' | |
readonly JOIN_REGEX="s/$JOIN_CHAR/ /" | |
readonly STAR="*" | |
readonly DELIM="|" | |
readonly NO_UPSTREAM="(no${JOIN_CHAR}upstream)" | |
readonly NO_RESULTS_MSG="Everything is synchronized" | |
### variables ### | |
n_total_differences=0 | |
local_w=0 | |
behind_w=0 | |
ahead_w=0 | |
remote_w=0 | |
declare -a local_msgs=() | |
declare -a behind_msgs=() | |
declare -a ahead_msgs=() | |
declare -a remote_msgs=() | |
declare -a local_colors=() | |
declare -a behind_colors=() | |
declare -a ahead_colors=() | |
declare -a remote_colors=() | |
# loop over all branches | |
while read local remote | |
do | |
# filter branches by name | |
[ $branch ] && [ "$branch" != "$local" ] && continue | |
# parse local<->remote sync status | |
if [ $remote ] ; then | |
status=$(get_status) ; (($?)) && continue ; | |
n_behind=$(echo $status | tr " " "\n" | grep -c '^>') | |
n_ahead=$( echo $status | tr " " "\n" | grep -c '^<') | |
n_differences=$(($n_behind + $n_ahead)) | |
n_total_differences=$(($n_total_differences + $n_differences)) | |
# filter branches by status | |
(($SHOW_ALL_REMOTE)) || (($n_differences)) || continue | |
# set data for branches with upstream | |
local_color=$CDEFAULT | |
if (($n_behind)) | |
then behind_msg="(behind$JOIN_CHAR$n_behind)" ; behind_color=$CBEHIND | |
else behind_msg="(even)" ; behind_color=$CEVEN ; | |
fi | |
if (($n_ahead)) | |
then ahead_msg="(ahead$JOIN_CHAR$n_ahead)" ; ahead_color=$CAHEAD ; | |
else ahead_msg="(even)" ; ahead_color=$CEVEN ; | |
fi | |
remote_color=$CDEFAULT | |
elif (($SHOW_ALL_LOCAL)) ; then | |
# dummy data for branches with no upstream | |
local_color=$CDEFAULT | |
behind_msg="n/a" ; behind_color="$CDEFAULT" ; | |
ahead_msg="n/a" ; ahead_color="$CDEFAULT" ; | |
remote="$NO_UPSTREAM" ; remote_color=$CNOUPSTREAM ; | |
else continue | |
fi | |
# populate lists | |
local_msg="$(get_head_date $local)$local" ; local_msg="${local_msg:0:$MAX_COL_W}" ; | |
remote_msg="$(get_head_date $remote)$remote" ; remote_msg="${remote_msg:0:$MAX_COL_W}" ; | |
local_msgs=( ${local_msgs[@]} "$local_msg" ) | |
behind_msgs=( ${behind_msgs[@]} "$behind_msg" ) | |
ahead_msgs=( ${ahead_msgs[@]} "$ahead_msg" ) | |
remote_msgs=( ${remote_msgs[@]} "$remote_msg" ) | |
local_colors=( ${local_colors[@]} "$local_color" ) | |
behind_colors=( ${behind_colors[@]} "$behind_color" ) | |
ahead_colors=( ${ahead_colors[@]} "$ahead_color" ) | |
remote_colors=( ${remote_colors[@]} "$remote_color" ) | |
# determine max column widths | |
if [ ${#local_msg} -gt $local_w ] ; then local_w=${#local_msg} ; fi ; | |
if [ ${#behind_msg} -gt $behind_w ] ; then behind_w=${#behind_msg} ; fi ; | |
if [ ${#ahead_msg} -gt $ahead_w ] ; then ahead_w=${#ahead_msg} ; fi ; | |
if [ ${#remote_msg} -gt $remote_w ] ; then remote_w=${#remote_msg} ; fi ; | |
done < <(get_refs) | |
# pretty print results | |
for (( result_n = 0 ; result_n < ${#local_msgs[@]} ; result_n++ )) | |
do | |
# fetch data | |
local_msg=$( echo ${local_msgs[$result_n]} | sed "$JOIN_REGEX" ) | |
behind_msg=$( echo ${behind_msgs[$result_n]} | sed "$JOIN_REGEX" ) | |
ahead_msg=$( echo ${ahead_msgs[$result_n]} | sed "$JOIN_REGEX" ) | |
remote_msg=$( echo ${remote_msgs[$result_n]} | sed "$JOIN_REGEX" ) | |
local_color="${local_colors[$result_n]}" | |
behind_color="${behind_colors[$result_n]}" | |
ahead_color="${ahead_colors[$result_n]}" | |
remote_color="${remote_colors[$result_n]}" | |
# calculate column offsets | |
local_offset=1 | |
behind_offset=$(( $local_w - ${#local_msg} )) | |
ahead_offset=$(( $behind_w - ${#behind_msg} )) | |
remote_offset=$(( $ahead_w - ${#ahead_msg} )) | |
end_offset=$(( $remote_w - ${#remote_msg} )) | |
# build output messages and display | |
if (($(is_current_branch $local_msg))) ; then star=$STAR ; else star=" " ; fi ; | |
local_msg="%$(( $local_offset ))s$star$(echo -e $DELIM $local_color$local_msg$CEND)" | |
behind_msg="%$(( $behind_offset ))s $( echo -e $DELIM $behind_color$behind_msg$CEND)" | |
ahead_msg="%$(( $ahead_offset ))s $( echo -e $DELIM $ahead_color$ahead_msg$CEND)" | |
remote_msg="%$(( $remote_offset ))s $( echo -e $DELIM $remote_color$remote_msg$CEND)" | |
end_msg="%$(( $end_offset ))s $DELIM" | |
printf "$local_msg$behind_msg$ahead_msg$remote_msg$end_msg\n" | |
done | |
# print something if no diffs (and some branches exist in this dir) | |
if [ "$n_total_differences" == "0" -a "$(get_refs)" ] | |
then echo -e "$CEVEN$NO_RESULTS_MSG$CEND" | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
i have created a repo for this script on github in order to have a proper issue tracker - all further development and communications will take place there - feel free to clone, fork, star , or watch the github repo -->
https://github.com/bill-auger/git-branch-status/
this script is also now icluded in jwiegley's git-scripts collection but please direct comments, bug reports, and feature requests to the upstream issue tracker
@sroze -
could you please merge my fork into yours - this will replace the script body with a link to the latest upstream repo on github like so: