git-branch-status 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. #!/usr/bin/env bash
  2. # git-branch-status - print pretty git branch sync status reports
  3. #
  4. # Copyright 2011 Jehiah Czebotar <https://github.com/jehiah>
  5. # Copyright 2013 Fredrik Strandin <https://github.com/kd35a>
  6. # Copyright 2014 Kristijan Novoselić <https://github.com/knovoselic>
  7. # Copyright 2014-2020 bill-auger <https://github.com/bill-auger>
  8. #
  9. # git-branch-status is free software: you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License version 3
  11. # as published by the Free Software Foundation.
  12. #
  13. # git-branch-status is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License version 3
  19. # along with git-branch-status. If not, see <http://www.gnu.org/licenses/>.
  20. # credits:
  21. # * original `git rev-list` grepping - by Jehiah Czebotar
  22. # * "s'all good!" message - by Fredrik Strandin
  23. # * ANSI colors - by Kristijan Novoselić
  24. # * various features and maintenance - by bill-auger
  25. # please direct bug reports, feature requests, or PRs to one of the upstream repos:
  26. # * https://github.com/bill-auger/git-branch-status/issues/
  27. # * https://notabug.org/bill-auger/git-branch-status/issues/
  28. # * https://pagure.io/git-branch-status/issues/
  29. read -r -d '' USAGE <<-'USAGE_MSG'
  30. USAGE:
  31. git-branch-status
  32. git-branch-status [ base-branch-name compare-branch-name ]
  33. git-branch-status [ -a | --all ]
  34. git-branch-status [ -b | --branch ] [ filter-branch-name ]
  35. git-branch-status [ -d | --dates ]
  36. git-branch-status [ -h | --help ]
  37. git-branch-status [ -l | --local ]
  38. git-branch-status [ -r | --remotes ]
  39. git-branch-status [ -v | --verbose ]
  40. EXAMPLES:
  41. # show only branches for which upstream differs from local
  42. $ git-branch-status
  43. | collab-branch | (behind 1) | (ahead 2) | origin/collab-branch |
  44. | feature-branch | (even) | (ahead 2) | origin/feature-branch |
  45. | master | (behind 1) | (even) | origin/master |
  46. # compare two arbitrary branches - local or remote
  47. $ git-branch-status local-arbitrary-branch fork/arbitrary-branch
  48. | local-arbitrary-branch | (even) | (ahead 1) | fork/arbitrary-branch |
  49. $ git-branch-status fork/arbitrary-branch local-arbitrary-branch
  50. | fork/arbitrary-branch | (behind 1) | (even) | local-arbitrary-branch |
  51. # show all branches - local and remote, regardless of state or relationship
  52. $ git-branch-status -a
  53. $ git-branch-status --all
  54. *| master | (even) | (ahead 1) | origin/master |
  55. | tracked-branch | (even) | (even) | origin/tracked-branch |
  56. | (no local) | n/a | n/a | origin/untracked-branch |
  57. | local-branch | n/a | n/a | (no upstream) |
  58. | master | (behind 1) | (ahead 1) | a-remote/master |
  59. | (no local) | n/a | n/a | a-remote/untracked-branch |
  60. # show the current branch
  61. $ git-branch-status -b
  62. $ git-branch-status --branch
  63. *| current-branch | (even) | (ahead 2) | origin/current-branch |
  64. # show a specific branch
  65. $ git-branch-status specific-branch
  66. $ git-branch-status -b specific-branch
  67. $ git-branch-status --branch specific-branch
  68. | specific-branch | (even) | (ahead 2) | origin/specific-branch |
  69. # show the timestamp of each HEAD
  70. $ git-branch-status -d
  71. $ git-branch-status --dates
  72. | 1999-12-31 master | (behind 2) | (even) | 2000-01-01 origin/master |
  73. # print this usage message
  74. $ git-branch-status -h
  75. $ git-branch-status --help
  76. "prints this usage message"
  77. # show all local branches - including those synchronized or non-tracking
  78. $ git-branch-status -l
  79. $ git-branch-status --local
  80. *| master | (even) | (ahead 1) | origin/master |
  81. | tracked-branch | (even) | (even) | origin/tracked-branch |
  82. | local-branch | n/a | n/a | (no upstream) |
  83. # show all remote branches - including those not checked-out
  84. $ git-branch-status -r
  85. $ git-branch-status --remotes
  86. | master | (behind 1) | (even) | a-remote/master |
  87. | (no local) | n/a | n/a | a-remote/untracked-branch |
  88. # show all branches with timestamps (like -a -d)
  89. $ git-branch-status -v
  90. $ git-branch-status --verbose
  91. | 1999-12-31 master | (behind 1) | (even) | 2000-01-01 origin/master |
  92. | 1999-12-31 tracked | (even) | (even) | 2000-01-01 origin/tracked |
  93. *| 1999-12-31 local-wip | n/a | n/a | (no upstream) |
  94. USAGE_MSG
  95. ## user-defined configuration (see ./gbs-config.sh.inc.example) ##
  96. readonly THIS_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo $0)")"
  97. readonly DEF_CFG_FILE="${THIS_DIR}"/gbs-config.sh.inc
  98. readonly CFG_FILE=$( ( [[ "$GBS_CFG_FILE" && -f "$GBS_CFG_FILE" ]] && echo -n "$GBS_CFG_FILE" ) || \
  99. ( [[ "$DEF_CFG_FILE" && -f "$DEF_CFG_FILE" ]] && echo -n "$DEF_CFG_FILE" ) )
  100. source "$CFG_FILE" 2> /dev/null
  101. readonly FETCH_PERIOD=$( ( [[ "$GBS_FETCH_PERIOD" =~ ^-?[0-9]+$ ]] && echo -n "$GBS_FETCH_PERIOD" ) || \
  102. ( [[ "$CFG_FETCH_PERIOD" =~ ^-?[0-9]+$ ]] && echo -n "$CFG_FETCH_PERIOD" ) || \
  103. echo -n '-1' )
  104. readonly LAST_FETCH_FILE=$( ( touch "$GBS_LAST_FETCH_FILE" 2> /dev/null && echo -n "$GBS_LAST_FETCH_FILE" ) || \
  105. ( touch "$CFG_LAST_FETCH_FILE" 2> /dev/null && echo -n "$CFG_LAST_FETCH_FILE" ) || \
  106. echo -n ~/.GBS_LAST_FETCH )
  107. readonly USE_ANSI_COLOR=$( ( [[ "$GBS_USE_ANSI_COLOR" =~ ^[01]$ ]] && echo -n "$GBS_USE_ANSI_COLOR" ) || \
  108. ( [[ "$CFG_USE_ANSI_COLOR" =~ ^[01]$ ]] && echo -n "$CFG_USE_ANSI_COLOR" ) || \
  109. echo -n '1' )
  110. ## constants ##
  111. readonly INNER_PADDING_W=13 # '| ' + ' | ' + ' | ' + ' | ' + ' |'
  112. readonly MARGIN_PAD_W=2
  113. readonly MARGINS_PAD_W=$(( $MARGIN_PAD_W * 2 ))
  114. readonly ALL_PADDING_W=$(( $INNER_PADDING_W + $MARGINS_PAD_W ))
  115. readonly MAX_DIVERGENCE_W=12 # e.g. "(behind 999)"
  116. readonly MIN_TTY_W=60 # ASSERT: (0 < (ALL_PADDING_W + (MAX_DIVERGENCE_W * 2)) <= MIN_TTY_W)
  117. readonly MARGIN_PAD=$(printf "%$(( $MARGIN_PAD_W ))s")
  118. readonly CWHITE=$( (( $USE_ANSI_COLOR )) && echo '\033[0;37m' )
  119. readonly CRED=$( (( $USE_ANSI_COLOR )) && echo '\033[0;31m' )
  120. readonly CGREEN=$( (( $USE_ANSI_COLOR )) && echo '\033[0;32m' )
  121. readonly CYELLOW=$( (( $USE_ANSI_COLOR )) && echo '\033[1;33m' )
  122. readonly CBLUE=$( (( $USE_ANSI_COLOR )) && echo '\033[1;34m' )
  123. readonly CEND=$( (( $USE_ANSI_COLOR )) && echo '\033[0m' )
  124. readonly CDEFAULT=$CWHITE
  125. readonly CTRACKING=$CBLUE
  126. readonly CAHEAD=$CYELLOW
  127. readonly CBEHIND=$CRED
  128. readonly CEVEN=$CGREEN
  129. readonly CNOUPSTREAM=$CRED
  130. readonly CNOLOCAL=$CRED
  131. readonly HRULE_CHAR='-'
  132. readonly JOIN_CHAR='~'
  133. readonly JOIN_REGEX="s/$JOIN_CHAR/ /g"
  134. readonly TRIM_REGEX="s/.*\(.\{$MAX_DIVERGENCE_W\}\)$/\1/"
  135. readonly STAR='*'
  136. readonly DELIM='|'
  137. readonly NO_UPSTREAM="(no${JOIN_CHAR}upstream)"
  138. readonly NO_LOCAL="(no${JOIN_CHAR}local)"
  139. readonly NOT_REPO_MSG="Not a git repo"
  140. readonly BARE_REPO_MSG="Bare repo"
  141. readonly NO_COMMITS_MSG="No commits"
  142. readonly TTY_W_MSG="TTY must be wider than $MIN_TTY_W chars"
  143. readonly INVALID_BRANCH_MSG="No such branch:"
  144. readonly INVALID_LOCAL_BRANCH_MSG="No such local branch:"
  145. readonly NO_RESULTS_MSG="Nothing to compare"
  146. readonly NO_REFS_MSG="(No data)"
  147. readonly LOCALS_SYNCED_MSG="All tracking branches are synchronized with their upstreams"
  148. readonly BRANCH_SYNCED_MSG="This tracking branch is synchronized with it's upstream"
  149. readonly UNTRACKED_SYNCHED_MSG="These branches are synchronized with no tracking relationship"
  150. readonly REMOTES_SYNCED_MSG="All local branches with corresponding names on this remote are synchronized with that remote"
  151. readonly EXIT_SUCCESS=0
  152. readonly EXIT_FAILURE=1
  153. # readonly SHOW_DATES # Init()
  154. # readonly SHOW_ALL_SYNCED # Init()
  155. # readonly SHOW_ALL_LOCAL # Init()
  156. # readonly SHOW_ALL_REMOTE # Init()
  157. # readonly FILTER_BRANCH # Init()
  158. # readonly COMPARE_BRANCH # Init()
  159. ## variables ##
  160. WereAnyDivergences=0
  161. WereAnyCompared=0
  162. LocalW=0
  163. BehindW=0
  164. AheadW=0
  165. RemoteW=0
  166. declare -a LocalMsgs=()
  167. declare -a BehindMsgs=()
  168. declare -a AheadMsgs=()
  169. declare -a RemoteMsgs=()
  170. declare -a LocalColors=()
  171. declare -a BehindColors=()
  172. declare -a AheadColors=()
  173. declare -a RemoteColors=()
  174. ## helpers ##
  175. AssertIsValidRepo()
  176. {
  177. [ "$(git rev-parse --is-inside-work-tree 2> /dev/null)" == 'true' ] || \
  178. ! (( $(AssertIsNotBareRepo) )) && echo 1 || echo 0
  179. }
  180. AssertHasCommits()
  181. {
  182. [ "$(git cat-file -t HEAD 2> /dev/null)" ] && echo 1 || echo 0
  183. }
  184. AssertIsNotBareRepo()
  185. {
  186. [ "$(git rev-parse --is-bare-repository 2> /dev/null)" != 'true' ] && echo 1 || echo 0
  187. }
  188. GetRefs() # (refs_dir)
  189. {
  190. local refs_dir=$1
  191. local fmt='%(refname:short) %(upstream:short)'
  192. local sort=$( (( $SHOW_DATES )) && echo '--sort=creatordate')
  193. git for-each-ref --format="$fmt" $sort $refs_dir 2> /dev/null
  194. }
  195. GetLocalRefs()
  196. {
  197. GetRefs refs/heads
  198. }
  199. GetRemoteRefs() # (remote_repo_name)
  200. {
  201. local remote_repo=$1
  202. GetRefs refs/remotes/$remote_repo
  203. }
  204. GetStatus() # (base_commit compare_commit)
  205. {
  206. local base_commit=$1
  207. local compare_commit=$2
  208. git rev-list --left-right ${base_commit}...${compare_commit} -- 2>/dev/null
  209. }
  210. GetCurrentBranch()
  211. {
  212. git rev-parse --abbrev-ref HEAD
  213. }
  214. GetUpstreamBranch() # (local_branch)
  215. {
  216. local local_branch=$1
  217. git rev-parse --abbrev-ref $local_branch@{upstream} 2> /dev/null
  218. }
  219. IsCurrentBranch() # (branch_name)
  220. {
  221. local branch=$1
  222. local this_branch=$(AppendHeadDate $branch)
  223. local current_branch=$(AppendHeadDate $(GetCurrentBranch))
  224. [ "$this_branch" == "$current_branch" ] && echo 1 || echo 0
  225. }
  226. IsLocalBranch() # (branch_name)
  227. {
  228. local branch=$1
  229. local is_local_branch=$(git branch -a | grep -E "^.* $branch$")
  230. [ "$is_local_branch" ] && echo 1 || echo 0
  231. }
  232. IsTrackedBranch() # (base_branch_name compare_branch_name)
  233. {
  234. local base_branch=$1
  235. local compare_branch=$2
  236. local upstream_branch=$(GetUpstreamBranch $base_branch)
  237. [ "$compare_branch" == "$upstream_branch" ] && echo 1 || echo 0
  238. }
  239. DoesBranchExist() # (branch_name)
  240. {
  241. local branch=$1
  242. local is_known_branch=$(git branch -a | grep -E "^.* (remotes\/)?$branch$")
  243. [ "$is_known_branch" ] && echo 1 || echo 0
  244. }
  245. AppendHeadDate() # (commit_ref)
  246. {
  247. local commit=$1
  248. local author_date=$(git log -n 1 --format=format:"%ai" $commit 2> /dev/null)
  249. local date=''
  250. (($SHOW_DATES)) && [ "$author_date" ] && date="${author_date:0:10}$JOIN_CHAR"
  251. echo $date$commit
  252. }
  253. CurrentTtyW()
  254. {
  255. local tty_dims=$(stty -F /dev/tty size 2> /dev/null || stty -f /dev/tty size 2> /dev/null)
  256. local tty_w=$(echo $tty_dims | cut -d ' ' -f 2)
  257. (( $tty_w )) && echo "$tty_w" || echo "$MIN_TTY_W"
  258. }
  259. PrintHRule() # (rule_width)
  260. {
  261. local rule_w=$1
  262. local h_rule="$(dd if=/dev/zero bs=$rule_w count=1 2> /dev/null | tr '\0' $HRULE_CHAR)"
  263. echo "$MARGIN_PAD$h_rule"
  264. }
  265. Exit() # (exit_msg exit_status)
  266. {
  267. local exit_msg=$1
  268. local exit_status=$2
  269. case "$exit_status" in
  270. $EXIT_SUCCESS ) echo "$exit_msg" ; exit $EXIT_SUCCESS ;;
  271. $EXIT_FAILURE | '' ) echo "fatal: $exit_msg" ; exit $EXIT_FAILURE ;;
  272. esac
  273. }
  274. ## business ##
  275. Init() # (cli_args)
  276. {
  277. local show_dates=0
  278. local show_all=0
  279. local show_all_synced=0
  280. local show_all_local=0
  281. local show_all_remote=0
  282. local branch_a=''
  283. local branch_b=''
  284. # parse CLI switches
  285. case "$1" in
  286. '-a'|'--all' ) show_all=1 ;;
  287. '-b'|'--branch' ) [ "$2" ] && branch_a="$2" || branch_a=$(GetCurrentBranch) ;;
  288. '-d'|'--dates' ) branch_a="$2" ; branch_b="$3" ; show_dates=1 ;;
  289. '-h'|'--help' ) Exit "$USAGE" $EXIT_SUCCESS ;;
  290. '-l'|'--local' ) show_all_local=1 ; show_all_synced=1 ; ;;
  291. '-r'|'--remotes' ) show_all_remote=1 ; show_all_synced=1 ; ;;
  292. '-v'|'--verbose' ) show_all=1 ; show_dates=1 ; ;;
  293. * ) branch_a="$1" ; branch_b="$2" ; ;;
  294. esac
  295. # sanity checks
  296. (( $(AssertIsValidRepo ) )) || Exit "$NOT_REPO_ERR"
  297. (( $(AssertIsNotBareRepo) )) || Exit "$BARE_REPO_MSG"
  298. (( $(AssertHasCommits ) )) || Exit "$NO_COMMITS_MSG"
  299. (( $(CurrentTtyW) >= $MIN_TTY_W )) || Exit "$TTY_W_MSG"
  300. [ -z "$branch_a" ] || (($(DoesBranchExist $branch_a))) || Exit "$INVALID_BRANCH_MSG '$branch_a'"
  301. [ -z "$branch_b" ] || (($(DoesBranchExist $branch_b))) || Exit "$INVALID_BRANCH_MSG '$branch_b'"
  302. [ -z "$branch_a" ] || (($(IsLocalBranch $branch_a))) || [ "$branch_b" ] || Exit "$INVALID_LOCAL_BRANCH_MSG '$branch_a'"
  303. [ -z "$branch_a" ] || show_all_local=1 # force "no upstream" message for non-tracking branches
  304. readonly SHOW_DATES=$show_dates
  305. readonly SHOW_ALL_SYNCED=$(( $show_all + $show_all_synced )) # show branches that are synchronized with their counterparts
  306. readonly SHOW_ALL_LOCAL=$(( $show_all + $show_all_local )) # show local branches that are not tracking any upstream
  307. readonly SHOW_ALL_REMOTE=$(( $show_all + $show_all_remote )) # show all remote branches
  308. readonly FILTER_BRANCH=$branch_a
  309. readonly COMPARE_BRANCH=$branch_b
  310. }
  311. FetchRemotes()
  312. {
  313. if (( ${FETCH_PERIOD} >= 0 ))
  314. then local now_ts=$(date +%s)
  315. local last_fetch_ts=$(( $(cat ${LAST_FETCH_FILE} 2> /dev/null) + 0 ))
  316. if (( ${now_ts} - ${last_fetch_ts} >= ${FETCH_PERIOD} * 60 ))
  317. then git fetch --all
  318. echo -n ${now_ts} > ${LAST_FETCH_FILE}
  319. fi
  320. fi
  321. }
  322. GenerateReports()
  323. {
  324. if [ "$COMPARE_BRANCH" ]
  325. then CustomReport $(IsTrackedBranch $FILTER_BRANCH $COMPARE_BRANCH)
  326. else (( !(( $SHOW_ALL_REMOTE )) || (( $SHOW_ALL_LOCAL )) )) && LocalReport
  327. (( $SHOW_ALL_REMOTE )) && RemoteReport
  328. fi
  329. }
  330. CustomReport() # (is_tracked_branch)
  331. {
  332. local is_tracked_branch=$1
  333. local synchronized_msg
  334. (($is_tracked_branch)) && synchronized_msg="$BRANCH_SYNCED_MSG" || synchronized_msg="$UNTRACKED_SYNCHED_MSG"
  335. # compare sync status of arbitrary branches per cli args
  336. GenerateReport $FILTER_BRANCH $COMPARE_BRANCH
  337. PrintReport "$FILTER_BRANCH <-> $COMPARE_BRANCH" "$synchronized_msg"
  338. }
  339. LocalReport()
  340. {
  341. local synchronized_msg
  342. [ "$FILTER_BRANCH" ] && synchronized_msg="$BRANCH_SYNCED_MSG" || synchronized_msg="$LOCALS_SYNCED_MSG"
  343. # compare sync status of local branches to their upstreams
  344. while read local upstream ; do GenerateReport $local $upstream ; done < <(GetLocalRefs) ;
  345. PrintReport "local <-> upstream" "$synchronized_msg"
  346. }
  347. RemoteReport()
  348. {
  349. local synchronized_msg="$REMOTES_SYNCED_MSG"
  350. local remote_repo
  351. # compare sync status of remote branches to local branches with the same names
  352. for remote_repo in $(git remote)
  353. do while read remote_branch
  354. do local local_branch=${remote_branch#$remote_repo/}
  355. GenerateReport $local_branch $remote_branch
  356. done < <(GetRemoteRefs $remote_repo)
  357. PrintReport "local <-> $remote_repo" "$synchronized_msg"
  358. done
  359. }
  360. GenerateReport() # (base_branch compare_branch)
  361. {
  362. local base_branch=$1
  363. local compare_branch=$2
  364. local does_base_branch_exist=$( DoesBranchExist $base_branch )
  365. local does_compare_branch_exist=$(DoesBranchExist $compare_branch )
  366. local is_tracked_branch=$( IsTrackedBranch $base_branch $compare_branch)
  367. local local_msg ; local behind_msg ; local ahead_msg ; local remote_msg ;
  368. local local_color ; local behind_color ; local ahead_color ; local remote_color ;
  369. # filter heads
  370. [ "$base_branch" != 'HEAD' ] || return
  371. # filter branches per CLI arg
  372. [ -z "$FILTER_BRANCH" -o "$base_branch" == "$FILTER_BRANCH" ] || return
  373. # parse local<->remote or arbitrary branches sync status
  374. if (( ! $does_base_branch_exist )) && (( ! $does_compare_branch_exist )) ; then return ;
  375. elif (( $does_base_branch_exist )) && (( $does_compare_branch_exist ))
  376. then local status=$(GetStatus $base_branch $compare_branch) ; (( ! $? )) || return ;
  377. local n_behind=$(echo $status | tr " " "\n" | grep -c '^>')
  378. local n_ahead=$( echo $status | tr " " "\n" | grep -c '^<')
  379. local n_divergences=$(( $n_behind + $n_ahead ))
  380. (( $WereAnyDivergences + $n_divergences )) && WereAnyDivergences=1
  381. WereAnyCompared=1
  382. # filter branches by status
  383. (( $SHOW_ALL_SYNCED )) || (( $n_divergences > 0 )) || return
  384. # set data for branches with remote counterparts or arbitrary branches
  385. (($is_tracked_branch)) && color=$CTRACKING || color=$CDEFAULT
  386. local_color=$color
  387. if (( $n_behind ))
  388. then behind_msg="(behind$JOIN_CHAR$n_behind)" ; behind_color=$CBEHIND ;
  389. else behind_msg="(even)" ; behind_color=$CEVEN ;
  390. fi
  391. if (( $n_ahead ))
  392. then ahead_msg="(ahead$JOIN_CHAR$n_ahead)" ; ahead_color=$CAHEAD ;
  393. else ahead_msg="(even)" ; ahead_color=$CEVEN ;
  394. fi
  395. remote_color=$color
  396. elif (( $does_base_branch_exist )) && (( $SHOW_ALL_LOCAL ))
  397. then # dummy data for local branches with no upstream counterpart
  398. local_color=$CDEFAULT
  399. behind_color="$CDEFAULT" ; behind_msg="n/a" ;
  400. ahead_color="$CDEFAULT" ; ahead_msg="n/a" ;
  401. remote_color=$CNOUPSTREAM ; compare_branch="$NO_UPSTREAM" ;
  402. elif (( ! $does_base_branch_exist )) && (( $SHOW_ALL_REMOTE ))
  403. then # dummy data for remote branches with no local counterpart
  404. local_color=$CNOLOCAL ; base_branch="$NO_LOCAL" ;
  405. behind_color="$CDEFAULT" ; behind_msg="n/a" ;
  406. ahead_color="$CDEFAULT" ; ahead_msg="n/a" ;
  407. remote_color=$CDEFAULT
  408. else return
  409. fi
  410. local_msg="$( AppendHeadDate $base_branch )"
  411. behind_msg=$( echo $behind_msg | sed "$TRIM_REGEX")
  412. ahead_msg=$( echo $ahead_msg | sed "$TRIM_REGEX")
  413. remote_msg="$(AppendHeadDate $compare_branch )"
  414. # populate lists
  415. LocalMsgs=( ${LocalMsgs[@]} "$local_msg" )
  416. BehindMsgs=( ${BehindMsgs[@]} "$behind_msg" )
  417. AheadMsgs=( ${AheadMsgs[@]} "$ahead_msg" )
  418. RemoteMsgs=( ${RemoteMsgs[@]} "$remote_msg" )
  419. LocalColors=( ${LocalColors[@]} "$local_color" )
  420. BehindColors=( ${BehindColors[@]} "$behind_color" )
  421. AheadColors=( ${AheadColors[@]} "$ahead_color" )
  422. RemoteColors=( ${RemoteColors[@]} "$remote_color" )
  423. # determine uniform column widths
  424. if [ ${#local_msg} -gt $LocalW ] ; then LocalW=${#local_msg} ; fi ;
  425. if [ ${#behind_msg} -gt $BehindW ] ; then BehindW=${#behind_msg} ; fi ;
  426. if [ ${#ahead_msg} -gt $AheadW ] ; then AheadW=${#ahead_msg} ; fi ;
  427. if [ ${#remote_msg} -gt $RemoteW ] ; then RemoteW=${#remote_msg} ; fi ;
  428. }
  429. PrintReport() # (table_header_line synchronized_msg)
  430. {
  431. local table_header_line=$1
  432. local synchronized_msg=$2
  433. local available_w=$(( $(CurrentTtyW) - $AheadW - $BehindW - $ALL_PADDING_W ))
  434. local n_results=${#LocalMsgs[@]}
  435. local are_all_in_sync
  436. local result_n
  437. (( $WereAnyCompared )) && !(( $WereAnyDivergences )) && are_all_in_sync=1 || are_all_in_sync=0
  438. # truncate column widths to fit
  439. while (( $LocalW + $RemoteW > $available_w ))
  440. do (( $LocalW >= $RemoteW )) && LocalW=$(( $LocalW - 1 ))
  441. (( $LocalW <= $RemoteW )) && RemoteW=$(( $RemoteW - 1 ))
  442. done
  443. # print comparison header
  444. if (( $are_all_in_sync ))
  445. then printf "\n$CGREEN$MARGIN_PAD$table_header_line$CEND\n"
  446. elif (( $n_results > 0 ))
  447. then printf "\n$MARGIN_PAD$table_header_line\n"
  448. else printf "\n$CRED$MARGIN_PAD$table_header_line$CEND$MARGIN_PAD"
  449. fi
  450. # pretty print divergence results
  451. if (( $n_results > 0 ))
  452. then local rule_w=$(( $LocalW + $BehindW + $AheadW + $RemoteW + $INNER_PADDING_W ))
  453. PrintHRule $rule_w
  454. for (( result_n = 0 ; result_n < $n_results ; ++result_n ))
  455. do PrintReportLine
  456. done
  457. PrintHRule $rule_w
  458. else ([ -z "$(GetRemoteRefs $remote_repo)" ] && printf "$CRED$NO_REFS_MSG$CEND\n") || \
  459. (!(( $are_all_in_sync )) && echo "$NO_RESULTS_MSG" )
  460. fi
  461. # print "synchronized" message if all compared upstreams had no divergence
  462. if (( $are_all_in_sync ))
  463. then local l_border="$DELIM " ; local r_border=" $DELIM" ;
  464. local borders_pad_w=$(( ${#l_border} + ${#r_border} ))
  465. local wrap_w=$(( $(CurrentTtyW) - $MARGINS_PAD_W - $borders_pad_w ))
  466. local line
  467. local rule_w=0
  468. # wrap message and determine box width
  469. synchronized_msg=$(echo "$synchronized_msg" | fold -s -w $wrap_w | tr ' ' "$JOIN_CHAR")
  470. for line in $synchronized_msg
  471. do line=${line/%$JOIN_CHAR/}
  472. [ ${#line} -gt $rule_w ] && rule_w=${#line}
  473. done
  474. rule_w=$(( $rule_w + $MARGINS_PAD_W ))
  475. # display message
  476. PrintHRule $rule_w
  477. for line in $synchronized_msg
  478. do line=${line/%$JOIN_CHAR/}
  479. local pad_w=$(( $rule_w - ${#line} - $MARGINS_PAD_W ))
  480. local line_fmt="$MARGIN_PAD$l_border$CEVEN$line$CEND%$(( $pad_w ))s$r_border"
  481. printf "$line_fmt\n" | sed "$JOIN_REGEX"
  482. done
  483. PrintHRule $rule_w
  484. fi
  485. Reset
  486. }
  487. PrintReportLine()
  488. {
  489. # select data
  490. local local_msg=$( echo ${LocalMsgs[ $result_n]} | sed "$JOIN_REGEX")
  491. local behind_msg=$(echo ${BehindMsgs[$result_n]:$trim_w} | sed "$JOIN_REGEX")
  492. local ahead_msg=$( echo ${AheadMsgs[ $result_n]:$trim_w} | sed "$JOIN_REGEX")
  493. local remote_msg=$(echo ${RemoteMsgs[$result_n]} | sed "$JOIN_REGEX")
  494. local end_msg ; local star ;
  495. local_msg="${local_msg:0:$LocalW}"
  496. remote_msg="${remote_msg:0:$RemoteW}"
  497. local local_color="${LocalColors[$result_n]}"
  498. local behind_color="${BehindColors[$result_n]}"
  499. local ahead_color="${AheadColors[$result_n]}"
  500. local remote_color="${RemoteColors[$result_n]}"
  501. # calculate column offsets
  502. local local_offset=1
  503. local behind_offset=$(( $LocalW - ${#local_msg} ))
  504. local ahead_offset=$(( $BehindW - ${#behind_msg} ))
  505. local remote_offset=$(( $AheadW - ${#ahead_msg} ))
  506. local end_offset=$(( $RemoteW - ${#remote_msg} ))
  507. # build output messages and display
  508. (( $(IsCurrentBranch $local_msg) )) && star=$STAR || star=" "
  509. local_msg="%$(( $local_offset ))s$star$(echo -e $DELIM $local_color$local_msg$CEND )"
  510. behind_msg="%$(( $behind_offset ))s $( echo -e $DELIM $behind_color$behind_msg$CEND)"
  511. ahead_msg="%$(( $ahead_offset ))s $( echo -e $DELIM $ahead_color$ahead_msg$CEND )"
  512. remote_msg="%$(( $remote_offset ))s $( echo -e $DELIM $remote_color$remote_msg$CEND)"
  513. end_msg="%$(( $end_offset ))s $DELIM"
  514. printf "$local_msg$behind_msg$ahead_msg$remote_msg$end_msg\n"
  515. }
  516. Reset()
  517. {
  518. WereAnyDivergences=0
  519. WereAnyCompared=0
  520. LocalW=0
  521. BehindW=0
  522. AheadW=0
  523. RemoteW=0
  524. LocalMsgs=()
  525. BehindMsgs=()
  526. AheadMsgs=()
  527. RemoteMsgs=()
  528. LocalColors=()
  529. BehindColors=()
  530. AheadColors=()
  531. RemoteColors=()
  532. }
  533. ## main entry ##
  534. Init $*
  535. FetchRemotes
  536. GenerateReports