13 Komitmen 50e922f4f3 ... d921093170

Pembuat SHA1 Pesan Tanggal
  bill-auger d921093170 re-format header and empty result 1 tahun lalu
  bill-auger 9683595b41 housekeeping 1 tahun lalu
  bill-auger 5d2e5a876b shorten hash 1 tahun lalu
  bill-auger 87cd11c545 denote merged/unmerged/unrelated 1 tahun lalu
  bill-auger 08039af8ab housekeeping 1 tahun lalu
  bill-auger 96f1bffdd4 accept remote branches 4 tahun lalu
  bill-auger a8ac062c44 add -a (list all) and -u (list unmerged only) options 11 bulan lalu
  bill-auger 9a8e730a04 housekeeping and bugfixes 6 tahun lalu
  bill-auger 50e922f4f3 accept remote branches 4 tahun lalu
  bill-auger cc8da1c22c stash 4 tahun lalu
  bill-auger 74917d4d48 stash 6 tahun lalu
  bill-auger e99d349830 squashme 6 tahun lalu
  bill-auger 3963cb996f housekeeping and bugfixes 6 tahun lalu
1 mengubah file dengan 251 tambahan dan 113 penghapusan
  1. 251 113
      git-graph

+ 251 - 113
git-graph

@@ -1,8 +1,8 @@
 #!/bin/bash
 
-#  git-graph - print pretty git branch sync status reports
+#  git-graph - print pretty git commit logs
 #
-#  Copyright 2016-2018 bill-auger <https://github.com/bill-auger>
+#  Copyright 2016-2018,2020,2023-2024 bill-auger <https://github.com/bill-auger>
 #
 #  git-graph is free software: you can redistribute it and/or modify
 #  it under the terms of the GNU General Public License as published by
@@ -17,10 +17,18 @@
 #  You should have received a copy of the GNU General Public License version 3
 #  along with git-graph.  If not, see <http://www.gnu.org/licenses/>.
 
+# USAGE: git-graph [ -a | -n <N_COMMITS> | -u ] [ branch_name | tag_name | commit_id | file ]
+
+
+## configuration ##
+
+# PUB_BRANCH eg: 'master', 'upstream/stable-1.0' - is remote tracking branch, if empty
+PUB_BRANCH=
+
 
 ## constants ##
 
-readonly USE_ANSI_COLOR=$( [[ "$1" == '--no-color' ]] && echo '0' || echo '1' )
+readonly DEF_HASH_LEN=7 # git may extend to the minimum unique prefix
 readonly JOIN_CHAR='~'
 readonly HRULE_CHAR='-'
 readonly GRAPH_REGEX="(.+)"
@@ -31,74 +39,125 @@ readonly SIG_REGEX="\[(([^\(\)]*) + *\(?.*\)? *<.*>|)\]"
 readonly STAT_REGEX="\[(.)\]"
 readonly MSG_REGEX="(.*)"
 readonly REFS_REGEX="\((.*)\)"
-readonly GIT_LOG_FMT="git log --graph --date=short -n %d --pretty=format:"
-readonly LOG_FMT="%h${JOIN_CHAR}%ad${JOIN_CHAR}%an${JOIN_CHAR}[%GS]${JOIN_CHAR}[%G?]${JOIN_CHAR}%s${JOIN_CHAR}(%D)"
+readonly GIT_LOG_FMT="%%h${JOIN_CHAR}%%ad${JOIN_CHAR}%%an${JOIN_CHAR}[%%GS]${JOIN_CHAR}[%%G?]${JOIN_CHAR}%%s${JOIN_CHAR}(%%D)"
+readonly GIT_LOG_CMD_FMT="git log --graph --date=short -n %d --pretty=format:$GIT_LOG_FMT --abbrev=${DEF_HASH_LEN}"
 readonly LOG_REGEX="^${GRAPH_REGEX} ${ID_REGEX}${JOIN_CHAR}${DATE_REGEX}${JOIN_CHAR}${AUTHOR_REGEX}${JOIN_CHAR}${SIG_REGEX}${JOIN_CHAR}${STAT_REGEX}${JOIN_CHAR}${MSG_REGEX}${JOIN_CHAR}${REFS_REGEX}$"
