11#! /bin/bash
22
3- # git-branch-status
4- # * originally by http://github.com/jehiah
5- # * "s'all good!" message by http://github.com/kd35a
6- # * ANSI colors by http://github.com/knovoselic
7- # * column formatting, filters, and usage by http://github.com/bill-auger
8-
9- # this script prints out pretty git branch sync status reports
3+ # git-branch-status - this script prints out pretty git branch sync status reports
4+ # * originally by http://github.com/jehiah
5+ # * "s'all good!" message by http://github.com/kd35a
6+ # * ANSI colors by http://github.com/knovoselic
7+ # * formatting, filters, usage, and remotes by http://github.com/bill-auger
108
119
1210read -r -d ' ' USAGE << -'USAGE '
@@ -17,58 +15,108 @@ usage:
1715 git-branch-status [-b | --branch] [branch-name]
1816 git-branch-status [-d | --dates]
1917 git-branch-status [-h | --help]
18+ git-branch-status [-r | --remotes]
2019 git-branch-status [-v | --verbose]
2120
2221examples:
2322
2423 # show only branches for which upstream HEAD differs from local
2524 $ git-branch-status
2625 | collab-branch | (behind 1) | (ahead 2) | origin/collab-branch |
27- | feature-branch | (behind 0) | (ahead 2) | origin/feature-branch |
28- | master | (behind 1) | (ahead 0) | origin/master |
26+ | feature-branch | (even) | (ahead 2) | origin/feature-branch |
27+ | master | (behind 1) | (even) | origin/master |
2928
30- # show all branches - even those with no upstream and those up-to-date
29+ # show all branches - even those with no upstream or no local and those up-to-date
3130 $ git-branch-status -a
3231 $ git-branch-status --all
33- | local-branch | n/a | n/a | (no upstream) |
34- | master | (behind 1) | (ahead 0) | origin/master |
35- | tracked-branch | (even) | (even) | origin/tracked-branch |
32+ | master | (even) | (ahead 1) | origin/master |
33+ | tracked-branch | (even) | (even) | origin/tracked-branch |
34+ | (no local) | n/a | n/a | origin/untracked-branch |
35+ | local-branch | n/a | n/a | (no upstream) |
36+ | master | (behind 1) | (ahead 1) | a-remote/master |
37+ | (no local) | n/a | n/a | a-remote/untracked-branch |
3638
3739 # show the current branch
3840 $ git-branch-status -b
3941 $ git-branch-status --branch
40- | current-branch | (behind 0 ) | (ahead 2) | origin/current-branch |
42+ | current-branch | (even ) | (ahead 2) | origin/current-branch |
4143
4244 # show a specific branch
4345 $ git-branch-status specific-branch
4446 $ git-branch-status -b specific-branch
4547 $ git-branch-status --branch specific-branch
46- | specific-branch | (behind 0 ) | (ahead 2) | origin/specific-branch |
48+ | specific-branch | (even ) | (ahead 2) | origin/specific-branch |
4749
4850 # show the timestamp of each HEAD
4951 $ git-branch-status -d
5052 $ git-branch-status --dates
51- | 1999-12-31 master | (behind 2) | (ahead 0 ) | 2000-01-01 origin/master |
53+ | 1999-12-31 master | (behind 2) | (even ) | 2000-01-01 origin/master |
5254
5355 # print this usage message
5456 $ git-branch-status -h
5557 $ git-branch-status --help
5658 "prints this usage message"
5759
60+ # show all remote branches - even those with no local
61+ $ git-branch-status -r
62+ $ git-branch-status --remotes
63+ | master | (behind 1) | (even) | a-remote/master |
64+ | (no local) | n/a | n/a | a-remote/untracked-branch |
65+
5866 # show all branches with timestamps (like -a -d)
5967 $ git-branch-status -v
6068 $ git-branch-status --verbose
61- | 1999-12-31 local | n/a | n/a | (no upstream) |
62- | 1999-12-31 master | (behind 1) | (ahead 0 ) | 2000-01-01 origin/master |
63- | 1999-12-31 tracked | (even) | (even) | 2000-01-01 origin/tracked |
69+ | 1999-12-31 local | n/a | n/a | (no upstream) |
70+ | 1999-12-31 master | (behind 1) | (even ) | 2000-01-01 origin/master |
71+ | 1999-12-31 tracked | (even) | (even) | 2000-01-01 origin/tracked |
6472USAGE
6573
6674
75+ # ## constants ###
76+
77+ readonly MAX_COL_W=27 # should be => 12
78+ readonly CWHITE=' \033[0;37m'
79+ readonly CGREEN=' \033[0;32m'
80+ readonly CYELLOW=' \033[1;33m'
81+ readonly CRED=' \033[0;31m'
82+ readonly CEND=' \033[0m'
83+ readonly CDEFAULT=$CWHITE
84+ readonly CAHEAD=$CYELLOW
85+ readonly CBEHIND=$CRED
86+ readonly CEVEN=$CGREEN
87+ readonly CNOUPSTREAM=$CRED
88+ readonly CNOLOCAL=$CRED
89+ readonly HRULE_CHAR=' -'
90+ readonly JOIN_CHAR=' _'
91+ readonly JOIN_REGEX=" s/$JOIN_CHAR / /"
92+ readonly STAR=" *"
93+ readonly DELIM=" |"
94+ readonly NO_UPSTREAM=" (no${JOIN_CHAR} upstream)"
95+ readonly NO_LOCAL=" (no${JOIN_CHAR} local)"
96+ readonly NO_RESULTS_MSG=" Everything is synchronized"
97+
98+
99+ # ## variables ###
100+ n_total_differences=0
101+ local_w=0
102+ behind_w=0
103+ ahead_w=0
104+ remote_w=0
105+ declare -a local_msgs=()
106+ declare -a behind_msgs=()
107+ declare -a ahead_msgs=()
108+ declare -a remote_msgs=()
109+ declare -a local_colors=()
110+ declare -a behind_colors=()
111+ declare -a ahead_colors=()
112+ declare -a remote_colors=()
113+
114+
67115# ## helpers ###
68116
69- function get_refs
117+ function get_refs # (a_refs_dir)
70118{
71- git for-each-ref --format=" %(refname:short) %(upstream:short)" refs/heads 2> /dev/null
119+ git for-each-ref --format=" %(refname:short) %(upstream:short)" $1 2> /dev/null
72120}
73121
74122function get_status
@@ -117,70 +165,45 @@ function get_commit_msg # (a_commit_ref)
117165 git log -n 1 --format=format:" %s" $1
118166}
119167
120-
121- # ## switches ###
122-
123- if [ $1 ] ; then
124- if [ " $1 " == " -a" -o " $1 " == " --all" ] ; then readonly SHOW_ALL=1 ;
125- elif [ " $1 " == " -b" -o " $1 " == " --branch" ] ; then
126- if [ $2 ] ; then set_filter_or_die $2 ; else branch=$( current_branch) ; fi ;
127- elif [ " $1 " == " -d" -o " $1 " == " --dates" ] ; then readonly SHOW_DATES=1 ;
128- elif [ " $1 " == " -h" -o " $1 " == " --help" ] ; then echo " $USAGE " ; exit ;
129- elif [ " $1 " == " -v" -o " $1 " == " --verbose" ]
130- then readonly SHOW_ALL=1 ; readonly SHOW_DATES=1 ;
131- else set_filter_or_die $1
132- fi
133- fi
134-
135-
136- # ## constants ###
137-
138- readonly SHOW_ALL_LOCAL=$(( $SHOW_ALL + 0 )) # also show branches that have no upstream
139- readonly SHOW_ALL_REMOTE=$(( $SHOW_ALL + 0 )) # also show branches that are up to date
140- readonly MAX_COL_W=27 # should be => 12
141- readonly CWHITE=' \033[0;37m'
142- readonly CGREEN=' \033[0;32m'
143- readonly CYELLOW=' \033[1;33m'
144- readonly CRED=' \033[0;31m'
145- readonly CEND=' \033[0m'
146- readonly CDEFAULT=$CWHITE
147- readonly CAHEAD=$CYELLOW
148- readonly CBEHIND=$CRED
149- readonly CEVEN=$CGREEN
150- readonly CNOUPSTREAM=$CRED
151- readonly JOIN_CHAR=' _'
152- readonly JOIN_REGEX=" s/$JOIN_CHAR / /"
153- readonly STAR=" *"
154- readonly DELIM=" |"
155- readonly NO_UPSTREAM=" (no${JOIN_CHAR} upstream)"
156- readonly NO_RESULTS_MSG=" Everything is synchronized"
168+ function printHRule # (rule_w)
169+ {
170+ printf " $( head -c $1 < /dev/zero | tr ' \0' $HRULE_CHAR ) \n"
171+ }
157172
158173
159- # ## variables ###
174+ # ## business ###
160175
161- n_total_differences=0
162- local_w=0
163- behind_w=0
164- ahead_w=0
165- remote_w=0
166- declare -a local_msgs=()
167- declare -a behind_msgs=()
168- declare -a ahead_msgs=()
169- declare -a remote_msgs=()
170- declare -a local_colors=()
171- declare -a behind_colors=()
172- declare -a ahead_colors=()
173- declare -a remote_colors=()
176+ function reset
177+ {
178+ n_total_differences=0
179+ local_w=0
180+ behind_w=0
181+ ahead_w=0
182+ remote_w=0
183+ local_msgs=()
184+ behind_msgs=()
185+ ahead_msgs=()
186+ remote_msgs=()
187+ local_colors=()
188+ behind_colors=()
189+ ahead_colors=()
190+ remote_colors=()
191+ }
174192
193+ function report # (a_local_branch_name a_remote_branch_name)
194+ {
195+ local=$1
196+ remote=$2
197+ does_local_exist=$( does_branch_exist $local_branch )
175198
176- # loop over all branches
177- while read local remote
178- do
179- # filter branches by name
199+ # filter branches per CLI arg
180200 [ $branch ] && [ " $branch " != " $local " ] && continue
181201
202+ # filter heads
203+ [ " $local " == " HEAD" ] && continue
204+
182205 # parse local<->remote sync status
183- if [ $remote ] ; then
206+ if (( $does_local_exist )) && [ $remote ] ; then
184207 status=$( get_status) ; (( $? )) && continue ;
185208
186209 n_behind=$( echo $status | tr " " " \n" | grep -c ' ^>' )
189212 n_total_differences=$(( $n_total_differences + $n_differences ))
190213
191214 # filter branches by status
192- (( $SHOW_ALL_REMOTE )) || (( $n_differences )) || continue
215+ (( $SHOW_ALL_UPSTREAM )) || (( $n_differences )) || continue
193216
194217 # set data for branches with upstream
195218 local_color=$CDEFAULT
202225 else ahead_msg=" (even)" ; ahead_color=$CEVEN ;
203226 fi
204227 remote_color=$CDEFAULT
205- elif (( $SHOW_ALL_LOCAL )) ; then
206- # dummy data for branches with no upstream
228+ elif (( $does_local_exist )) && [ -z $remote ] && (( $ SHOW_ALL_LOCAL)) ; then
229+ # dummy data for local branches with no upstream counterpart
207230 local_color=$CDEFAULT
208- behind_msg=" n/a" ; behind_color=" $CDEFAULT " ;
209- ahead_msg=" n/a" ; ahead_color=" $CDEFAULT " ;
210- remote=" $NO_UPSTREAM " ; remote_color=$CNOUPSTREAM ;
231+ behind_color=" $CDEFAULT " ; behind_msg=" n/a" ;
232+ ahead_color=" $CDEFAULT " ; ahead_msg=" n/a" ;
233+ remote_color=$CNOUPSTREAM ; remote=" $NO_UPSTREAM " ;
234+ elif ! (( $does_local_exist )) && [ $remote ] && (( $SHOW_ALL_REMOTE )) ; then
235+ # dummy data for remote branches with no local counterpart
236+ local_color=$CNOLOCAL ; local=" $NO_LOCAL " ;
237+ behind_color=" $CDEFAULT " ; behind_msg=" n/a" ;
238+ ahead_color=" $CDEFAULT " ; ahead_msg=" n/a" ;
239+ remote_color=$CDEFAULT
211240 else continue
212241 fi
213242
228257 if [ ${# behind_msg} -gt $behind_w ] ; then behind_w=${# behind_msg} ; fi ;
229258 if [ ${# ahead_msg} -gt $ahead_w ] ; then ahead_w=${# ahead_msg} ; fi ;
230259 if [ ${# remote_msg} -gt $remote_w ] ; then remote_w=${# remote_msg} ; fi ;
231- done < <( get_refs )
260+ }
232261
233- # pretty print results
234- for (( result_n = 0 ; result_n < ${# local_msgs[@]} ; result_n++ ))
235- do
262+ function printReportLine
263+ {
236264 # fetch data
237265 local_msg=$( echo ${local_msgs[$result_n]} | sed " $JOIN_REGEX " )
238266 behind_msg=$( echo ${behind_msgs[$result_n]} | sed " $JOIN_REGEX " )
258286 remote_msg=" %$(( $remote_offset )) s $( echo -e $DELIM $remote_color$remote_msg$CEND ) "
259287 end_msg=" %$(( $end_offset )) s $DELIM "
260288 printf " $local_msg$behind_msg$ahead_msg$remote_msg$end_msg \n"
261- done
289+ }
290+
291+ function printReport # (header)
292+ {
293+ n_notable_differences=${# local_msgs[@]}
294+
295+ # pretty print results
296+ printf " \n $1 \n"
297+ if [ " $n_notable_differences " != " 0" ]
298+ then rule_w=$(( $local_w + $behind_w + $ahead_w + $remote_w + 13 ))
299+ printHRule $rule_w
300+ for (( result_n = 0 ; result_n < $n_notable_differences ; result_n++ ))
301+ do printReportLine
302+ done
303+ printHRule $rule_w
304+ fi
262305
263- # print something if no diffs (and some branches exist in this dir)
264- if [ " $n_total_differences " == " 0" -a " $( get_refs) " ]
265- then echo -e " $CEVEN$NO_RESULTS_MSG$CEND "
306+ # print something if no diffs
307+ if [ " $n_total_differences " == " 0" -a " $( get_refs) " ]
308+ then rule_w=$(( ${# NO_RESULTS_MSG} + 4 ))
309+ printHRule $rule_w
310+ echo -e " $DELIM $CEVEN$NO_RESULTS_MSG$CEND $DELIM "
311+ printHRule $rule_w
312+ fi
313+
314+ reset
315+ }
316+
317+
318+ # ## main entry ###
319+
320+ # parse CLI switches
321+ if [ $1 ] ; then
322+ show_dates=0
323+ show_all=0
324+ show_all_local=0
325+ show_all_upstream=0
326+ show_all_remote=0
327+ if [ " $1 " == " -a" -o " $1 " == " --all" ] ; then show_all=1 ;
328+ elif [ " $1 " == " -b" -o " $1 " == " --branch" ] ; then
329+ if [ $2 ] ; then set_filter_or_die $2 ; else branch=$( current_branch) ; fi ;
330+ elif [ " $1 " == " -d" -o " $1 " == " --dates" ] ; then show_dates=1 ;
331+ elif [ " $1 " == " -h" -o " $1 " == " --help" ] ; then echo " $USAGE " ; exit ;
332+ elif [ " $1 " == " -r" -o " $1 " == " --remotes" ] ; then show_all_remote=1 ;
333+ elif [ " $1 " == " -v" -o " $1 " == " --verbose" ] ; then show_all=1 ;
334+ show_dates=1 ;
335+ else set_filter_or_die $1
336+ fi
337+ readonly SHOW_DATES=$show_dates
338+ readonly SHOW_ALL=$show_all
339+ readonly SHOW_ALL_LOCAL=$(( $show_all + $show_all_local )) # also show branches that have no upstream
340+ readonly SHOW_ALL_UPSTREAM=$(( $show_all + $show_all_upstream )) # also show branches that are up to date
341+ readonly SHOW_ALL_REMOTE=$(( $show_all + $show_all_remote )) # also show branches that have no local
266342fi
343+
344+
345+ # compare local branches status to their upstreams
346+ while read local upstream ; do report $local $upstream ; done < <( get_refs refs/heads) ;
347+ printReport " local <-> upstream"
348+
349+
350+ (( $SHOW_ALL_REMOTE )) || exit
351+
352+
353+ # compare other remote branches status to local branches
354+ for remote_repo in ` git remote`
355+ do
356+ while read remote_branch ; do
357+ local_branch=${remote_branch# $remote_repo / }
358+ upstream_branch=` git rev-parse --abbrev-ref $local_branch @{upstream} 2> /dev/null`
359+
360+ [ " $remote_branch " != " $upstream_branch " ] && report $local_branch $remote_branch
361+ done < <( get_refs refs/remotes/$remote_repo )
362+
363+ printReport " local <-> $remote_repo "
364+ done
0 commit comments