From d90d98f00d7d28638ef3fea3cb5caea26c18bf95 Mon Sep 17 00:00:00 2001 From: bill auger Date: Apr 20 2016 10:51:38 +0000 Subject: [PATCH 1/30] make terminal width calculation more robust --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 2ad42e7..e2d6014 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -11,11 +11,14 @@ # '+' character indicates that some changes are staged for commit # '$' character indicates that a stash exists # [n<-->n] indicates the number of commits behind and ahead of upstream -# example usage: +# usage: # source ~/bin/git-status-prompt/git-status-prompt.sh -# PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;36m\]\w/\[\033[00m\]\[\033[00;32m\]\$(GitStatusPrompt)\[\033[00m\]\n\$ " +# PS1="\$(GitStatusPrompt)" +readonly PROMPT_HEAD='\033[01;32m'$USER@$HOSTNAME'\033[00m:\033[01;36m' +readonly PROMPT_MID='\033[00m\033[00;32m' +readonly PROMPT_TAIL='\033[00m\n$ ' readonly DIRTY_CHAR="*" readonly TRACKED_CHAR="!" readonly UNTRACKED_CHAR="?" @@ -37,6 +40,7 @@ readonly BEHIND_COLOR=$RED readonly AHEAD_COLOR=$YELLOW readonly EVEN_COLOR=$GREEN readonly ANSI_FILTER_REGEX="s/\\\033\[([0-9]{1,2}(;[0-9]{1,2})?)?m//g" +readonly TIMESTAMP_LEN=10 # helpers @@ -84,10 +88,7 @@ function SyncStatus [ $(($?)) -eq 0 ] && echo $status } - -# main entry point - -function GitStatusPrompt +function GitStatus { # ensure we are in a valid git repository with commits [ ! $(AssertIsValidRepo) ] && return @@ -99,7 +100,7 @@ function GitStatusPrompt while read local_branch remote_branch do # filter branches by name - [ "$current_branch" == "$local_branch" ] || continue + [ "$current_branch" != "$local_branch" ] && continue # set branch color based on dirty status if [ -z "$(HasAnyChanges)" ] ; then branch_color=$CLEAN_COLOR ; else branch_color=$DIRTY_COLOR ; fi ; @@ -128,6 +129,7 @@ function GitStatusPrompt stashed=$(HasStashedChanges) # build output + current_dir="$(pwd)" open_paren="$branch_color($END" close_paren="$branch_color)$END" open_bracket="$branch_color[$END" @@ -144,16 +146,28 @@ function GitStatusPrompt branch_status_msg=$open_paren$branch_msg$status_msg$upstream_msg$close_paren # append last commit message trunctuated to console width - current_dir=$(pwd) - status_msg_filtered=$(echo $branch_status_msg | sed -r $ANSI_FILTER_REGEX --) + status_msg=$(echo $branch_status_msg | sed -r $ANSI_FILTER_REGEX --) author_date=$(git log -n 1 --format=format:"%ai" $1 2> /dev/null) - commit_msg=$(git log -n 1 --format=format:\"%s\" | sed -r "s/\"//g") + commit_log=$( git log -n 1 --format=format:\"%s\" | sed -r "s/\"//g") + commit_msg=" ${author_date:0:TIMESTAMP_LEN} $commit_log" current_tty_w=$(($(stty -F /dev/tty size | cut -d ' ' -f2))) - prompt_len=$((${#USER} + 1 + ${#HOSTNAME} + 1 + ${#current_dir})) - status_msg_len=${#status_msg_filtered} - timestamp_len=10 - n_chars=$(($current_tty_w - $prompt_len - $status_msg_len - $timestamp_len - 4)) - echo -e "$branch_status_msg ${author_date:0:10} ${commit_msg:0:$n_chars}" + prompt_msg_len=$((${#USER} + 1 + ${#HOSTNAME} + 1 + ${#current_dir} + 1 + ${#status_msg})) + prompt_msg_mod=$(($prompt_msg_len % $current_tty_w)) + commit_msg_len=$(($current_tty_w - $prompt_msg_mod)) + min_len=$(($TIMESTAMP_LEN + 1)) + max_len=$(($current_tty_w - 1)) + [ $commit_msg_len -lt $min_len -o $commit_msg_len -gt $max_len ] && commit_msg_len=0 + commit_msg=${commit_msg:0:commit_msg_len} + + echo "$branch_status_msg$commit_msg" done < <(git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads) } + + +# main entry point + +function GitStatusPrompt +{ + echo -e "$PROMPT_HEAD$(pwd)/$PROMPT_MID$(GitStatus)$PROMPT_TAIL" +} From fb57dd4067fb77885e5003695b4ce4d4d8981aca Mon Sep 17 00:00:00 2001 From: bill-auger Date: Aug 17 2016 09:07:17 +0000 Subject: [PATCH 2/30] add 'merge', 'rebase', and 'detached' indicators --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index e2d6014..ceb484b 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -1,7 +1,7 @@ #!/bin/bash # git-status-prompt.sh -# Copyright 2013-2016 http://github.com/bill-auger +# Copyright 2013-2016 bill-auger # this script formats git branch name plus dirty and sync status for appending to bash prompt # format is: (branch-name status-indicators [divergence]) last-commit-message @@ -96,7 +96,21 @@ function GitStatus current_branch=`git rev-parse --abbrev-ref HEAD` ; [ $current_branch ] || return ; - # loop over all branches + # detect detached HEAD state and abort + if [ -f $(pwd)/.git/MERGE_HEAD ] && [ ! -z "`cat .git/MERGE_MSG | grep -E '^Merge'`" ] + then merge_msg=`cat .git/MERGE_MSG | grep -E "^Merge (.*)branch '" | \ + sed -e "s/^Merge \(.*\)branch '\(.*\)' into \(.*\)$/\2 into \3/"` + echo "$UNTRACKED_COLOR(merging $merge_msg)$END" ; return ; + elif [ -d $(pwd)/.git/rebase-apply/ ] || [ -d $(pwd)/.git/rebase-merge/ ] + then rebase_dir=`ls -d .git/rebase-* | sed -e "s/^.git\/rebase-\(.*\)$/.git\/rebase-\1/"` + this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` + their_commit=`cat $rebase_dir/onto` + echo "$UNTRACKED_COLOR(rebasing $this_branch onto $their_commit)$END" ; return ; + elif [ "$current_branch" == "HEAD" ] + then echo "$UNTRACKED_COLOR(detached)$END" ; return ; + fi + + # loop over all branches to find remote tracking branch while read local_branch remote_branch do # filter branches by name From 57853b7e695a34cf44e5c96896fbf01e7ac20512 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Oct 03 2016 01:43:04 +0000 Subject: [PATCH 3/30] indicate 'tag' and 'commit' merge tartgets --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index ceb484b..324d6ce 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -98,9 +98,9 @@ function GitStatus # detect detached HEAD state and abort if [ -f $(pwd)/.git/MERGE_HEAD ] && [ ! -z "`cat .git/MERGE_MSG | grep -E '^Merge'`" ] - then merge_msg=`cat .git/MERGE_MSG | grep -E "^Merge (.*)branch '" | \ - sed -e "s/^Merge \(.*\)branch '\(.*\)' into \(.*\)$/\2 into \3/"` - echo "$UNTRACKED_COLOR(merging $merge_msg)$END" ; return ; + then merge_msg=`cat .git/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ + sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)'\( into .*\)\?$/ \3\4/"` + echo "$UNTRACKED_COLOR(merging$merge_msg)$END" ; return ; elif [ -d $(pwd)/.git/rebase-apply/ ] || [ -d $(pwd)/.git/rebase-merge/ ] then rebase_dir=`ls -d .git/rebase-* | sed -e "s/^.git\/rebase-\(.*\)$/.git\/rebase-\1/"` this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` From 745b4f6cc00b306263372d65d410090510cfd732 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Jan 01 2017 05:31:49 +0000 Subject: [PATCH 4/30] accommodate whitespace in repo dirname when rebasing and merging --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 324d6ce..7e9be99 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -16,20 +16,23 @@ # PS1="\$(GitStatusPrompt)" -readonly PROMPT_HEAD='\033[01;32m'$USER@$HOSTNAME'\033[00m:\033[01;36m' -readonly PROMPT_MID='\033[00m\033[00;32m' -readonly PROMPT_TAIL='\033[00m\n$ ' +readonly RED='\033[1;31m' +readonly YELLOW='\033[01;33m' +readonly GREEN='\033[00;32m' +readonly LIME='\033[01;32m' +readonly PURPLE='\033[00;35m' +readonly BLUE='\033[00;36m' +readonly AQUA='\033[01;36m' +readonly END='\033[00m' +readonly PROMPT_HEAD=$PURPLE$USER@$HOSTNAME$END':'$BLUE +readonly PROMPT_MID=$END$GREEN +readonly PROMPT_TAIL=$END'\n$ ' readonly DIRTY_CHAR="*" readonly TRACKED_CHAR="!" readonly UNTRACKED_CHAR="?" readonly STAGED_CHAR="+" readonly STASHED_CHAR="$" readonly GIT_CLEAN_MSG_REGEX="nothing to commit,? (?working directory clean)?" -readonly GREEN='\033[0;32m' -readonly LIME='\033[1;32m' -readonly YELLOW='\033[1;33m' -readonly RED='\033[1;31m' -readonly END='\033[0m' readonly CLEAN_COLOR=$GREEN readonly DIRTY_COLOR=$YELLOW readonly TRACKED_COLOR=$YELLOW @@ -97,11 +100,11 @@ function GitStatus current_branch=`git rev-parse --abbrev-ref HEAD` ; [ $current_branch ] || return ; # detect detached HEAD state and abort - if [ -f $(pwd)/.git/MERGE_HEAD ] && [ ! -z "`cat .git/MERGE_MSG | grep -E '^Merge'`" ] + if [ -f "$(pwd)/.git/MERGE_HEAD" ] && [ ! -z "`cat .git/MERGE_MSG | grep -E '^Merge'`" ] then merge_msg=`cat .git/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)'\( into .*\)\?$/ \3\4/"` echo "$UNTRACKED_COLOR(merging$merge_msg)$END" ; return ; - elif [ -d $(pwd)/.git/rebase-apply/ ] || [ -d $(pwd)/.git/rebase-merge/ ] + elif [ -d "$(pwd)/.git/rebase-apply/" ] || [ -d "$(pwd)/.git/rebase-merge/" ] then rebase_dir=`ls -d .git/rebase-* | sed -e "s/^.git\/rebase-\(.*\)$/.git\/rebase-\1/"` this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` their_commit=`cat $rebase_dir/onto` From e056e91413c2642a711e3927263b881d9ff43722 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Jan 31 2017 02:54:09 +0000 Subject: [PATCH 5/30] add README --- diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fce391 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +git-status-prompt.sh - pretty format git sync and dirty status for shell prompt + +![git-status-prompt screenshot][scrot] + +``` +FORMAT: + (branch-name status-indicators [divergence]) last-commit-date last-commit-message + where: + '*' character indicates that the working tree differs from HEAD (per .gitignore) + '!' character indicates that some tracked files have changed + '?' character indicates that some new or untracked files exist + '+' character indicates that some changes are staged for commit + '$' character indicates that a stash exists + [n<-->n] indicates the number of commits behind and ahead of upstream + +USAGE: + # ~/.bashrc + source /path/to/git-status-prompt/git-status-prompt.sh + PS1="\$(GitStatusPrompt)" +``` + + +[scrot]: http://bill-auger.github.io/git-status-prompt-scrot.png "git-status-prompt screenshot" diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 7e9be99..7bba600 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -1,18 +1,35 @@ #!/bin/bash -# git-status-prompt.sh -# Copyright 2013-2016 bill-auger - -# this script formats git branch name plus dirty and sync status for appending to bash prompt -# format is: (branch-name status-indicators [divergence]) last-commit-message -# '*' character indicates that the working tree differs from HEAD -# '!' character indicates that some tracked files have changed -# '?' character indicates that some new or untracked files exist -# '+' character indicates that some changes are staged for commit -# '$' character indicates that a stash exists -# [n<-->n] indicates the number of commits behind and ahead of upstream -# usage: -# source ~/bin/git-status-prompt/git-status-prompt.sh + +# git-status-prompt.sh - pretty format git sync and dirty status for shell prompt +# Copyright 2013-2017 bill-auger +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# FORMAT: +# (branch-name status-indicators [divergence]) last-commit-date last-commit-message +# where: +# '*' character indicates that the working tree differs from HEAD (per .gitignore) +# '!' character indicates that some tracked files have changed +# '?' character indicates that some new or untracked files exist +# '+' character indicates that some changes are staged for commit +# '$' character indicates that a stash exists +# [n<-->n] indicates the number of commits behind and ahead of upstream +# +# USAGE: +# source /path/to/git-status-prompt/git-status-prompt.sh # PS1="\$(GitStatusPrompt)" From 64dc93dfe963881d7268feeb36fcccdeb2722496 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Jun 22 2017 11:18:12 +0000 Subject: [PATCH 6/30] fix sed bug in merge --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 7bba600..518d5f8 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -119,7 +119,8 @@ function GitStatus # detect detached HEAD state and abort if [ -f "$(pwd)/.git/MERGE_HEAD" ] && [ ! -z "`cat .git/MERGE_MSG | grep -E '^Merge'`" ] then merge_msg=`cat .git/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ - sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)'\( into .*\)\?$/ \3\4/"` + sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/"` + echo "$UNTRACKED_COLOR(merging$merge_msg)$END" ; return ; elif [ -d "$(pwd)/.git/rebase-apply/" ] || [ -d "$(pwd)/.git/rebase-merge/" ] then rebase_dir=`ls -d .git/rebase-* | sed -e "s/^.git\/rebase-\(.*\)$/.git\/rebase-\1/"` From e9e640144111b977ffc1266310fcd06f1a61d4bb Mon Sep 17 00:00:00 2001 From: bill-auger Date: Jun 22 2017 11:18:24 +0000 Subject: [PATCH 7/30] color su login@host red --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 518d5f8..93e8ca2 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -1,6 +1,5 @@ #!/bin/bash - # git-status-prompt.sh - pretty format git sync and dirty status for shell prompt # Copyright 2013-2017 bill-auger # @@ -41,7 +40,8 @@ readonly PURPLE='\033[00;35m' readonly BLUE='\033[00;36m' readonly AQUA='\033[01;36m' readonly END='\033[00m' -readonly PROMPT_HEAD=$PURPLE$USER@$HOSTNAME$END':'$BLUE +mkdir /deleteme 2> /dev/null && rmdir /deleteme/ && LOGIN_COLOR=$RED || LOGIN_COLOR=$PURPLE +readonly PROMPT_HEAD=$LOGIN_COLOR`whoami`@$HOSTNAME$END':'$BLUE readonly PROMPT_MID=$END$GREEN readonly PROMPT_TAIL=$END'\n$ ' readonly DIRTY_CHAR="*" From bfe4803e99cab7c6739b375f7f7960ed26024b39 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Jun 22 2017 16:36:39 +0000 Subject: [PATCH 8/30] update README --- diff --git a/README.md b/README.md index 3fce391..5b50c0c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -git-status-prompt.sh - pretty format git sync and dirty status for shell prompt +### git-status-prompt.sh - pretty format git sync and dirty status for shell prompt ![git-status-prompt screenshot][scrot] From 79ded9bac2af7249394453f6bbc7cb755b948df6 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Jul 30 2017 03:04:24 +0000 Subject: [PATCH 9/30] refactor --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 93e8ca2..ea99fa7 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -39,11 +39,8 @@ readonly LIME='\033[01;32m' readonly PURPLE='\033[00;35m' readonly BLUE='\033[00;36m' readonly AQUA='\033[01;36m' -readonly END='\033[00m' +readonly CEND='\033[00m' mkdir /deleteme 2> /dev/null && rmdir /deleteme/ && LOGIN_COLOR=$RED || LOGIN_COLOR=$PURPLE -readonly PROMPT_HEAD=$LOGIN_COLOR`whoami`@$HOSTNAME$END':'$BLUE -readonly PROMPT_MID=$END$GREEN -readonly PROMPT_TAIL=$END'\n$ ' readonly DIRTY_CHAR="*" readonly TRACKED_CHAR="!" readonly UNTRACKED_CHAR="?" @@ -121,14 +118,14 @@ function GitStatus then merge_msg=`cat .git/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/"` - echo "$UNTRACKED_COLOR(merging$merge_msg)$END" ; return ; + echo "$UNTRACKED_COLOR(merging$merge_msg)$CEND" ; return ; elif [ -d "$(pwd)/.git/rebase-apply/" ] || [ -d "$(pwd)/.git/rebase-merge/" ] then rebase_dir=`ls -d .git/rebase-* | sed -e "s/^.git\/rebase-\(.*\)$/.git\/rebase-\1/"` this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` their_commit=`cat $rebase_dir/onto` - echo "$UNTRACKED_COLOR(rebasing $this_branch onto $their_commit)$END" ; return ; + echo "$UNTRACKED_COLOR(rebasing $this_branch onto $their_commit)$CEND" ; return ; elif [ "$current_branch" == "HEAD" ] - then echo "$UNTRACKED_COLOR(detached)$END" ; return ; + then echo "$UNTRACKED_COLOR(detached)$CEND" ; return ; fi # loop over all branches to find remote tracking branch @@ -165,18 +162,18 @@ function GitStatus # build output current_dir="$(pwd)" - open_paren="$branch_color($END" - close_paren="$branch_color)$END" - open_bracket="$branch_color[$END" - close_bracket="$branch_color]$END" - tracked_msg=$TRACKED_COLOR$tracked$END - untracked_msg=$UNTRACKED_COLOR$untracked$END - staged_msg=$STAGED_COLOR$staged$END - stashed_msg=$STASHED_COLOR$stashed$END - branch_msg=$branch_color$current_branch$END + open_paren="$branch_color($CEND" + close_paren="$branch_color)$CEND" + open_bracket="$branch_color[$CEND" + close_bracket="$branch_color]$CEND" + tracked_msg=$TRACKED_COLOR$tracked$CEND + untracked_msg=$UNTRACKED_COLOR$untracked$CEND + staged_msg=$STAGED_COLOR$staged$CEND + stashed_msg=$STASHED_COLOR$stashed$CEND + branch_msg=$branch_color$current_branch$CEND status_msg=$stashed_msg$untracked_msg$tracked_msg$staged_msg - [ $remote_branch ] && behind_msg="$behind_color$n_behind<-$END" - [ $remote_branch ] && ahead_msg="$ahead_color->$n_ahead$END" + [ $remote_branch ] && behind_msg="$behind_color$n_behind<-$CEND" + [ $remote_branch ] && ahead_msg="$ahead_color->$n_ahead$CEND" [ $remote_branch ] && upstream_msg=$open_bracket$behind_msg$ahead_msg$close_bracket branch_status_msg=$open_paren$branch_msg$status_msg$upstream_msg$close_paren @@ -204,5 +201,10 @@ function GitStatus function GitStatusPrompt { - echo -e "$PROMPT_HEAD$(pwd)/$PROMPT_MID$(GitStatus)$PROMPT_TAIL" + login_host="$LOGIN_COLOR`whoami`@$HOSTNAME$CEND:" + pwd_path="$BLUE$PWD/$CEND" + git_status="$GREEN$(GitStatus)$CEND" + prompt_tail='\n$ ' + + echo -e "$login_host $pwd_path$git_status$prompt_tail" } From 3b796996362f3114eaef3aa75c73fe5fe276f74d Mon Sep 17 00:00:00 2001 From: bill-auger Date: Jul 30 2017 03:04:45 +0000 Subject: [PATCH 10/30] allow proper rebasing/merging display in pwd other than root --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index ea99fa7..79b9530 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -111,16 +111,18 @@ function GitStatus [ ! $(AssertIsValidRepo) ] && return [ ! $(AssertHasCommits) ] && echo "(no commits)" && return - current_branch=`git rev-parse --abbrev-ref HEAD` ; [ $current_branch ] || return ; + # get the current state + git_dir=`git rev-parse --show-toplevel`/.git ; [ "$git_dir" ] || return ; + current_branch=`git rev-parse --abbrev-ref HEAD` ; [ "$current_branch" ] || return ; # detect detached HEAD state and abort - if [ -f "$(pwd)/.git/MERGE_HEAD" ] && [ ! -z "`cat .git/MERGE_MSG | grep -E '^Merge'`" ] - then merge_msg=`cat .git/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ + if [ -f "$git_dir/MERGE_HEAD" ] && [ ! -z "`cat $git_dir/MERGE_MSG | grep -E '^Merge'`" ] + then merge_msg=`cat $git_dir/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/"` echo "$UNTRACKED_COLOR(merging$merge_msg)$CEND" ; return ; - elif [ -d "$(pwd)/.git/rebase-apply/" ] || [ -d "$(pwd)/.git/rebase-merge/" ] - then rebase_dir=`ls -d .git/rebase-* | sed -e "s/^.git\/rebase-\(.*\)$/.git\/rebase-\1/"` + elif [ -d "$git_dir/rebase-apply/" ] || [ -d "$git_dir/rebase-merge/" ] + then rebase_dir=`ls -d $git_dir/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/"` this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` their_commit=`cat $rebase_dir/onto` echo "$UNTRACKED_COLOR(rebasing $this_branch onto $their_commit)$CEND" ; return ; @@ -183,7 +185,7 @@ function GitStatus commit_log=$( git log -n 1 --format=format:\"%s\" | sed -r "s/\"//g") commit_msg=" ${author_date:0:TIMESTAMP_LEN} $commit_log" current_tty_w=$(($(stty -F /dev/tty size | cut -d ' ' -f2))) - prompt_msg_len=$((${#USER} + 1 + ${#HOSTNAME} + 1 + ${#current_dir} + 1 + ${#status_msg})) + prompt_msg_len=$((${#USER} + 1 + ${#HOSTNAME} + 2 + ${#current_dir} + 1 + ${#status_msg})) prompt_msg_mod=$(($prompt_msg_len % $current_tty_w)) commit_msg_len=$(($current_tty_w - $prompt_msg_mod)) min_len=$(($TIMESTAMP_LEN + 1)) From 117811388cd9519417de36697a35da71017e9025 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Sep 27 2017 10:59:59 +0000 Subject: [PATCH 11/30] indicate next commit message during rebase --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 79b9530..2b502f7 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -125,7 +125,8 @@ function GitStatus then rebase_dir=`ls -d $git_dir/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/"` this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` their_commit=`cat $rebase_dir/onto` - echo "$UNTRACKED_COLOR(rebasing $this_branch onto $their_commit)$CEND" ; return ; + at_commit=`git log -n1 --oneline $(cat $rebase_dir/stopped-sha)` + echo "$UNTRACKED_COLOR(rebasing $this_branch onto ${their_commit::7} - at $at_commit)$CEND" ; return ; elif [ "$current_branch" == "HEAD" ] then echo "$UNTRACKED_COLOR(detached)$CEND" ; return ; fi @@ -183,6 +184,7 @@ function GitStatus status_msg=$(echo $branch_status_msg | sed -r $ANSI_FILTER_REGEX --) author_date=$(git log -n 1 --format=format:"%ai" $1 2> /dev/null) commit_log=$( git log -n 1 --format=format:\"%s\" | sed -r "s/\"//g") + [ "$commit_log" ] || commit_log='' commit_msg=" ${author_date:0:TIMESTAMP_LEN} $commit_log" current_tty_w=$(($(stty -F /dev/tty size | cut -d ' ' -f2))) prompt_msg_len=$((${#USER} + 1 + ${#HOSTNAME} + 2 + ${#current_dir} + 1 + ${#status_msg})) @@ -192,7 +194,6 @@ function GitStatus max_len=$(($current_tty_w - 1)) [ $commit_msg_len -lt $min_len -o $commit_msg_len -gt $max_len ] && commit_msg_len=0 commit_msg=${commit_msg:0:commit_msg_len} - echo "$branch_status_msg$commit_msg" done < <(git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads) From 0ba3f35d0bd587bee239ab5bfacde324f80508f1 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Oct 04 2017 02:58:27 +0000 Subject: [PATCH 12/30] suppress stopped-sha not found error msg --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 2b502f7..5b1374f 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -125,7 +125,7 @@ function GitStatus then rebase_dir=`ls -d $git_dir/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/"` this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` their_commit=`cat $rebase_dir/onto` - at_commit=`git log -n1 --oneline $(cat $rebase_dir/stopped-sha)` + at_commit=`git log -n1 --oneline $(cat $rebase_dir/stopped-sha 2> /dev/null)` echo "$UNTRACKED_COLOR(rebasing $this_branch onto ${their_commit::7} - at $at_commit)$CEND" ; return ; elif [ "$current_branch" == "HEAD" ] then echo "$UNTRACKED_COLOR(detached)$CEND" ; return ; From 0f895e899b9fdef83e5943eacff27c1c5f87d888 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:29:11 +0000 Subject: [PATCH 13/30] upgrade license to AGPLv3 --- diff --git a/LICENSE b/LICENSE index 9cecc1d..be3f7b2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,23 +1,21 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to +our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. The precise terms and conditions for copying, distribution and modification follow. @@ -72,7 +60,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Use with the GNU Affero General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single +under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General +Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published +GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's +versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -631,44 +629,33 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} + + Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by + it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + GNU Affero General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 5b1374f..8aa6c08 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -4,16 +4,16 @@ # Copyright 2013-2017 bill-auger # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . From 3a3c3c0cf256a9319c3876feca897d9af139f34b Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:40 +0000 Subject: [PATCH 14/30] add screenshot --- diff --git a/README.md b/README.md index 5b50c0c..1a7becf 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,4 @@ USAGE: ``` -[scrot]: http://bill-auger.github.io/git-status-prompt-scrot.png "git-status-prompt screenshot" +[scrot]: data/git-status-prompt-scrot.png "git-status-prompt screenshot" diff --git a/data/git-status-prompt-scrot.png b/data/git-status-prompt-scrot.png new file mode 100644 index 0000000..72be496 Binary files /dev/null and b/data/git-status-prompt-scrot.png differ From 606409624ed4bb83ea609d11bcaf217a392987e5 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:49 +0000 Subject: [PATCH 15/30] housekeeping --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 8aa6c08..ca6d2dc 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -40,7 +40,7 @@ readonly PURPLE='\033[00;35m' readonly BLUE='\033[00;36m' readonly AQUA='\033[01;36m' readonly CEND='\033[00m' -mkdir /deleteme 2> /dev/null && rmdir /deleteme/ && LOGIN_COLOR=$RED || LOGIN_COLOR=$PURPLE +(($EUID)) && readonly LOGIN_COLOR=$PURPLE || readonly LOGIN_COLOR=$RED readonly DIRTY_CHAR="*" readonly TRACKED_CHAR="!" readonly UNTRACKED_CHAR="?" @@ -105,6 +105,8 @@ function SyncStatus [ $(($?)) -eq 0 ] && echo $status } +function CurrentDir { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } + function GitStatus { # ensure we are in a valid git repository with commits @@ -164,7 +166,7 @@ function GitStatus stashed=$(HasStashedChanges) # build output - current_dir="$(pwd)" + current_dir=$(CurrentDir) open_paren="$branch_color($CEND" close_paren="$branch_color)$CEND" open_bracket="$branch_color[$CEND" @@ -204,9 +206,9 @@ function GitStatus function GitStatusPrompt { - login_host="$LOGIN_COLOR`whoami`@$HOSTNAME$CEND:" - pwd_path="$BLUE$PWD/$CEND" - git_status="$GREEN$(GitStatus)$CEND" + login_host="${LOGIN_COLOR}${USER}@${HOSTNAME}${CEND}:" + pwd_path="${BLUE}$(CurrentDir)${CEND}" + git_status="${GREEN}$(GitStatus)${CEND}" prompt_tail='\n$ ' echo -e "$login_host $pwd_path$git_status$prompt_tail" From 2b6f8a5fef833078deb040a3643e3c85d274f6ad Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:50 +0000 Subject: [PATCH 16/30] detect bare repos --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index ca6d2dc..5126d10 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -64,13 +64,19 @@ readonly TIMESTAMP_LEN=10 function AssertIsValidRepo { - git rev-parse --is-inside-work-tree > /dev/null 2>&1 && echo "OK" + [ "`git rev-parse --is-inside-work-tree 2> /dev/null`" == 'true' ] || \ + ! (($(AssertIsNotBareRepo))) && echo 1 || echo 0 } function AssertHasCommits { # TODO: does this fail if detached HEAD ? - git cat-file -t HEAD > /dev/null 2>&1 && echo "OK" + [ "`git cat-file -t HEAD 2> /dev/null`" ] && echo 1 || echo 0 +} + +function AssertIsNotBareRepo +{ + [ "`git rev-parse --is-bare-repository 2> /dev/null`" != 'true' ] && echo 1 || echo 0 } function HasAnyChanges @@ -109,9 +115,10 @@ function CurrentDir { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } function GitStatus { - # ensure we are in a valid git repository with commits - [ ! $(AssertIsValidRepo) ] && return - [ ! $(AssertHasCommits) ] && echo "(no commits)" && return + # ensure we are in a valid, non-bare git repository with commits + ! (($(AssertIsValidRepo ))) && return + ! (($(AssertIsNotBareRepo))) && echo "(bare repo)" && return + ! (($(AssertHasCommits ))) && echo "(no commits)" && return # get the current state git_dir=`git rev-parse --show-toplevel`/.git ; [ "$git_dir" ] || return ; From 0302a831dfd8039d953b708b2ec933730b48e67c Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:51 +0000 Subject: [PATCH 17/30] factor out truncation for detached messages --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 5126d10..b01c3f8 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -116,9 +116,9 @@ function CurrentDir { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } function GitStatus { # ensure we are in a valid, non-bare git repository with commits - ! (($(AssertIsValidRepo ))) && return - ! (($(AssertIsNotBareRepo))) && echo "(bare repo)" && return - ! (($(AssertHasCommits ))) && echo "(no commits)" && return + ! (($(AssertIsValidRepo ))) && return + ! (($(AssertIsNotBareRepo))) && echo $(TruncateToWidth "" "(bare repo)" ) && return + ! (($(AssertHasCommits ))) && echo $(TruncateToWidth "" "(no commits)") && return # get the current state git_dir=`git rev-parse --show-toplevel`/.git ; [ "$git_dir" ] || return ; @@ -129,15 +129,19 @@ function GitStatus then merge_msg=`cat $git_dir/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/"` - echo "$UNTRACKED_COLOR(merging$merge_msg)$CEND" ; return ; + echo $UNTRACKED_COLOR$(TruncateToWidth "" "(merging $merge_msg)")$CEND ; return ; + elif [ -d "$git_dir/rebase-apply/" ] || [ -d "$git_dir/rebase-merge/" ] then rebase_dir=`ls -d $git_dir/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/"` this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` their_commit=`cat $rebase_dir/onto` at_commit=`git log -n1 --oneline $(cat $rebase_dir/stopped-sha 2> /dev/null)` - echo "$UNTRACKED_COLOR(rebasing $this_branch onto ${their_commit::7} - at $at_commit)$CEND" ; return ; + msg="(rebasing $this_branch onto ${their_commit::7} - at $at_commit)" + + echo $UNTRACKED_COLOR$(TruncateToWidth "" "$msg" )$CEND ; return ; + elif [ "$current_branch" == "HEAD" ] - then echo "$UNTRACKED_COLOR(detached)$CEND" ; return ; + then echo $UNTRACKED_COLOR$(TruncateToWidth "" "(detached)")$CEND ; return ; fi # loop over all branches to find remote tracking branch @@ -173,7 +177,6 @@ function GitStatus stashed=$(HasStashedChanges) # build output - current_dir=$(CurrentDir) open_paren="$branch_color($CEND" close_paren="$branch_color)$CEND" open_bracket="$branch_color[$CEND" @@ -189,25 +192,35 @@ function GitStatus [ $remote_branch ] && upstream_msg=$open_bracket$behind_msg$ahead_msg$close_bracket branch_status_msg=$open_paren$branch_msg$status_msg$upstream_msg$close_paren - # append last commit message trunctuated to console width - status_msg=$(echo $branch_status_msg | sed -r $ANSI_FILTER_REGEX --) + # append last commit message author_date=$(git log -n 1 --format=format:"%ai" $1 2> /dev/null) commit_log=$( git log -n 1 --format=format:\"%s\" | sed -r "s/\"//g") [ "$commit_log" ] || commit_log='' - commit_msg=" ${author_date:0:TIMESTAMP_LEN} $commit_log" - current_tty_w=$(($(stty -F /dev/tty size | cut -d ' ' -f2))) - prompt_msg_len=$((${#USER} + 1 + ${#HOSTNAME} + 2 + ${#current_dir} + 1 + ${#status_msg})) - prompt_msg_mod=$(($prompt_msg_len % $current_tty_w)) - commit_msg_len=$(($current_tty_w - $prompt_msg_mod)) - min_len=$(($TIMESTAMP_LEN + 1)) - max_len=$(($current_tty_w - 1)) - [ $commit_msg_len -lt $min_len -o $commit_msg_len -gt $max_len ] && commit_msg_len=0 - commit_msg=${commit_msg:0:commit_msg_len} - echo "$branch_status_msg$commit_msg" + + echo $(TruncateToWidth "$branch_status_msg" " ${author_date:0:TIMESTAMP_LEN} $commit_log") done < <(git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads) } +function TruncateToWidth +{ + branch_status_msg=$1 + commit_msg=$2 + + # trunctuate to console width + current_dir=$(CurrentDir) + status_msg=$(echo $branch_status_msg | sed -r $ANSI_FILTER_REGEX --) + current_tty_w=$(($(stty -F /dev/tty size | cut -d ' ' -f2))) + prompt_msg_len=$((${#USER} + 1 + ${#HOSTNAME} + 1 + ${#current_dir} + 1 + ${#status_msg})) + prompt_msg_mod=$(($prompt_msg_len % $current_tty_w)) + commit_msg_len=$(($current_tty_w - $prompt_msg_mod)) + min_len=$(($TIMESTAMP_LEN + 1)) + max_len=$(($current_tty_w - 1)) + [ $commit_msg_len -lt $min_len -o $commit_msg_len -gt $max_len ] && commit_msg_len=0 + + echo "$branch_status_msg${commit_msg:0:commit_msg_len}" +} + # main entry point From 17516ac78fb93f77d8b9e9c933a5406a2adbfbb8 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:51 +0000 Subject: [PATCH 18/30] factor out login-host prep --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index b01c3f8..336c82e 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -40,7 +40,6 @@ readonly PURPLE='\033[00;35m' readonly BLUE='\033[00;36m' readonly AQUA='\033[01;36m' readonly CEND='\033[00m' -(($EUID)) && readonly LOGIN_COLOR=$PURPLE || readonly LOGIN_COLOR=$RED readonly DIRTY_CHAR="*" readonly TRACKED_CHAR="!" readonly UNTRACKED_CHAR="?" @@ -56,6 +55,9 @@ readonly STASHED_COLOR=$LIME readonly BEHIND_COLOR=$RED readonly AHEAD_COLOR=$YELLOW readonly EVEN_COLOR=$GREEN +readonly ROOT_COLOR=$RED +readonly USER_COLOR=$PURPLE +readonly LOGIN=$(whoami) readonly ANSI_FILTER_REGEX="s/\\\033\[([0-9]{1,2}(;[0-9]{1,2})?)?m//g" readonly TIMESTAMP_LEN=10 @@ -111,6 +113,14 @@ function SyncStatus [ $(($?)) -eq 0 ] && echo $status } +function LoginColor { (( $EUID )) && echo $USER_COLOR || echo $ROOT_COLOR ; } + +function LoginHost +{ + [ -z "$STY" ] && echo "${USER}@${HOSTNAME}${CEND}:" || \ + echo "[${USER}@${HOSTNAME}]${CEND}:" # GNU screen +} + function CurrentDir { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } function GitStatus @@ -208,10 +218,11 @@ function TruncateToWidth commit_msg=$2 # trunctuate to console width + login_host=$(LoginHost | sed -r $ANSI_FILTER_REGEX --) current_dir=$(CurrentDir) status_msg=$(echo $branch_status_msg | sed -r $ANSI_FILTER_REGEX --) current_tty_w=$(($(stty -F /dev/tty size | cut -d ' ' -f2))) - prompt_msg_len=$((${#USER} + 1 + ${#HOSTNAME} + 1 + ${#current_dir} + 1 + ${#status_msg})) + prompt_msg_len=$((${#login_host} + 1 + ${#current_dir} + 1 + ${#status_msg})) prompt_msg_mod=$(($prompt_msg_len % $current_tty_w)) commit_msg_len=$(($current_tty_w - $prompt_msg_mod)) min_len=$(($TIMESTAMP_LEN + 1)) @@ -226,7 +237,7 @@ function TruncateToWidth function GitStatusPrompt { - login_host="${LOGIN_COLOR}${USER}@${HOSTNAME}${CEND}:" + login_host="$(LoginColor)$(LoginHost)${CEND}" pwd_path="${BLUE}$(CurrentDir)${CEND}" git_status="${GREEN}$(GitStatus)${CEND}" prompt_tail='\n$ ' From bad322ee8b954f2a9809d418a408fff041ccde02 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:52 +0000 Subject: [PATCH 19/30] add blacklist feature --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d80690 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +ignore_dirs diff --git a/README.md b/README.md index 1a7becf..48dbc9f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ USAGE: # ~/.bashrc source /path/to/git-status-prompt/git-status-prompt.sh PS1="\$(GitStatusPrompt)" + + # this script can be sluggish in repos with a large number of commits + # such directories may be listed in a file named 'ignore_dirs' to avoid processing ``` diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 336c82e..3498672 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -32,6 +32,10 @@ # PS1="\$(GitStatusPrompt)" +# this script can be sluggish in very large repos +declare -r -a IGNORED_DIRS=( $(grep -v ^# ignore_dirs 2> /dev/null) ) + + readonly RED='\033[1;31m' readonly YELLOW='\033[01;33m' readonly GREEN='\033[00;32m' @@ -64,6 +68,17 @@ readonly TIMESTAMP_LEN=10 # helpers +function AssertIsNotIgnoredDir +{ + local ignored_dir + + for ignored_dir in ${IGNORED_DIRS[*]} + do [[ "$(pwd)" =~ ^${ignored_dir} ]] && echo 0 + done + + echo 1 +} + function AssertIsValidRepo { [ "`git rev-parse --is-inside-work-tree 2> /dev/null`" == 'true' ] || \ @@ -125,10 +140,11 @@ function CurrentDir { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } function GitStatus { - # ensure we are in a valid, non-bare git repository with commits - ! (($(AssertIsValidRepo ))) && return - ! (($(AssertIsNotBareRepo))) && echo $(TruncateToWidth "" "(bare repo)" ) && return - ! (($(AssertHasCommits ))) && echo $(TruncateToWidth "" "(no commits)") && return + # ensure we are in a valid, non-bare git repository, with commits, and not blacklisted + ! (($(AssertIsValidRepo ))) && return + ! (($(AssertIsNotBareRepo ))) && echo $(TruncateToWidth "" "(bare repo)" ) && return + ! (($(AssertHasCommits ))) && echo $(TruncateToWidth "" "(no commits)") && return + ! (($(AssertIsNotIgnoredDir))) && echo $(TruncateToWidth "" "(heavy-git)" ) && return # get the current state git_dir=`git rev-parse --show-toplevel`/.git ; [ "$git_dir" ] || return ; From ae5d067fe75bf994147f81b2daa6231003e033c0 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:52 +0000 Subject: [PATCH 20/30] housekeeping --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 3498672..8020df4 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -1,7 +1,7 @@ #!/bin/bash # git-status-prompt.sh - pretty format git sync and dirty status for shell prompt -# Copyright 2013-2017 bill-auger +# Copyright 2013-2019 bill-auger # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -33,8 +33,7 @@ # this script can be sluggish in very large repos -declare -r -a IGNORED_DIRS=( $(grep -v ^# ignore_dirs 2> /dev/null) ) - +declare -r -a IGNORED_DIRS=( $(grep -v ^# "$(dirname ${BASH_SOURCE})/ignore_dirs" 2> /dev/null) ) readonly RED='\033[1;31m' readonly YELLOW='\033[01;33m' @@ -50,17 +49,17 @@ readonly UNTRACKED_CHAR="?" readonly STAGED_CHAR="+" readonly STASHED_CHAR="$" readonly GIT_CLEAN_MSG_REGEX="nothing to commit,? (?working directory clean)?" -readonly CLEAN_COLOR=$GREEN -readonly DIRTY_COLOR=$YELLOW -readonly TRACKED_COLOR=$YELLOW -readonly UNTRACKED_COLOR=$RED -readonly STAGED_COLOR=$GREEN -readonly STASHED_COLOR=$LIME -readonly BEHIND_COLOR=$RED -readonly AHEAD_COLOR=$YELLOW -readonly EVEN_COLOR=$GREEN -readonly ROOT_COLOR=$RED -readonly USER_COLOR=$PURPLE +readonly CLEAN_COLOR=${GREEN} +readonly DIRTY_COLOR=${YELLOW} +readonly TRACKED_COLOR=${YELLOW} +readonly UNTRACKED_COLOR=${RED} +readonly STAGED_COLOR=${GREEN} +readonly STASHED_COLOR=${LIME} +readonly BEHIND_COLOR=${RED} +readonly AHEAD_COLOR=${YELLOW} +readonly EVEN_COLOR=${GREEN} +readonly ROOT_COLOR=${RED} +readonly USER_COLOR=${PURPLE} readonly LOGIN=$(whoami) readonly ANSI_FILTER_REGEX="s/\\\033\[([0-9]{1,2}(;[0-9]{1,2})?)?m//g" readonly TIMESTAMP_LEN=10 @@ -68,195 +67,210 @@ readonly TIMESTAMP_LEN=10 # helpers -function AssertIsNotIgnoredDir +AssertIsNotIgnoredDir() { local ignored_dir for ignored_dir in ${IGNORED_DIRS[*]} - do [[ "$(pwd)" =~ ^${ignored_dir} ]] && echo 0 + do [[ "$(pwd)" =~ ^${ignored_dir} ]] && return 1 done - echo 1 + return 0 } -function AssertIsValidRepo +AssertIsValidRepo() { - [ "`git rev-parse --is-inside-work-tree 2> /dev/null`" == 'true' ] || \ - ! (($(AssertIsNotBareRepo))) && echo 1 || echo 0 + [[ "$(git rev-parse --is-inside-work-tree 2> /dev/null)" == 'true' ]] || \ + [[ "$(git rev-parse --is-bare-repository 2> /dev/null)" == 'true' ]] } -function AssertHasCommits +AssertIsNotBareRepo() { - # TODO: does this fail if detached HEAD ? - [ "`git cat-file -t HEAD 2> /dev/null`" ] && echo 1 || echo 0 + [[ "$(git rev-parse --is-bare-repository 2> /dev/null)" != 'true' ]] } -function AssertIsNotBareRepo +AssertHasCommits() { - [ "`git rev-parse --is-bare-repository 2> /dev/null`" != 'true' ] && echo 1 || echo 0 + # TODO: does this fail if detached HEAD ? + [[ "$(git cat-file -t HEAD 2> /dev/null)" ]] } -function HasAnyChanges +HasAnyChanges() { - [ "`git status 2> /dev/null | tail -n1 | grep -E \"$GIT_CLEAN_MSG_REGEX\"`" ] || echo "$DIRTY_CHAR" + [ "$(git status 2> /dev/null | tail -n1 | grep -E "${GIT_CLEAN_MSG_REGEX}")" ] || echo "${DIRTY_CHAR}" } -function HasTrackedChanges +HasTrackedChanges() { - git diff --no-ext-diff --quiet --exit-code || echo "$TRACKED_CHAR" + git diff --no-ext-diff --quiet --exit-code || echo "${TRACKED_CHAR}" } -function HasUntrackedChanges +HasUntrackedChanges() { - [ -n "$(git ls-files --others --exclude-standard)" ] && echo "$UNTRACKED_CHAR" + [ -n "$(git ls-files --others --exclude-standard)" ] && echo "${UNTRACKED_CHAR}" } -function HasStagedChanges +HasStagedChanges() { - git diff-index --cached --quiet HEAD -- || echo "$STAGED_CHAR" + git diff-index --cached --quiet HEAD -- || echo "${STAGED_CHAR}" } -function HasStashedChanges +HasStashedChanges() { - git rev-parse --verify refs/stash > /dev/null 2>&1 && echo "$STASHED_CHAR" + git rev-parse --verify refs/stash > /dev/null 2>&1 && echo "${STASHED_CHAR}" } -function SyncStatus +SyncStatus() { - local_branch=$1 ; remote_branch=$2 ; - status=`git rev-list --left-right ${local_branch}...${remote_branch} -- 2>/dev/null` - [ $(($?)) -eq 0 ] && echo $status + local local_branch=$1 + local remote_branch=$2 + local status=$(git rev-list --left-right ${local_branch}...${remote_branch} -- 2>/dev/null) + + [ $(( $? )) -eq 0 ] && echo ${status} } -function LoginColor { (( $EUID )) && echo $USER_COLOR || echo $ROOT_COLOR ; } +LoginColor() { (( ${EUID} )) && echo ${USER_COLOR} || echo ${ROOT_COLOR} ; } -function LoginHost +LoginHost() { - [ -z "$STY" ] && echo "${USER}@${HOSTNAME}${CEND}:" || \ - echo "[${USER}@${HOSTNAME}]${CEND}:" # GNU screen + [ -z "${STY}" ] && echo "${USER}@${HOSTNAME}${CEND}:" || \ + echo "[${USER}@${HOSTNAME}]${CEND}:" # GNU screen } -function CurrentDir { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } +CurrentDir() { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } -function GitStatus +TruncateToWidth() { + local fixed_len_msg=$1 + local truncate_msg=$2 + + # trunctuate to console width + local login_host=$( echo $(LoginHost) | sed -r ${ANSI_FILTER_REGEX} --) + local current_dir=$(CurrentDir ) + local status_msg=$( echo $fixed_len_msg | sed -r ${ANSI_FILTER_REGEX} --) + local current_tty_w=$(( $(stty -F /dev/tty size | cut -d ' ' -f2) )) + local prompt_len=$(( ${#login_host} + ${#current_dir} + ${#status_msg} )) + local prompt_mod=$(( ${prompt_len} % ${current_tty_w} )) + local truncate_len=$(( ${current_tty_w} - ${prompt_mod} )) + local min_len=$(( ${TIMESTAMP_LEN} + 1 )) + local max_len=$(( ${current_tty_w} - 1 )) + [ ${truncate_len} -lt ${min_len} -o ${truncate_len} -gt ${max_len} ] && truncate_len=0 + local truncate_msg=${truncate_msg:0:truncate_len} + +# (>&2 echo "(login_host=${#login_host}) + (current_dir=${#current_dir}) + (status_msg=${#status_msg}) = (prompt_len=$prompt_len)") +# (>&2 echo "(current_tty_w=$current_tty_w) - (prompt_mod=$prompt_mod) = (truncate_len=$truncate_len)") +# (>&2 echo 123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_....) + + echo "${fixed_len_msg}${truncate_msg}" +} + +GitStatus() +{ +# (>&2 echo "AssertIsValidRepo=$( AssertIsValidRepo && echo 'true' || echo 'false - bailing')") +# (>&2 echo "AssertIsNotBareRepo=$( AssertIsNotBareRepo && echo 'true' || echo 'false - bailing')") +# (>&2 echo "AssertHasCommits=$( AssertHasCommits && echo 'true' || echo 'false - bailing')") +# (>&2 echo "AssertIsNotIgnoredDir=$(AssertIsNotIgnoredDir && echo 'true' || echo 'false - bailing')") + # ensure we are in a valid, non-bare git repository, with commits, and not blacklisted - ! (($(AssertIsValidRepo ))) && return - ! (($(AssertIsNotBareRepo ))) && echo $(TruncateToWidth "" "(bare repo)" ) && return - ! (($(AssertHasCommits ))) && echo $(TruncateToWidth "" "(no commits)") && return - ! (($(AssertIsNotIgnoredDir))) && echo $(TruncateToWidth "" "(heavy-git)" ) && return + ! AssertIsValidRepo && return + ! AssertIsNotBareRepo && echo $(TruncateToWidth "" "(bare repo)" ) && return + ! AssertHasCommits && echo $(TruncateToWidth "" "(no commits)") && return + ! AssertIsNotIgnoredDir && echo $(TruncateToWidth "" "(heavy-git)" ) && return # get the current state - git_dir=`git rev-parse --show-toplevel`/.git ; [ "$git_dir" ] || return ; - current_branch=`git rev-parse --abbrev-ref HEAD` ; [ "$current_branch" ] || return ; + local git_dir=$( git rev-parse --show-toplevel )/.git ; [ "${git_dir}" ] || return ; + local current_branch=$(git rev-parse --abbrev-ref HEAD) ; [ "${current_branch}" ] || return ; # detect detached HEAD state and abort - if [ -f "$git_dir/MERGE_HEAD" ] && [ ! -z "`cat $git_dir/MERGE_MSG | grep -E '^Merge'`" ] - then merge_msg=`cat $git_dir/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ - sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/"` + if [ -f "${git_dir}/MERGE_HEAD" ] && [ ! -z "$(cat ${git_dir}/MERGE_MSG | grep -E '^Merge')" ] + then local merge_msg=$(cat ${git_dir}/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ + sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/") - echo $UNTRACKED_COLOR$(TruncateToWidth "" "(merging $merge_msg)")$CEND ; return ; + echo ${UNTRACKED_COLOR}$(TruncateToWidth "" "(merging $merge_msg)")${CEND} ; return ; - elif [ -d "$git_dir/rebase-apply/" ] || [ -d "$git_dir/rebase-merge/" ] - then rebase_dir=`ls -d $git_dir/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/"` - this_branch=`cat $rebase_dir/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/"` - their_commit=`cat $rebase_dir/onto` - at_commit=`git log -n1 --oneline $(cat $rebase_dir/stopped-sha 2> /dev/null)` - msg="(rebasing $this_branch onto ${their_commit::7} - at $at_commit)" + elif [ -d "${git_dir}/rebase-apply/" ] || [ -d "${git_dir}/rebase-merge/" ] + then local rebase_dir=$( ls -d ${git_dir}/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/") + local this_branch=$( cat ${rebase_dir}/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/" ) + local their_commit=$(cat ${rebase_dir}/onto ) + local at_commit=$( git log -n1 --oneline $(cat ${rebase_dir}/stopped-sha 2> /dev/null)) + local msg="(rebasing ${this_branch} onto ${their_commit::7} - at ${at_commit})" - echo $UNTRACKED_COLOR$(TruncateToWidth "" "$msg" )$CEND ; return ; + echo ${UNTRACKED_COLOR}$(TruncateToWidth "" "${msg}" )${CEND} ; return ; - elif [ "$current_branch" == "HEAD" ] - then echo $UNTRACKED_COLOR$(TruncateToWidth "" "(detached)")$CEND ; return ; + elif [ "${current_branch}" == "HEAD" ] + then echo ${UNTRACKED_COLOR}$(TruncateToWidth "" "(detached)")${CEND} ; return ; fi # loop over all branches to find remote tracking branch + local local_branch + local remote_branch while read local_branch remote_branch do # filter branches by name - [ "$current_branch" != "$local_branch" ] && continue + [ "${current_branch}" != "${local_branch}" ] && continue # set branch color based on dirty status - if [ -z "$(HasAnyChanges)" ] ; then branch_color=$CLEAN_COLOR ; else branch_color=$DIRTY_COLOR ; fi ; + local branch_color=$([ -z "$(HasAnyChanges)" ] && echo ${CLEAN_COLOR} || echo ${DIRTY_COLOR}) # get sync status - if [ $remote_branch ] ; then - status=$(SyncStatus $local_branch $remote_branch) - n_behind=`echo "$status" | tr " " "\n" | grep -c '^>'` - n_ahead=` echo "$status" | tr " " "\n" | grep -c '^<'` + if [ ${remote_branch} ] ; then + local status=$(SyncStatus ${local_branch} ${remote_branch}) + local n_behind=$(echo "${status}" | tr " " "\n" | grep -c '^>') + local n_ahead=$( echo "${status}" | tr " " "\n" | grep -c '^<') # set sync color - if [ "$n_behind" -ne 0 ] ; then behind_color=$BEHIND_COLOR ; else behind_color=$EVEN_COLOR ; fi ; - if [ "$n_ahead" -ne 0 ] ; then ahead_color=$AHEAD_COLOR ; else ahead_color=$EVEN_COLOR ; fi ; + local behind_color=$([ "$n_behind" -ne 0 ] && echo ${BEHIND_COLOR} || echo ${EVEN_COLOR}) + local ahead_color=$( [ "$n_ahead" -ne 0 ] && echo ${AHEAD_COLOR} || echo ${EVEN_COLOR}) fi # get tracked status - tracked=$(HasTrackedChanges) + local tracked=$(HasTrackedChanges) # get untracked status - untracked=$(HasUntrackedChanges) + local untracked=$(HasUntrackedChanges) # get staged status - staged=$(HasStagedChanges) + local staged=$(HasStagedChanges) # get stashed status - stashed=$(HasStashedChanges) + local stashed=$(HasStashedChanges) # build output - open_paren="$branch_color($CEND" - close_paren="$branch_color)$CEND" - open_bracket="$branch_color[$CEND" - close_bracket="$branch_color]$CEND" - tracked_msg=$TRACKED_COLOR$tracked$CEND - untracked_msg=$UNTRACKED_COLOR$untracked$CEND - staged_msg=$STAGED_COLOR$staged$CEND - stashed_msg=$STASHED_COLOR$stashed$CEND - branch_msg=$branch_color$current_branch$CEND - status_msg=$stashed_msg$untracked_msg$tracked_msg$staged_msg - [ $remote_branch ] && behind_msg="$behind_color$n_behind<-$CEND" - [ $remote_branch ] && ahead_msg="$ahead_color->$n_ahead$CEND" - [ $remote_branch ] && upstream_msg=$open_bracket$behind_msg$ahead_msg$close_bracket - branch_status_msg=$open_paren$branch_msg$status_msg$upstream_msg$close_paren + local open_paren="${branch_color}(${CEND}" + local close_paren="${branch_color})${CEND}" + local open_bracket="${branch_color}[${CEND}" + local close_bracket="${branch_color}]${CEND}" + local tracked_msg=${TRACKED_COLOR}${tracked}${CEND} + local untracked_msg=${UNTRACKED_COLOR}${untracked}${CEND} + local staged_msg=${STAGED_COLOR}${staged}${CEND} + local stashed_msg=${STASHED_COLOR}${stashed}${CEND} + local branch_msg=${branch_color}${current_branch}${CEND} + local status_msg=${stashed_msg}${untracked_msg}${tracked_msg}${staged_msg} + local behind_msg=$( [ $remote_branch ] && echo "${behind_color}${n_behind}<-${CEND}" ) + local ahead_msg=$( [ $remote_branch ] && echo "${ahead_color}->${n_ahead}${CEND}" ) + local upstream_msg=$([ $remote_branch ] && echo "${open_bracket}${behind_msg}${ahead_msg}${close_bracket}") + local branch_status_msg="${open_paren}${branch_msg}${status_msg}${upstream_msg}${close_paren}" # append last commit message - author_date=$(git log -n 1 --format=format:"%ai" $1 2> /dev/null) - commit_log=$( git log -n 1 --format=format:\"%s\" | sed -r "s/\"//g") - [ "$commit_log" ] || commit_log='' + local author_date=$(git log --max-count=1 --format=format:"%ai" 2> /dev/null ) + local commit_log=$( git log --max-count=1 --format=format:\"%s\" | sed -r "s/\"//g") + [ "${commit_log}" ] || commit_log='' + local commit_msg=" ${author_date:0:TIMESTAMP_LEN} ${commit_log}" - echo $(TruncateToWidth "$branch_status_msg" " ${author_date:0:TIMESTAMP_LEN} $commit_log") + echo $(TruncateToWidth "${branch_status_msg}" "${commit_msg}") done < <(git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads) } -function TruncateToWidth -{ - branch_status_msg=$1 - commit_msg=$2 - - # trunctuate to console width - login_host=$(LoginHost | sed -r $ANSI_FILTER_REGEX --) - current_dir=$(CurrentDir) - status_msg=$(echo $branch_status_msg | sed -r $ANSI_FILTER_REGEX --) - current_tty_w=$(($(stty -F /dev/tty size | cut -d ' ' -f2))) - prompt_msg_len=$((${#login_host} + 1 + ${#current_dir} + 1 + ${#status_msg})) - prompt_msg_mod=$(($prompt_msg_len % $current_tty_w)) - commit_msg_len=$(($current_tty_w - $prompt_msg_mod)) - min_len=$(($TIMESTAMP_LEN + 1)) - max_len=$(($current_tty_w - 1)) - [ $commit_msg_len -lt $min_len -o $commit_msg_len -gt $max_len ] && commit_msg_len=0 - - echo "$branch_status_msg${commit_msg:0:commit_msg_len}" -} - # main entry point -function GitStatusPrompt +GitStatusPrompt() { - login_host="$(LoginColor)$(LoginHost)${CEND}" - pwd_path="${BLUE}$(CurrentDir)${CEND}" - git_status="${GREEN}$(GitStatus)${CEND}" - prompt_tail='\n$ ' + local login_host="$(LoginColor)$(LoginHost)${CEND}" + local pwd_path="${BLUE}$(CurrentDir)${CEND}" + local git_status="${GREEN}$(GitStatus)${CEND}" + local prompt_tail='\n$ ' - echo -e "$login_host $pwd_path$git_status$prompt_tail" + echo -e "${login_host}${pwd_path}${git_status}${prompt_tail}" } From c4df7bb8d1b23125854b84c0530dd3cd6e8078ba Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:52 +0000 Subject: [PATCH 21/30] refactor --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 8020df4..bb0c8c3 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -92,7 +92,45 @@ AssertIsNotBareRepo() AssertHasCommits() { # TODO: does this fail if detached HEAD ? - [[ "$(git cat-file -t HEAD 2> /dev/null)" ]] + [[ -n "$(git cat-file -t HEAD 2> /dev/null)" ]] +} + +GitDir() { echo "$(git rev-parse --show-toplevel )/.git" ; } + +CurrentBranch() { git rev-parse --abbrev-ref HEAD ; } + +DetachedMsg() # (git_dir current_branch) +{ + local git_dir=$1 + local current_branch=$2 + + [[ -n "${git_dir}" ]] || return ; + + if [[ -f "${git_dir}/MERGE_HEAD" && ! -z "$(cat ${git_dir}/MERGE_MSG | grep -E '^Merge')" ]] + then local merge_msg=$(cat ${git_dir}/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ + sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/") + + echo "${UNTRACKED_COLOR}$(TruncateToWidth "" "(merging ${merge_msg})")${CEND}" + + elif [[ -d "${git_dir}/rebase-apply/" || -d "${git_dir}/rebase-merge/" ]] + then local rebase_dir=$( ls -d ${git_dir}/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/") + local this_branch=$( cat ${rebase_dir}/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/" ) + local their_commit=$(cat ${rebase_dir}/onto ) + local at_commit=$( git log -n1 --oneline $(cat ${rebase_dir}/stopped-sha 2> /dev/null)) + local msg="(rebasing ${this_branch} onto ${their_commit::7} - at ${at_commit})" + + echo "${UNTRACKED_COLOR}$(TruncateToWidth "" "${msg}" )${CEND}" + + elif [[ "${current_branch}" == "HEAD" ]] + then echo "${UNTRACKED_COLOR}$(TruncateToWidth "" "(detached)")${CEND}" + fi +} + +IsLocalBranch() # (branch_name) +{ + local branch=$1 + + [[ -n "$(git branch -a | grep -E "^.* $branch$")" ]] } HasAnyChanges() @@ -157,7 +195,7 @@ TruncateToWidth() [ ${truncate_len} -lt ${min_len} -o ${truncate_len} -gt ${max_len} ] && truncate_len=0 local truncate_msg=${truncate_msg:0:truncate_len} -# (>&2 echo "(login_host=${#login_host}) + (current_dir=${#current_dir}) + (status_msg=${#status_msg}) = (prompt_len=$prompt_len)") +# (>&2 echo "(login_host_len=${#login_host}) + (current_dir_len=${#current_dir}) + (status_msg_len=${#status_msg}) = (prompt_len=$prompt_len)") # (>&2 echo "(current_tty_w=$current_tty_w) - (prompt_mod=$prompt_mod) = (truncate_len=$truncate_len)") # (>&2 echo 123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_....) @@ -169,7 +207,7 @@ GitStatus() # (>&2 echo "AssertIsValidRepo=$( AssertIsValidRepo && echo 'true' || echo 'false - bailing')") # (>&2 echo "AssertIsNotBareRepo=$( AssertIsNotBareRepo && echo 'true' || echo 'false - bailing')") # (>&2 echo "AssertHasCommits=$( AssertHasCommits && echo 'true' || echo 'false - bailing')") -# (>&2 echo "AssertIsNotIgnoredDir=$(AssertIsNotIgnoredDir && echo 'true' || echo 'false - bailing')") +# (>&2 echo "AssertIsNotIgnoredDir=$(AssertIsNotIgnoredDir && echo 'true' || echo 'false' )") # ensure we are in a valid, non-bare git repository, with commits, and not blacklisted ! AssertIsValidRepo && return @@ -177,89 +215,80 @@ GitStatus() ! AssertHasCommits && echo $(TruncateToWidth "" "(no commits)") && return ! AssertIsNotIgnoredDir && echo $(TruncateToWidth "" "(heavy-git)" ) && return - # get the current state - local git_dir=$( git rev-parse --show-toplevel )/.git ; [ "${git_dir}" ] || return ; - local current_branch=$(git rev-parse --abbrev-ref HEAD) ; [ "${current_branch}" ] || return ; + # get current state + local git_dir="$( GitDir )" + local current_branch=$(CurrentBranch ) + local detached_msg="$( DetachedMsg "${git_dir}" "${current_branch}")" - # detect detached HEAD state and abort - if [ -f "${git_dir}/MERGE_HEAD" ] && [ ! -z "$(cat ${git_dir}/MERGE_MSG | grep -E '^Merge')" ] - then local merge_msg=$(cat ${git_dir}/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ - sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/") +# (>&2 echo "is_valid_git_dir=$( [[ -n "${git_dir}" ]] && echo 'true' || echo 'false - bailing')") +# (>&2 echo "is_valid_current_branch=$([[ -n "${current_branch}" ]] && echo 'true' || echo 'false - bailing')") +# (>&2 echo "is_detached=$( [[ -n "${detached_msg}" ]] && echo 'true' || echo 'false' )") +# (>&2 echo "is_local_branch=$( IsLocalBranch ${current_branch} && echo 'true' || echo 'false - bailing')") - echo ${UNTRACKED_COLOR}$(TruncateToWidth "" "(merging $merge_msg)")${CEND} ; return ; + # validate current state + [[ -n "${git_dir}" && -n "${current_branch}" ]] || return + [[ -n "${detached_msg}" ]] && echo "${detached_msg}" && return + IsLocalBranch ${current_branch} || return - elif [ -d "${git_dir}/rebase-apply/" ] || [ -d "${git_dir}/rebase-merge/" ] - then local rebase_dir=$( ls -d ${git_dir}/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/") - local this_branch=$( cat ${rebase_dir}/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/" ) - local their_commit=$(cat ${rebase_dir}/onto ) - local at_commit=$( git log -n1 --oneline $(cat ${rebase_dir}/stopped-sha 2> /dev/null)) - local msg="(rebasing ${this_branch} onto ${their_commit::7} - at ${at_commit})" + # get remote tracking branch + local local_branch + local remote_branch + local should_count_divergences + while read local_branch remote_branch + do [[ "${current_branch}" == "${local_branch}" ]] && \ + should_count_divergences=$([[ -n "${remote_branch}" ]] && echo 1 || echo 0) && \ + break + done < <(git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads) - echo ${UNTRACKED_COLOR}$(TruncateToWidth "" "${msg}" )${CEND} ; return ; + # set branch color based on dirty status + local branch_color=$([ -z "$(HasAnyChanges)" ] && echo ${CLEAN_COLOR} || echo ${DIRTY_COLOR}) - elif [ "${current_branch}" == "HEAD" ] - then echo ${UNTRACKED_COLOR}$(TruncateToWidth "" "(detached)")${CEND} ; return ; + # get sync status + if (( ${should_count_divergences} )) + then local status=$( SyncStatus ${current_branch} ${remote_branch} ) + local n_behind=$( echo "${status}" | tr " " "\n" | grep -c '^>' ) + local n_ahead=$( echo "${status}" | tr " " "\n" | grep -c '^<' ) + local behind_color=$([ "${n_behind}" -ne 0 ] && echo ${BEHIND_COLOR} || echo ${EVEN_COLOR}) + local ahead_color=$( [ "${n_ahead}" -ne 0 ] && echo ${AHEAD_COLOR} || echo ${EVEN_COLOR}) fi - # loop over all branches to find remote tracking branch - local local_branch - local remote_branch - while read local_branch remote_branch - do - # filter branches by name - [ "${current_branch}" != "${local_branch}" ] && continue - - # set branch color based on dirty status - local branch_color=$([ -z "$(HasAnyChanges)" ] && echo ${CLEAN_COLOR} || echo ${DIRTY_COLOR}) - - # get sync status - if [ ${remote_branch} ] ; then - local status=$(SyncStatus ${local_branch} ${remote_branch}) - local n_behind=$(echo "${status}" | tr " " "\n" | grep -c '^>') - local n_ahead=$( echo "${status}" | tr " " "\n" | grep -c '^<') - - # set sync color - local behind_color=$([ "$n_behind" -ne 0 ] && echo ${BEHIND_COLOR} || echo ${EVEN_COLOR}) - local ahead_color=$( [ "$n_ahead" -ne 0 ] && echo ${AHEAD_COLOR} || echo ${EVEN_COLOR}) - fi - - # get tracked status - local tracked=$(HasTrackedChanges) - - # get untracked status - local untracked=$(HasUntrackedChanges) - - # get staged status - local staged=$(HasStagedChanges) - - # get stashed status - local stashed=$(HasStashedChanges) - - # build output - local open_paren="${branch_color}(${CEND}" - local close_paren="${branch_color})${CEND}" - local open_bracket="${branch_color}[${CEND}" - local close_bracket="${branch_color}]${CEND}" - local tracked_msg=${TRACKED_COLOR}${tracked}${CEND} - local untracked_msg=${UNTRACKED_COLOR}${untracked}${CEND} - local staged_msg=${STAGED_COLOR}${staged}${CEND} - local stashed_msg=${STASHED_COLOR}${stashed}${CEND} - local branch_msg=${branch_color}${current_branch}${CEND} - local status_msg=${stashed_msg}${untracked_msg}${tracked_msg}${staged_msg} - local behind_msg=$( [ $remote_branch ] && echo "${behind_color}${n_behind}<-${CEND}" ) - local ahead_msg=$( [ $remote_branch ] && echo "${ahead_color}->${n_ahead}${CEND}" ) - local upstream_msg=$([ $remote_branch ] && echo "${open_bracket}${behind_msg}${ahead_msg}${close_bracket}") - local branch_status_msg="${open_paren}${branch_msg}${status_msg}${upstream_msg}${close_paren}" - - # append last commit message - local author_date=$(git log --max-count=1 --format=format:"%ai" 2> /dev/null ) - local commit_log=$( git log --max-count=1 --format=format:\"%s\" | sed -r "s/\"//g") - [ "${commit_log}" ] || commit_log='' - local commit_msg=" ${author_date:0:TIMESTAMP_LEN} ${commit_log}" - - echo $(TruncateToWidth "${branch_status_msg}" "${commit_msg}") + # get tracked status + local tracked=$(HasTrackedChanges) + + # get untracked status + local untracked=$(HasUntrackedChanges) + + # get staged status + local staged=$(HasStagedChanges) + + # get stashed status + local stashed=$(HasStashedChanges) + + # build output + local open_paren="${branch_color}(${CEND}" + local close_paren="${branch_color})${CEND}" + local open_bracket="${branch_color}[${CEND}" + local close_bracket="${branch_color}]${CEND}" + local tracked_msg=${TRACKED_COLOR}${tracked}${CEND} + local untracked_msg=${UNTRACKED_COLOR}${untracked}${CEND} + local staged_msg=${STAGED_COLOR}${staged}${CEND} + local stashed_msg=${STASHED_COLOR}${stashed}${CEND} + local branch_msg=${branch_color}${current_branch}${CEND} + local status_msg=${stashed_msg}${untracked_msg}${tracked_msg}${staged_msg} + if (( ${should_count_divergences} )) + then local behind_msg="${behind_color}${n_behind}<-${CEND}" + local ahead_msg="${ahead_color}->${n_ahead}${CEND}" + local upstream_msg="${open_bracket}${behind_msg}${ahead_msg}${close_bracket}" + fi + local branch_status_msg="${open_paren}${branch_msg}${status_msg}${upstream_msg}${close_paren}" - done < <(git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads) + # append last commit message + local author_date=$(git log --max-count=1 --format=format:"%ai" 2> /dev/null ) + local commit_log=$( git log --max-count=1 --format=format:\"%s\" | sed -r "s/\"//g") + [[ -n "${commit_log}" ]] || commit_log='' + local commit_msg=" ${author_date:0:TIMESTAMP_LEN} ${commit_log}" + + echo $(TruncateToWidth "${branch_status_msg}" "${commit_msg}") } From b35765e0dda7d59f57c20cf793dc74034140862f Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:53 +0000 Subject: [PATCH 22/30] debug logging --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index bb0c8c3..321f234 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -65,7 +65,53 @@ readonly ANSI_FILTER_REGEX="s/\\\033\[([0-9]{1,2}(;[0-9]{1,2})?)?m//g" readonly TIMESTAMP_LEN=10 -# helpers +## debugging ## + +Dbg() { (>&2 echo -e "[GitStatusPrompt]: $@") ; } + +DbgTruncateToWidth() # (login_host_len current_dir_len status_msg_len prompt_len current_tty_w prompt_mod truncate_len min_len max_len) +{ + local login_host_len=$1 ; local current_dir_len=$2 ; local status_msg_len=$3 ; local prompt_len=$4 ; + local current_tty_w=$5 ; local prompt_mod=$6 ; local truncate_len=$7 ; + local min_len=$8 ; local max_len=$9 ; + + Dbg "(login_host_len=${#login_host_len}) + (current_dir_len=${#current_dir_len}) + (status_msg_len=${#status_msg_len}) = (prompt_len=$prompt_len)" + Dbg "(current_tty_w=$current_tty_w) - (prompt_mod=$prompt_mod) = (truncate_len=$truncate_len)" + Dbg "(min_len=$min_len) < (truncate_len=$truncate_len) < (max_len=$max_len)" + Dbg 123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_.... +} + +DbgGitStatusAssertions() +{ + Dbg "AssertIsValidRepo=$( AssertIsValidRepo && echo 'true' || echo 'false - bailing')" + Dbg "AssertIsNotBareRepo=$( AssertIsNotBareRepo && echo 'true' || echo 'false - bailing')" + Dbg "AssertHasCommits=$( AssertHasCommits && echo 'true' || echo 'false - bailing')" + Dbg "AssertIsNotIgnoredDir=$(AssertIsNotIgnoredDir && echo 'true' || echo 'false' )" +} + +DbgGitStatusState() +{ + Dbg "current_branch=${current_branch}" + Dbg "is_valid_git_dir=$( [[ -n "${git_dir}" ]] && echo 'true' || echo 'false - bailing')" + Dbg "is_valid_current_branch=$([[ -n "${current_branch}" ]] && echo 'true' || echo 'false - bailing')" + Dbg "is_detached=$( [[ -n "${detached_msg}" ]] && echo 'true' || echo 'false' )" + Dbg "is_local_branch=$( IsLocalBranch ${current_branch} && echo 'true' || echo 'false - bailing')" +} + +DbgGitStatusChars() +{ + Dbg "tracked=$tracked untracked=$untracked staged=$staged stashed=$stashed\n" +} + +DbgGitStatusPrompt() +{ + Dbg "login_host=${login_host} pwd_path=${pwd_path} git_status=${git_status} prompt_tail=${prompt_tail}" +} + +DbgSourced() { Dbg "sourced" ; } + + +## helpers ## AssertIsNotIgnoredDir() { @@ -177,6 +223,33 @@ LoginHost() CurrentDir() { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } +DbgTruncateToWidth() +{ + (>&2 echo "(login_host_len=${#1}) + (current_dir_len=${#2}) + (status_msg_len=${#3}) = (prompt_len=$prompt_len)") + (>&2 echo "(current_tty_w=$current_tty_w) - (prompt_mod=$prompt_mod) = (truncate_len=$truncate_len)") + (>&2 echo "(min_len=$min_len) < (truncate_len=$truncate_len) < (max_len=$max_len)") + (>&2 echo 123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_....) +} + +DbgGitStatusAssertions() +{ + (>&2 echo "AssertIsValidRepo=$( AssertIsValidRepo && echo 'true' || echo 'false - bailing')") + (>&2 echo "AssertIsNotBareRepo=$( AssertIsNotBareRepo && echo 'true' || echo 'false - bailing')") + (>&2 echo "AssertHasCommits=$( AssertHasCommits && echo 'true' || echo 'false - bailing')") + (>&2 echo "AssertIsNotIgnoredDir=$(AssertIsNotIgnoredDir && echo 'true' || echo 'false' )") +} + +DbgGitStatusState() +{ + local git_dir=$1 ; local current_branch=$2 ; local detached_msg=$3 ; + + (>&2 echo "current_branch=${current_branch}") + (>&2 echo "is_valid_git_dir=$( [[ -n "${git_dir}" ]] && echo 'true' || echo 'false - bailing')") + (>&2 echo "is_valid_current_branch=$([[ -n "${current_branch}" ]] && echo 'true' || echo 'false - bailing')") + (>&2 echo "is_detached=$( [[ -n "${detached_msg}" ]] && echo 'true' || echo 'false' )") + (>&2 echo "is_local_branch=$( IsLocalBranch ${current_branch} && echo 'true' || echo 'false - bailing')") +} + TruncateToWidth() { local fixed_len_msg=$1 @@ -195,19 +268,14 @@ TruncateToWidth() [ ${truncate_len} -lt ${min_len} -o ${truncate_len} -gt ${max_len} ] && truncate_len=0 local truncate_msg=${truncate_msg:0:truncate_len} -# (>&2 echo "(login_host_len=${#login_host}) + (current_dir_len=${#current_dir}) + (status_msg_len=${#status_msg}) = (prompt_len=$prompt_len)") -# (>&2 echo "(current_tty_w=$current_tty_w) - (prompt_mod=$prompt_mod) = (truncate_len=$truncate_len)") -# (>&2 echo 123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_....) +# DbgTruncateToWidth echo "${fixed_len_msg}${truncate_msg}" } GitStatus() { -# (>&2 echo "AssertIsValidRepo=$( AssertIsValidRepo && echo 'true' || echo 'false - bailing')") -# (>&2 echo "AssertIsNotBareRepo=$( AssertIsNotBareRepo && echo 'true' || echo 'false - bailing')") -# (>&2 echo "AssertHasCommits=$( AssertHasCommits && echo 'true' || echo 'false - bailing')") -# (>&2 echo "AssertIsNotIgnoredDir=$(AssertIsNotIgnoredDir && echo 'true' || echo 'false' )") +# DbgGitStatusAssertions # ensure we are in a valid, non-bare git repository, with commits, and not blacklisted ! AssertIsValidRepo && return @@ -220,10 +288,7 @@ GitStatus() local current_branch=$(CurrentBranch ) local detached_msg="$( DetachedMsg "${git_dir}" "${current_branch}")" -# (>&2 echo "is_valid_git_dir=$( [[ -n "${git_dir}" ]] && echo 'true' || echo 'false - bailing')") -# (>&2 echo "is_valid_current_branch=$([[ -n "${current_branch}" ]] && echo 'true' || echo 'false - bailing')") -# (>&2 echo "is_detached=$( [[ -n "${detached_msg}" ]] && echo 'true' || echo 'false' )") -# (>&2 echo "is_local_branch=$( IsLocalBranch ${current_branch} && echo 'true' || echo 'false - bailing')") +# DbgGitStatusState # validate current state [[ -n "${git_dir}" && -n "${current_branch}" ]] || return @@ -264,6 +329,8 @@ GitStatus() # get stashed status local stashed=$(HasStashedChanges) +# DbgGitStatusChars + # build output local open_paren="${branch_color}(${CEND}" local close_paren="${branch_color})${CEND}" @@ -301,5 +368,9 @@ GitStatusPrompt() local git_status="${GREEN}$(GitStatus)${CEND}" local prompt_tail='\n$ ' +# DbgGitStatusPrompt + echo -e "${login_host}${pwd_path}${git_status}${prompt_tail}" } + +# DbgSourced From a975918d061512ac5af8b2f6cc6cff54d449d52a Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:53 +0000 Subject: [PATCH 23/30] housekeeping --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 321f234..0dfc277 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -61,7 +61,7 @@ readonly EVEN_COLOR=${GREEN} readonly ROOT_COLOR=${RED} readonly USER_COLOR=${PURPLE} readonly LOGIN=$(whoami) -readonly ANSI_FILTER_REGEX="s/\\\033\[([0-9]{1,2}(;[0-9]{1,2})?)?m//g" +readonly ANSI_FILTER_REGEX="s|\\\033\[([0-9]{1,2}(;[0-9]{1,2})?)?m||g" readonly TIMESTAMP_LEN=10 @@ -113,17 +113,6 @@ DbgSourced() { Dbg "sourced" ; } ## helpers ## -AssertIsNotIgnoredDir() -{ - local ignored_dir - - for ignored_dir in ${IGNORED_DIRS[*]} - do [[ "$(pwd)" =~ ^${ignored_dir} ]] && return 1 - done - - return 0 -} - AssertIsValidRepo() { [[ "$(git rev-parse --is-inside-work-tree 2> /dev/null)" == 'true' ]] || \ @@ -141,6 +130,17 @@ AssertHasCommits() [[ -n "$(git cat-file -t HEAD 2> /dev/null)" ]] } +AssertIsNotIgnoredDir() +{ + local ignored_dir + + for ignored_dir in ${IGNORED_DIRS[*]} + do [[ "$(pwd)" =~ ^${ignored_dir} ]] && return 1 + done + + return 0 +} + GitDir() { echo "$(git rev-parse --show-toplevel )/.git" ; } CurrentBranch() { git rev-parse --abbrev-ref HEAD ; } @@ -153,14 +153,14 @@ DetachedMsg() # (git_dir current_branch) [[ -n "${git_dir}" ]] || return ; if [[ -f "${git_dir}/MERGE_HEAD" && ! -z "$(cat ${git_dir}/MERGE_MSG | grep -E '^Merge')" ]] - then local merge_msg=$(cat ${git_dir}/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ - sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1 \2 \3 \4\5/") + then local merge_msg=$(cat ${git_dir}/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ + sed -e "s/^Merge \(.*\)\(branch\|tag\|commit\) '\(.*\)' \(of .* \)\?\(into .*\)\?$/\1\2 \3 \4\5/") echo "${UNTRACKED_COLOR}$(TruncateToWidth "" "(merging ${merge_msg})")${CEND}" elif [[ -d "${git_dir}/rebase-apply/" || -d "${git_dir}/rebase-merge/" ]] - then local rebase_dir=$( ls -d ${git_dir}/rebase-* | sed -e "s/^\$\(git_dir\)\/rebase-\(.*\)$/\$\(git_dir\)\/rebase-\1/") - local this_branch=$( cat ${rebase_dir}/head-name | sed -e "s/^refs\/heads\/\(.*\)$/\1/" ) + then local rebase_dir=$( ls -d ${git_dir}/rebase-* | sed -e "s|^\$\(git_dir\)/rebase-\(.*\)$|\$\(git_dir\)/rebase-\1|") + local this_branch=$( cat ${rebase_dir}/head-name | sed -e "s|^refs/heads/\(.*\)$|\1|" ) local their_commit=$(cat ${rebase_dir}/onto ) local at_commit=$( git log -n1 --oneline $(cat ${rebase_dir}/stopped-sha 2> /dev/null)) local msg="(rebasing ${this_branch} onto ${their_commit::7} - at ${at_commit})" @@ -204,7 +204,7 @@ HasStashedChanges() git rev-parse --verify refs/stash > /dev/null 2>&1 && echo "${STASHED_CHAR}" } -SyncStatus() +SyncStatus() # (local_branch remote_branch status) { local local_branch=$1 local remote_branch=$2 @@ -250,7 +250,7 @@ DbgGitStatusState() (>&2 echo "is_local_branch=$( IsLocalBranch ${current_branch} && echo 'true' || echo 'false - bailing')") } -TruncateToWidth() +TruncateToWidth() # (fixed_len_msg truncate_msg) { local fixed_len_msg=$1 local truncate_msg=$2 @@ -351,7 +351,7 @@ GitStatus() # append last commit message local author_date=$(git log --max-count=1 --format=format:"%ai" 2> /dev/null ) - local commit_log=$( git log --max-count=1 --format=format:\"%s\" | sed -r "s/\"//g") + local commit_log=$( git log --max-count=1 --format=format:\"%s\" | sed -r "s|\"||g") [[ -n "${commit_log}" ]] || commit_log='' local commit_msg=" ${author_date:0:TIMESTAMP_LEN} ${commit_log}" From e28d5b3d47b7ccddc8915a679c2a9687d28a47b8 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:54 +0000 Subject: [PATCH 24/30] color author date --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 0dfc277..06d8bbf 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -40,8 +40,9 @@ readonly YELLOW='\033[01;33m' readonly GREEN='\033[00;32m' readonly LIME='\033[01;32m' readonly PURPLE='\033[00;35m' -readonly BLUE='\033[00;36m' -readonly AQUA='\033[01;36m' +readonly BLUE='\033[00;34m' +readonly AQUA='\033[00;36m' +readonly CYAN='\033[01;36m' readonly CEND='\033[00m' readonly DIRTY_CHAR="*" readonly TRACKED_CHAR="!" @@ -49,6 +50,10 @@ readonly UNTRACKED_CHAR="?" readonly STAGED_CHAR="+" readonly STASHED_CHAR="$" readonly GIT_CLEAN_MSG_REGEX="nothing to commit,? (?working directory clean)?" +readonly ROOT_COLOR=${RED} +readonly USER_COLOR=${PURPLE} +readonly PWD_COLOR=${BLUE} +readonly GIT_COLOR='' readonly CLEAN_COLOR=${GREEN} readonly DIRTY_COLOR=${YELLOW} readonly TRACKED_COLOR=${YELLOW} @@ -58,8 +63,7 @@ readonly STASHED_COLOR=${LIME} readonly BEHIND_COLOR=${RED} readonly AHEAD_COLOR=${YELLOW} readonly EVEN_COLOR=${GREEN} -readonly ROOT_COLOR=${RED} -readonly USER_COLOR=${PURPLE} +readonly DATE_COLOR=${AQUA} readonly LOGIN=$(whoami) readonly ANSI_FILTER_REGEX="s|\\\033\[([0-9]{1,2}(;[0-9]{1,2})?)?m||g" readonly TIMESTAMP_LEN=10 @@ -69,13 +73,9 @@ readonly TIMESTAMP_LEN=10 Dbg() { (>&2 echo -e "[GitStatusPrompt]: $@") ; } -DbgTruncateToWidth() # (login_host_len current_dir_len status_msg_len prompt_len current_tty_w prompt_mod truncate_len min_len max_len) +DbgTruncateToWidth() { - local login_host_len=$1 ; local current_dir_len=$2 ; local status_msg_len=$3 ; local prompt_len=$4 ; - local current_tty_w=$5 ; local prompt_mod=$6 ; local truncate_len=$7 ; - local min_len=$8 ; local max_len=$9 ; - - Dbg "(login_host_len=${#login_host_len}) + (current_dir_len=${#current_dir_len}) + (status_msg_len=${#status_msg_len}) = (prompt_len=$prompt_len)" + Dbg "(login_host_len=${#1}) + (current_dir_len=${#2}) + (status_msg_len=${#3}) = (prompt_len=$prompt_len)" Dbg "(current_tty_w=$current_tty_w) - (prompt_mod=$prompt_mod) = (truncate_len=$truncate_len)" Dbg "(min_len=$min_len) < (truncate_len=$truncate_len) < (max_len=$max_len)" Dbg 123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_.... @@ -223,54 +223,26 @@ LoginHost() CurrentDir() { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } -DbgTruncateToWidth() -{ - (>&2 echo "(login_host_len=${#1}) + (current_dir_len=${#2}) + (status_msg_len=${#3}) = (prompt_len=$prompt_len)") - (>&2 echo "(current_tty_w=$current_tty_w) - (prompt_mod=$prompt_mod) = (truncate_len=$truncate_len)") - (>&2 echo "(min_len=$min_len) < (truncate_len=$truncate_len) < (max_len=$max_len)") - (>&2 echo 123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_....) -} - -DbgGitStatusAssertions() -{ - (>&2 echo "AssertIsValidRepo=$( AssertIsValidRepo && echo 'true' || echo 'false - bailing')") - (>&2 echo "AssertIsNotBareRepo=$( AssertIsNotBareRepo && echo 'true' || echo 'false - bailing')") - (>&2 echo "AssertHasCommits=$( AssertHasCommits && echo 'true' || echo 'false - bailing')") - (>&2 echo "AssertIsNotIgnoredDir=$(AssertIsNotIgnoredDir && echo 'true' || echo 'false' )") -} - -DbgGitStatusState() -{ - local git_dir=$1 ; local current_branch=$2 ; local detached_msg=$3 ; - - (>&2 echo "current_branch=${current_branch}") - (>&2 echo "is_valid_git_dir=$( [[ -n "${git_dir}" ]] && echo 'true' || echo 'false - bailing')") - (>&2 echo "is_valid_current_branch=$([[ -n "${current_branch}" ]] && echo 'true' || echo 'false - bailing')") - (>&2 echo "is_detached=$( [[ -n "${detached_msg}" ]] && echo 'true' || echo 'false' )") - (>&2 echo "is_local_branch=$( IsLocalBranch ${current_branch} && echo 'true' || echo 'false - bailing')") -} - -TruncateToWidth() # (fixed_len_msg truncate_msg) +TruncateToWidth() # (fixed_len_prefix truncate_msg) { - local fixed_len_msg=$1 + local fixed_len_prefix=$( [[ "$1" ]] && echo "$1 " ) local truncate_msg=$2 # trunctuate to console width - local login_host=$( echo $(LoginHost) | sed -r ${ANSI_FILTER_REGEX} --) + local login_host=$( echo $(LoginHost) | sed -r ${ANSI_FILTER_REGEX} --) local current_dir=$(CurrentDir ) - local status_msg=$( echo $fixed_len_msg | sed -r ${ANSI_FILTER_REGEX} --) + local status_msg=$( echo "${fixed_len_prefix}" | sed -r ${ANSI_FILTER_REGEX} --) local current_tty_w=$(( $(stty -F /dev/tty size | cut -d ' ' -f2) )) local prompt_len=$(( ${#login_host} + ${#current_dir} + ${#status_msg} )) local prompt_mod=$(( ${prompt_len} % ${current_tty_w} )) local truncate_len=$(( ${current_tty_w} - ${prompt_mod} )) - local min_len=$(( ${TIMESTAMP_LEN} + 1 )) - local max_len=$(( ${current_tty_w} - 1 )) + local min_len=${TIMESTAMP_LEN} + local max_len=${current_tty_w} [ ${truncate_len} -lt ${min_len} -o ${truncate_len} -gt ${max_len} ] && truncate_len=0 - local truncate_msg=${truncate_msg:0:truncate_len} # DbgTruncateToWidth - echo "${fixed_len_msg}${truncate_msg}" + echo "${truncate_msg:0:truncate_len}" } GitStatus() @@ -353,9 +325,11 @@ GitStatus() local author_date=$(git log --max-count=1 --format=format:"%ai" 2> /dev/null ) local commit_log=$( git log --max-count=1 --format=format:\"%s\" | sed -r "s|\"||g") [[ -n "${commit_log}" ]] || commit_log='' - local commit_msg=" ${author_date:0:TIMESTAMP_LEN} ${commit_log}" + local commit_msg="${author_date:0:TIMESTAMP_LEN} ${commit_log}" + commit_msg="$(TruncateToWidth "${branch_status_msg}" "${commit_msg}")" + commit_msg="${DATE_COLOR}${commit_msg:0:TIMESTAMP_LEN}${CEND} ${commit_msg:(TIMESTAMP_LEN + 1)}" - echo $(TruncateToWidth "${branch_status_msg}" "${commit_msg}") + echo "${branch_status_msg} ${commit_msg}" } @@ -364,8 +338,8 @@ GitStatus() GitStatusPrompt() { local login_host="$(LoginColor)$(LoginHost)${CEND}" - local pwd_path="${BLUE}$(CurrentDir)${CEND}" - local git_status="${GREEN}$(GitStatus)${CEND}" + local pwd_path="${PWD_COLOR}$(CurrentDir)${CEND}" + local git_status="$(GitStatus)" local prompt_tail='\n$ ' # DbgGitStatusPrompt From 799d032d8d5080d50bdc83a25d5300c5a39e599f Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:54 +0000 Subject: [PATCH 25/30] introduce env vars for ignore dirs --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 06d8bbf..835cd77 100755 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -33,7 +33,8 @@ # this script can be sluggish in very large repos -declare -r -a IGNORED_DIRS=( $(grep -v ^# "$(dirname ${BASH_SOURCE})/ignore_dirs" 2> /dev/null) ) +CFG_IGNORED_DIRS=( $(grep -v ^# "$(dirname ${BASH_SOURCE})/ignore_dirs" 2> /dev/null) ) + readonly RED='\033[1;31m' readonly YELLOW='\033[01;33m' @@ -134,8 +135,8 @@ AssertIsNotIgnoredDir() { local ignored_dir - for ignored_dir in ${IGNORED_DIRS[*]} - do [[ "$(pwd)" =~ ^${ignored_dir} ]] && return 1 + for ignored_dir in ${CFG_IGNORED_DIRS[@]} ${GSP_IGNORED_DIRS[@]} + do [[ "$(pwd)/" =~ ^${ignored_dir} ]] && return 1 done return 0 @@ -245,16 +246,13 @@ TruncateToWidth() # (fixed_len_prefix truncate_msg) echo "${truncate_msg:0:truncate_len}" } + +## business ## + GitStatus() { # DbgGitStatusAssertions - # ensure we are in a valid, non-bare git repository, with commits, and not blacklisted - ! AssertIsValidRepo && return - ! AssertIsNotBareRepo && echo $(TruncateToWidth "" "(bare repo)" ) && return - ! AssertHasCommits && echo $(TruncateToWidth "" "(no commits)") && return - ! AssertIsNotIgnoredDir && echo $(TruncateToWidth "" "(heavy-git)" ) && return - # get current state local git_dir="$( GitDir )" local current_branch=$(CurrentBranch ) @@ -267,6 +265,12 @@ GitStatus() [[ -n "${detached_msg}" ]] && echo "${detached_msg}" && return IsLocalBranch ${current_branch} || return + # ensure we are in a valid, non-bare git repository, with commits, and not blacklisted + ! AssertIsValidRepo && return + ! AssertIsNotBareRepo && echo "$(TruncateToWidth "" "(bare repo)" )" && return + ! AssertHasCommits && echo "$(TruncateToWidth "" "(no commits)" )" && return + ! AssertIsNotIgnoredDir && echo "$(TruncateToWidth "" "(${current_branch})")" && return + # get remote tracking branch local local_branch local remote_branch From 2bb8207699e88a609f01c6fb84e735c16c48cf84 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:55 +0000 Subject: [PATCH 26/30] housekeeping, supress errors, bugfix detached state --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh old mode 100755 new mode 100644 index 835cd77..870e65e --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -53,8 +53,7 @@ readonly STASHED_CHAR="$" readonly GIT_CLEAN_MSG_REGEX="nothing to commit,? (?working directory clean)?" readonly ROOT_COLOR=${RED} readonly USER_COLOR=${PURPLE} -readonly PWD_COLOR=${BLUE} -readonly GIT_COLOR='' +readonly PWD_COLOR=${AQUA} readonly CLEAN_COLOR=${GREEN} readonly DIRTY_COLOR=${YELLOW} readonly TRACKED_COLOR=${YELLOW} @@ -64,7 +63,7 @@ readonly STASHED_COLOR=${LIME} readonly BEHIND_COLOR=${RED} readonly AHEAD_COLOR=${YELLOW} readonly EVEN_COLOR=${GREEN} -readonly DATE_COLOR=${AQUA} +readonly DATE_COLOR=${BLUE} readonly LOGIN=$(whoami) readonly ANSI_FILTER_REGEX="s|\\\033\[([0-9]{1,2}(;[0-9]{1,2})?)?m||g" readonly TIMESTAMP_LEN=10 @@ -142,16 +141,16 @@ AssertIsNotIgnoredDir() return 0 } -GitDir() { echo "$(git rev-parse --show-toplevel )/.git" ; } +GitDir() { echo "$(git rev-parse --show-toplevel 2> /dev/null)/.git" ; } -CurrentBranch() { git rev-parse --abbrev-ref HEAD ; } +CurrentBranch() { git rev-parse --abbrev-ref HEAD 2> /dev/null ; } DetachedMsg() # (git_dir current_branch) { local git_dir=$1 local current_branch=$2 - [[ -n "${git_dir}" ]] || return ; + [[ -n "${git_dir}" && -n "${current_branch}" ]] || return if [[ -f "${git_dir}/MERGE_HEAD" && ! -z "$(cat ${git_dir}/MERGE_MSG | grep -E '^Merge')" ]] then local merge_msg=$(cat ${git_dir}/MERGE_MSG | grep -E "^Merge (.*)(branch|tag|commit) '" | \ @@ -177,12 +176,12 @@ IsLocalBranch() # (branch_name) { local branch=$1 - [[ -n "$(git branch -a | grep -E "^.* $branch$")" ]] + [[ -n "$(git branch -a 2> /dev/null | grep -E "^.* $branch$")" ]] } HasAnyChanges() { - [ "$(git status 2> /dev/null | tail -n1 | grep -E "${GIT_CLEAN_MSG_REGEX}")" ] || echo "${DIRTY_CHAR}" + [[ "$(git status 2> /dev/null | tail -n1 | grep -E "${GIT_CLEAN_MSG_REGEX}")" ]] || echo "${DIRTY_CHAR}" } HasTrackedChanges() @@ -192,7 +191,7 @@ HasTrackedChanges() HasUntrackedChanges() { - [ -n "$(git ls-files --others --exclude-standard)" ] && echo "${UNTRACKED_CHAR}" + [[ -n "$(git ls-files --others --exclude-standard 2> /dev/null)" ]] && echo "${UNTRACKED_CHAR}" } HasStagedChanges() @@ -211,15 +210,15 @@ SyncStatus() # (local_branch remote_branch status) local remote_branch=$2 local status=$(git rev-list --left-right ${local_branch}...${remote_branch} -- 2>/dev/null) - [ $(( $? )) -eq 0 ] && echo ${status} + [[ $(( $? )) -eq 0 ]] && echo ${status} } LoginColor() { (( ${EUID} )) && echo ${USER_COLOR} || echo ${ROOT_COLOR} ; } LoginHost() { - [ -z "${STY}" ] && echo "${USER}@${HOSTNAME}${CEND}:" || \ - echo "[${USER}@${HOSTNAME}]${CEND}:" # GNU screen + [[ -z "${STY}" ]] && echo "${USER}@${HOSTNAME}${CEND}:" || \ + echo "[${USER}@${HOSTNAME}]${CEND}:" # GNU screen } CurrentDir() { local pwd="${PWD}/" ; echo "${pwd/\/\//\/}" ; } @@ -239,7 +238,7 @@ TruncateToWidth() # (fixed_len_prefix truncate_msg) local truncate_len=$(( ${current_tty_w} - ${prompt_mod} )) local min_len=${TIMESTAMP_LEN} local max_len=${current_tty_w} - [ ${truncate_len} -lt ${min_len} -o ${truncate_len} -gt ${max_len} ] && truncate_len=0 + [[ ${truncate_len} -lt ${min_len} || ${truncate_len} -gt ${max_len} ]] && truncate_len=0 # DbgTruncateToWidth @@ -282,15 +281,15 @@ GitStatus() done < <(git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads) # set branch color based on dirty status - local branch_color=$([ -z "$(HasAnyChanges)" ] && echo ${CLEAN_COLOR} || echo ${DIRTY_COLOR}) + local branch_color=$( [[ -z "$(HasAnyChanges)" ]] && echo ${CLEAN_COLOR} || echo ${DIRTY_COLOR} ) # get sync status if (( ${should_count_divergences} )) - then local status=$( SyncStatus ${current_branch} ${remote_branch} ) - local n_behind=$( echo "${status}" | tr " " "\n" | grep -c '^>' ) - local n_ahead=$( echo "${status}" | tr " " "\n" | grep -c '^<' ) - local behind_color=$([ "${n_behind}" -ne 0 ] && echo ${BEHIND_COLOR} || echo ${EVEN_COLOR}) - local ahead_color=$( [ "${n_ahead}" -ne 0 ] && echo ${AHEAD_COLOR} || echo ${EVEN_COLOR}) + then local status=$( SyncStatus ${current_branch} ${remote_branch} ) + local n_behind=$( echo "${status}" | tr " " "\n" | grep -c '^>' ) + local n_ahead=$( echo "${status}" | tr " " "\n" | grep -c '^<' ) + local behind_color=$( [[ "${n_behind}" -ne 0 ]] && echo ${BEHIND_COLOR} || echo ${EVEN_COLOR} ) + local ahead_color=$( [[ "${n_ahead}" -ne 0 ]] && echo ${AHEAD_COLOR} || echo ${EVEN_COLOR} ) fi # get tracked status @@ -337,7 +336,7 @@ GitStatus() } -# main entry point +## main entry ## GitStatusPrompt() { From ac921d48ed74c8a6df88765d197d9689a2e1a4c1 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:55 +0000 Subject: [PATCH 27/30] refactor dirty chars functions --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 870e65e..1196cfa 100644 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -181,29 +181,39 @@ IsLocalBranch() # (branch_name) HasAnyChanges() { - [[ "$(git status 2> /dev/null | tail -n1 | grep -E "${GIT_CLEAN_MSG_REGEX}")" ]] || echo "${DIRTY_CHAR}" + ! [[ "$(git status 2> /dev/null | tail -n1 | grep -E "${GIT_CLEAN_MSG_REGEX}")" ]] } HasTrackedChanges() { - git diff --no-ext-diff --quiet --exit-code || echo "${TRACKED_CHAR}" + ! git diff --no-ext-diff --quiet --exit-code } HasUntrackedChanges() { - [[ -n "$(git ls-files --others --exclude-standard 2> /dev/null)" ]] && echo "${UNTRACKED_CHAR}" + [[ -n "$(git ls-files --others --exclude-standard 2> /dev/null)" ]] } HasStagedChanges() { - git diff-index --cached --quiet HEAD -- || echo "${STAGED_CHAR}" + ! git diff-index --cached --quiet HEAD -- } HasStashedChanges() { - git rev-parse --verify refs/stash > /dev/null 2>&1 && echo "${STASHED_CHAR}" + git rev-parse --verify refs/stash > /dev/null 2>&1 } +AnyChanges() { HasAnyChanges && echo -n "${DIRTY_CHAR}" ; } + +TrackedChanges() { HasTrackedChanges && echo -n "${TRACKED_CHAR}" ; } + +UntrackedChanges() { HasUntrackedChanges && echo -n "${UNTRACKED_CHAR}" ; } + +StagedChanges() { HasStagedChanges && echo -n "${STAGED_CHAR}" ; } + +StashedChanges() { HasStashedChanges && echo -n "${STASHED_CHAR}" ; } + SyncStatus() # (local_branch remote_branch status) { local local_branch=$1 @@ -293,16 +303,16 @@ GitStatus() fi # get tracked status - local tracked=$(HasTrackedChanges) + local tracked=$(TrackedChanges) # get untracked status - local untracked=$(HasUntrackedChanges) + local untracked=$(UntrackedChanges) # get staged status - local staged=$(HasStagedChanges) + local staged=$(StagedChanges) # get stashed status - local stashed=$(HasStashedChanges) + local stashed=$(StashedChanges) # DbgGitStatusChars From 3156858bb71f5fd13f4893c584c047b52cd37c51 Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:55 +0000 Subject: [PATCH 28/30] untrracked color --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 1196cfa..f05d3d3 100644 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -56,6 +56,7 @@ readonly USER_COLOR=${PURPLE} readonly PWD_COLOR=${AQUA} readonly CLEAN_COLOR=${GREEN} readonly DIRTY_COLOR=${YELLOW} +readonly UNO_COLOR=${LIME} readonly TRACKED_COLOR=${YELLOW} readonly UNTRACKED_COLOR=${RED} readonly STAGED_COLOR=${GREEN} @@ -291,7 +292,9 @@ GitStatus() done < <(git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads) # set branch color based on dirty status - local branch_color=$( [[ -z "$(HasAnyChanges)" ]] && echo ${CLEAN_COLOR} || echo ${DIRTY_COLOR} ) + local branch_color=$( ( HasTrackedChanges && echo -n ${DIRTY_COLOR} ) || + ( HasAnyChanges && echo -n ${UNO_COLOR} ) || + echo -n ${CLEAN_COLOR} ) # get sync status if (( ${should_count_divergences} )) From 66ef41e390d2ed580dd2b3812e90d0dff3d5bdbc Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:56 +0000 Subject: [PATCH 29/30] housekeeping --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index f05d3d3..9c0a98c 100644 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -93,6 +93,7 @@ DbgGitStatusAssertions() DbgGitStatusState() { Dbg "current_branch=${current_branch}" + Dbg "git_dir=${git_dir}" Dbg "is_valid_git_dir=$( [[ -n "${git_dir}" ]] && echo 'true' || echo 'false - bailing')" Dbg "is_valid_current_branch=$([[ -n "${current_branch}" ]] && echo 'true' || echo 'false - bailing')" Dbg "is_detached=$( [[ -n "${detached_msg}" ]] && echo 'true' || echo 'false' )" @@ -271,15 +272,15 @@ GitStatus() # DbgGitStatusState # validate current state - [[ -n "${git_dir}" && -n "${current_branch}" ]] || return - [[ -n "${detached_msg}" ]] && echo "${detached_msg}" && return - IsLocalBranch ${current_branch} || return + [[ -n "${git_dir}" && -n "${current_branch}" ]] || return + [[ -z "${detached_msg}" ]] || ! echo "${detached_msg}" || return + IsLocalBranch ${current_branch} || return # ensure we are in a valid, non-bare git repository, with commits, and not blacklisted - ! AssertIsValidRepo && return - ! AssertIsNotBareRepo && echo "$(TruncateToWidth "" "(bare repo)" )" && return - ! AssertHasCommits && echo "$(TruncateToWidth "" "(no commits)" )" && return - ! AssertIsNotIgnoredDir && echo "$(TruncateToWidth "" "(${current_branch})")" && return + AssertIsValidRepo || return + AssertIsNotBareRepo || ! echo "$(TruncateToWidth "" "(bare repo)" )" || return + AssertHasCommits || ! echo "$(TruncateToWidth "" "(no commits)" )" || return + AssertIsNotIgnoredDir || ! echo "$(TruncateToWidth "" "(${current_branch})")" || return # get remote tracking branch local local_branch From 67dc6af810d00f5459ec321cf003b3a3cb1c77fb Mon Sep 17 00:00:00 2001 From: bill-auger Date: May 27 2022 21:56:56 +0000 Subject: [PATCH 30/30] warn about new 'unsafe repository' error --- diff --git a/git-status-prompt.sh b/git-status-prompt.sh index 9c0a98c..eb9f3ce 100644 --- a/git-status-prompt.sh +++ b/git-status-prompt.sh @@ -1,7 +1,7 @@ #!/bin/bash # git-status-prompt.sh - pretty format git sync and dirty status for shell prompt -# Copyright 2013-2019 bill-auger +# Copyright 2013-2020, 2022 bill-auger # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -96,6 +96,7 @@ DbgGitStatusState() Dbg "git_dir=${git_dir}" Dbg "is_valid_git_dir=$( [[ -n "${git_dir}" ]] && echo 'true' || echo 'false - bailing')" Dbg "is_valid_current_branch=$([[ -n "${current_branch}" ]] && echo 'true' || echo 'false - bailing')" + Dbg "is_unsafe=$( [[ -n "${unsafe_msg}" ]] && echo 'true' || echo 'false' )" Dbg "is_detached=$( [[ -n "${detached_msg}" ]] && echo 'true' || echo 'false' )" Dbg "is_local_branch=$( IsLocalBranch ${current_branch} && echo 'true' || echo 'false - bailing')" } @@ -147,6 +148,14 @@ GitDir() { echo "$(git rev-parse --show-toplevel 2> /dev/null)/.git" ; } CurrentBranch() { git rev-parse --abbrev-ref HEAD 2> /dev/null ; } +UnsafeMsg() +{ + local git_status="$(git status 2>&1 1>/dev/null | sed '/^$/d')" + local my_advice="\nor, simply white-list them all:\n\tgit config --global --add safe.directory *" + + [[ "${git_status}" =~ ^'fatal: unsafe repository ' ]] && echo -e "${git_status}${my_advice}" +} + DetachedMsg() # (git_dir current_branch) { local git_dir=$1 @@ -265,6 +274,7 @@ GitStatus() # DbgGitStatusAssertions # get current state + local unsafe_msg="$( UnsafeMsg )" local git_dir="$( GitDir )" local current_branch=$(CurrentBranch ) local detached_msg="$( DetachedMsg "${git_dir}" "${current_branch}")" @@ -272,6 +282,7 @@ GitStatus() # DbgGitStatusState # validate current state + [[ -z "${unsafe_msg}" ]] || ! echo "${unsafe_msg}" >&2 || return [[ -n "${git_dir}" && -n "${current_branch}" ]] || return [[ -z "${detached_msg}" ]] || ! echo "${detached_msg}" || return IsLocalBranch ${current_branch} || return