-readonly CWHITE=$(  (( $USE_ANSI_COLOR )) && echo '\033[0;37m')
-readonly CGREEN=$(  (( $USE_ANSI_COLOR )) && echo '\033[0;32m')
-readonly CYELLOW=$( (( $USE_ANSI_COLOR )) && echo '\033[0;33m')
-readonly CRED=$(    (( $USE_ANSI_COLOR )) && echo '\033[0;31m')
-readonly CBLUE=$(   (( $USE_ANSI_COLOR )) && echo '\033[1;34m')
-readonly CEND=$(    (( $USE_ANSI_COLOR )) && echo '\033[0m'   )
+readonly CWHITE='\033[0;37m'
+readonly CGREEN='\033[0;32m'
+readonly CYELLOW='\033[0;33m'
+readonly CRED='\033[0;31m'
+readonly CAQUA='\033[1;36m'
+readonly CEND='\033[0m'
 readonly CGOOD=$CGREEN
 readonly CUNKNOWN=$CYELLOW
 readonly CEXPIRED=$CYELLOW
 readonly CBAD=$CRED
 readonly CNONE=$CWHITE
-readonly ID_COLOR=$CNONE
+readonly HASH_COLOR=$CNONE
 readonly DATE_COLOR=$CNONE
 readonly AUTHOR_COLOR=$CNONE
 readonly MSG_COLOR=$CNONE
-readonly REF_COLOR=$CBLUE
-#(deferred) readonly N_COMMITS
-#(deferred) readonly REF
+readonly REF_COLOR=$CAQUA
+readonly REF_ERR_MSG="no such ref or file:"
+declare -i USE_ANSI_COLOR=1 # (deferred)
+declare -i N_COMMITS=12     # (deferred)
+declare -i HIDE_MERGED=0    # (deferred)
+REF=                        # (deferred)
+FILE=                       # (deferred)
 
 
 ## variables ##
 
-declare -a graphs=()
-declare -a ids=()
-declare -a dates=()
-declare -a authors=()
-declare -a sigs=()
-declare -a msgs=()
-declare -a refs=()
-author_w=0
+declare -a Graphs=()
+declare -a Ids=()
+declare -a Dates=()
+declare -a Authors=()
+declare -a Sigs=()
+declare -a Stats=()
+declare -a Msgs=()
+declare -a Refs=()
+declare -a SigColors=()
+declare -i AuthorW=0
+declare -i NCommits=0
 
 
 ## helpers ##
 
-DoesBranchExist() # (branch_name)
+GetUpstreamBranch() # ( local_branch )
+{
+  local local_branch=$1
+
+  git rev-parse --abbrev-ref $local_branch@{upstream} 2> /dev/null
+}
+
+GetCurrentBranch()
+{
+  git rev-parse --abbrev-ref HEAD
+}
+
+DoesBranchExist() # ( branch_name )
 {
   local branch_name=$1
 
-  [ "$branch_name" -a "$(git branch --all --list $branch_name)" ] && echo 1 || echo 0
+  [[ "$branch_name" && "$(git branch --all --list $branch_name)" ]]
 }
 
-DoesTagExist() # (tag_name)
+DoesTagExist() # ( tag_name )
 {
   local tag_name=$1
 
-  [ "$tag_name" -a "$(git tag | grep -G "$tag_name$")" ] && echo 1 || echo 0
+  [[ "$tag_name" && "$(git tag | grep -G "$tag_name$")" ]]
+}
+
+DoesCidExist() # ( tag_name )
+{
+  local commit_id=$1
+
+  [[ "$commit_id" ]] && git rev-parse --verify ${commit_id}^{commit} &> /dev/null
 }
 
-ValidateRef() # (ref) # where ref is a branch_name, tag_name, or file_name
+ValidateRef() # ( ref ) # where param is a branch_name, tag_name, commit_id
 {
   local ref=$1
 
-  (( $(DoesBranchExist $ref) )) || (( $(DoesTagExist $ref) )) || [ -f "$ref" ] || ref=''
+  DoesBranchExist $ref || DoesTagExist $ref || DoesCidExist $ref || ref=''
 
   echo $ref ; [[ -n "$ref" ]] ;
 }
 
-JoinChars() # (a_string)
+ValidateParam() # ( ref ) # where param is a branch_name, tag_name, commit_id, or file
 {
-  local a_string=$1
+  local ref=$1
+
+  ref="$( ValidateRef $ref || [[ ! -f "$ref" ]] || echo $ref )"
+
+  echo $ref ; [[ -n "$ref" ]] ;
+}
+
+IsAncestor() # ( ref_a ref_b )
+{
+  local ref_a=$1
+  local ref_b=$2
+
+  git merge-base --is-ancestor $ref_a $ref_b
+}
+
+Ancestor() # ( ref_a ref_b )
+{
+  local ref_a=$1
+  local ref_b=$2
+
+  git merge-base $ref_a $ref_b
+}
+
+JoinChars() # ( "a_spaced_string" )
+{
+  local a_string="$1"
 
   echo "${a_string// /$JOIN_CHAR}"
 }
 
-FilterJoinChars() # (a_string)
+FilterJoinChars() # ( an_unspaced_string )
 {
   local a_string=$1
 
@@ -106,86 +165,165 @@ FilterJoinChars() # (a_string)
 }
 
 
-## main entry ##
+## business ##
+
+Init() # ( cli_args* )
+{
+  local arg
+  local valid_param
+  local valid_ref
+  local is_valid_param
+  local is_valid_ref
+  local is_file
+
+  # parse cli args
+  while getopts 'acn:u' arg
+  do    case "${arg}" in
+             a) N_COMMITS="$(git rev-list --count HEAD)" ;;
+             c) USE_ANSI_COLOR=0                         ;;
+             n) N_COMMITS="${OPTARG}"                    ;;
+             u) HIDE_MERGED=1                            ;;
+             *) echo "Invalid argument: '${arg}'"        ;;
+        esac
+  done
+  shift $(( OPTIND - 1 ))
+
+  # process cli args
+  valid_param=$(ValidateParam "$1")
+  valid_ref=$(  ValidateRef   "$1")
+  is_valid_param=$( [[ -n "$valid_param"                ]] ; echo $((!$?)) ; )
+  is_valid_ref=$(   [[ -n "$valid_ref"                  ]] ; echo $((!$?)) ; )
+  is_file=$(        (( is_valid_param && ! is_valid_ref )) ; echo $((!$?)) ; )
+  REF=$( (( is_file || ! $# )) && echo HEAD         || echo $valid_ref)
+  FILE=$((( is_file         )) && echo $valid_param || echo $2        )
+  PUB_BRANCH=${PUB_BRANCH:-$(GetUpstreamBranch $(GetCurrentBranch))}
+
+  readonly USE_ANSI_COLOR
+  readonly N_COMMITS
+  readonly HIDE_MERGED
+  readonly REF
+  readonly FILE
+  readonly PUB_BRANCH
+
+
+# echo "is_valid_param=$is_valid_param is_valid_ref=$is_valid_ref is_file=$is_file REF=$REF FILE=$FILE" # DEBUG
+
+
+  (( ! $# )) || (( is_valid_param )) || ! echo "$REF_ERR_MSG $1"
+}
+
+CompileResults()
+{
+  local log_data graph id date author sig stat msg ref
+
+  # reset data
+  Graphs=() Ids=() Dates=() Authors=() Sigs=() Stats=() Msgs=() Refs=() SigColors=()
+
+  # compile results
+  while read -r log_data
+  do    [[ $log_data =~ $LOG_REGEX ]] || continue
+
 
-# parse cli args
-while getopts 'an:u' arg
-do    case "${arg}" in
-           a) n_commits="$(git rev-list --count HEAD)" ;;
-           n) n_commits="${OPTARG}"                    ;;
-           u) hide_merged=1                            ;;
-           *) echo "Invalid argument: '${arg}'"        ;;
-      esac
-done
-
-ref_param=$(ValidateRef "${!OPTIND}")
-[ -n "${!OPTIND}" ] && [ -z "$ref_param" ] && echo "no such branch, tag, or file: '${!OPTIND}'"
-readonly N_COMMITS=${n_commits:-12}
-# readonly REF=${ref_param}
-readonly REF=$( (( ${hide_merged} )) && [ -n "${ref_param}" ] && echo "${ref_param}..HEAD" || \
-                                                                 echo "${ref_param}"          )
-
-# compile results
-while read -r commit
-do
 # TODO: graph colors and fork/merge node lines
-# printf "commit='%s'\n" "$commit" ; [[ $commit =~ $LOG_REGEX ]] && printf "graph='%s'\n" "${BASH_REMATCH[1]}" || printf "graph NFG='%s'\n" "$commit"
-
-[[ $commit =~ $LOG_REGEX ]] || continue
-
-   graph=${BASH_REMATCH[  1]} ; graphs=( ${graphs[@]}  $(JoinChars "$graph" )) ;
-   id=${BASH_REMATCH[     2]} ; ids=(    ${ids[@]}     $(JoinChars "$id"    )) ;
-   date=${BASH_REMATCH[   3]} ; dates=(  ${dates[@]}   $(JoinChars "$date"  )) ;
-   author=${BASH_REMATCH[ 4]} ; authors=(${authors[@]} $(JoinChars "$author")) ;
-   sig=${BASH_REMATCH[    6]} ; sigs=(   ${sigs[@]}    $(JoinChars "$sig"   )) ;
-   stat=${BASH_REMATCH[   7]} ; stats=(  ${stats[@]}   $(JoinChars "$stat"  )) ;
-   msg=${BASH_REMATCH[    8]} ; msgs=(   ${msgs[@]}    $(JoinChars "$msg"   )) ;
-   ref=${BASH_REMATCH[    9]} ; refs=(   ${refs[@]}    $(JoinChars "$ref"   )) ;
-   [ "$msg"         ] ||        msgs=(   ${msgs[@]}    "<EMPTY>"             )
-   [ "$stat" == 'E' ] &&        sigs=(   ${sigs[@]}    "<UNKNOWN>"           ) || \
-   [ "$sig"         ] ||        sigs=(   ${sigs[@]}    "$JOIN_CHAR"          )
-   [ "$ref"         ] ||        refs=(   ${refs[@]}    "$JOIN_CHAR"          )
-
-   case $stat in
-        'G') sig_colors=(${sig_colors[@]} "$CGOOD"   ) ;; # good signature
-        'X') sig_colors=(${sig_colors[@]} "$CGOOD"   ) ;; # good signature that has expired
-        'U') sig_colors=(${sig_colors[@]} "$CUNKNOWN") ;; # good signature with unknown trust
-        'E') sig_colors=(${sig_colors[@]} "$CUNKNOWN") ;; # cannot be checked (e.g. missing key)
-        'B') sig_colors=(${sig_colors[@]} "$CBAD"    ) ;; # bad signature
-        'Y') sig_colors=(${sig_colors[@]} "$CEXPIRED") ;; # good signature made by an expired key
-        'R') sig_colors=(${sig_colors[@]} "$CBAD"    ) ;; # good signature made by a revoked key
-        'N') sig_colors=(${sig_colors[@]} "$CNONE"   ) ;; # no signature
-   esac
-
-   [ ${#author} -gt $author_w ] && author_w=${#author}
-# done < <($(printf "$GIT_LOG_FMT" $N_COMMITS)"$LOG_FMT" $REF)
-done < <(echo "$($(printf "$GIT_LOG_FMT" $N_COMMITS)"$LOG_FMT" $REF)")
-
-# pretty print results
-for (( result_n = 0 ; result_n < ${#ids[@]} ; result_n++ ))
-do graph=${graphs[$result_n]}
-   id=${ids[$result_n]}
-   date=${dates[$result_n]}
-   author=${authors[$result_n]}
-   sig=${sigs[$result_n]}
-   stat=${stats[$result_n]}
-   msg=${msgs[$result_n]}
-   ref=${refs[$result_n]}
-   sig_color=${sig_colors[$result_n]}
-   pad="%$(( $author_w - ${#author} ))s"
-
-  [ "$ID_COLOR"         ] && id_color=$ID_COLOR      || id_color=$sig_color
-  [ "$DATE_COLOR"       ] && date_color=$DATE_COLOR  || date_color=$sig_color
-  [ "$author" == "$sig" ] && author_color=$sig_color || author_color=$AUTHOR_COLOR
-  [ "$MSG_COLOR"        ] && msg_color=$MSG_COLOR    || msg_color=$sig_color
-  [ "$REF_COLOR"        ] && ref_color=$REF_COLOR    || ref_color=$sig_color
-
-#    printf "$graph_color$(FilterJoinChars $graph) $CEND"
-   printf "$id_color$id $CEND"
-   printf "$date_color$date $CEND"
-   printf "$author_color%s$CEND" "$(FilterJoinChars $author)"
-   printf "$msg_color:$pad %s$CEND" '' "$(FilterJoinChars $msg)"
-   [ "$ref" != "$JOIN_CHAR" ] && printf " $ref_color($(FilterJoinChars $ref))$CEND"
-   [ "$sig" != "$JOIN_CHAR" ] && printf " $sig_color[$(FilterJoinChars $sig)]$CEND"
-   printf "\n"
-done
+# printf "log_data='%s'\n" "$log_data" ; [[ $log_data =~ $LOG_REGEX ]] && printf "graph='%s'\n" "${BASH_REMATCH[1]}" || printf "graph NFG='%s'\n" "$log_data"
+
+
+        graph=${BASH_REMATCH[  1]} ; Graphs=( ${Graphs[*]}  $(JoinChars "$graph" )) ;
+        id=${BASH_REMATCH[     2]} ; Ids=(    ${Ids[*]}     $(JoinChars "$id"    )) ;
+        date=${BASH_REMATCH[   3]} ; Dates=(  ${Dates[*]}   $(JoinChars "$date"  )) ;
+        author=${BASH_REMATCH[ 4]} ; Authors=(${Authors[*]} $(JoinChars "$author")) ;
+        sig=${BASH_REMATCH[    6]} ; Sigs=(   ${Sigs[*]}    $(JoinChars "$sig"   )) ;
+        stat=${BASH_REMATCH[   7]} ; Stats=(  ${Stats[*]}   $(JoinChars "$stat"  )) ;
+        msg=${BASH_REMATCH[    8]} ; Msgs=(   ${Msgs[*]}    $(JoinChars "$msg"   )) ;
+        ref=${BASH_REMATCH[    9]} ; Refs=(   ${Refs[*]}    $(JoinChars "$ref"   )) ;
+        [[ -n "$msg"      ]] ||      Msgs=(   ${Msgs[*]}    "<EMPTY>"             )
+        [[ "$stat" == 'E' ]] &&      Sigs=(   ${Sigs[*]}    "<UNKNOWN>"           ) || \
+        [[ -n "$sig"      ]] ||      Sigs=(   ${Sigs[*]}    "$JOIN_CHAR"          )
+        [[ -n "$ref"      ]] ||      Refs=(   ${Refs[*]}    "$JOIN_CHAR"          )
+
+        (( ${#author} > AuthorW )) && AuthorW=${#author}
+  done
+}
+
+PrintReport() # ( "header" )
+{
+  local header="$1"
+  local n_results=$(( ${#Ids[*]} ))
+  local pad_w=$(( ( -${#header} + ${#Ids[0]} + 1 + ${#Dates[0]} + AuthorW ) / 2 ))
+  local pad="$(printf "%${pad_w}s" ' ' | tr ' ' "$HRULE_CHAR")"
+  local hrule="|<${pad} ${header} ${pad:$(( pad_w > 0 && ! ( AuthorW % 2 ) ))}>|"
+
+  local result_n graph id date author sig stat msg ref pad
+  local has_author_sig sig_color hash_color date_color author_color msg_color ref_color
+
+  # pretty print results
+  (( ! HIDE_MERGED )) && echo "${hrule}"
+  (( ! n_results   )) && sed 's/[^|]/ /g ; s/^|       /| <None>/' <<<"${hrule}"
+  for (( result_n = 0 ; result_n < n_results ; ++result_n ))
+  do  graph=${Graphs[$result_n]}
+      id=${Ids[$result_n]}
+      date=${Dates[$result_n]}
+      author=${Authors[$result_n]}
+      sig=${Sigs[$result_n]}
+      stat=${Stats[$result_n]}
+      msg=${Msgs[$result_n]}
+      ref=${Refs[$result_n]}
+      pad=$(printf "%$(( AuthorW - ${#author} ))s" '')
+
+      if   (( USE_ANSI_COLOR ))
+      then has_author_sig=$([[ "$author" == "$sig" ]] ; echo $((!$?)) ;)
+           sig_color=$(case "$stat" in
+                            'G') echo $CGOOD    ;; # good signature
+                            'X') echo $CEXPIRED ;; # good signature that has expired
+                            'U') echo $CGOOD    ;; # good signature with unknown trust
+                            'E') echo $CUNKNOWN ;; # cannot be checked (e.g. missing key)
+                            'B') echo $CBAD     ;; # bad signature
+                            'Y') echo $CEXPIRED ;; # good signature made by an expired key
+                            'R') echo $CBAD     ;; # good signature made by a revoked key
+                            'N') echo $CNONE    ;; # no signature
+                       esac)
+           hash_color=$HASH_COLOR
+           date_color=$DATE_COLOR
+           author_color=$((( has_author_sig )) && echo $sig_color || echo $AUTHOR_COLOR)
+           msg_color=$MSG_COLOR
+           ref_color=$REF_COLOR
+      fi
+
+#     printf "$graph_color$(FilterJoinChars $graph) $CEND"
+      printf "| $hash_color$id$CEND"
+      printf " $date_color$date$CEND"
+      printf " $author_color%s$CEND"    "$(FilterJoinChars $author)"
+      printf " $pad| $msg_color%s$CEND" "$(FilterJoinChars $msg   )"
+      [[ "$ref" != "$JOIN_CHAR" ]] && printf " $ref_color($(FilterJoinChars $ref))$CEND"
+      [[ "$sig" != "$JOIN_CHAR" ]] && printf " $sig_color[$(FilterJoinChars $sig)]$CEND"
+      printf "\n"
+  done
+
+  NCommits=$(( NCommits + ${#Ids[*]} ))
+}
+
+Main()
+{
+  local ancestor=$(Ancestor $PUB_BRANCH $REF)
+  local log_cmd header
+
+  if   [[ -z "$PUB_BRANCH" ]]
+  then log_cmd="$(printf "$GIT_LOG_CMD_FMT" $N_COMMITS)" header='NO UPSTREAM'
+       CompileResults < <($log_cmd              $REF $FILE ; echo ;) ; PrintReport "${header}" ;
+  elif ! IsAncestor $PUB_BRANCH $REF && [[ -z ${ancestor:-} ]]
+  then log_cmd="$(printf "$GIT_LOG_CMD_FMT" $N_COMMITS)" header='UNRELATED'
+       CompileResults < <($log_cmd              $REF $FILE ; echo ;) ; PrintReport ${header} ;
+  else log_cmd="$(printf "$GIT_LOG_CMD_FMT" $N_COMMITS)" header='UNMERGED'
+       CompileResults < <($log_cmd $ancestor..$REF $FILE ; echo ;) ; PrintReport ${header} ;
+
+       if   (( ! HIDE_MERGED && NCommits < N_COMMITS ))
+       then log_cmd="$(printf "$GIT_LOG_CMD_FMT" $(( N_COMMITS - NCommits )))" header='MERGED'
+            CompileResults < <($log_cmd $ancestor $FILE  ; echo ;) ; PrintReport ${header} ;
+       fi
+  fi
+}
+
+
+## main entry ##
+
+Init "$@" && Main