diff --git a/git-prompt.conf b/git-prompt.conf index dd88fd0..cfd9f15 100644 --- a/git-prompt.conf +++ b/git-prompt.conf @@ -10,7 +10,8 @@ # error_bell=off # sound terminal bell when command return code is not zero. (use setterm to set pitch and duration) # max_file_list_length=100 # in characters - +# count_only=off # off - display file list; on - display file count +# rawhex_len=5 # length of git rawhex revision id display (use 0 to hide it)) ############################################################ MODULES diff --git a/git-prompt.sh b/git-prompt.sh index f951623..74ddab0 100755 --- a/git-prompt.sh +++ b/git-prompt.sh @@ -54,8 +54,13 @@ max_file_list_length=${max_file_list_length:-100} upcase_hostname=${upcase_hostname:-on} + count_only=${count_only:-off} + rawhex_len=${rawhex_len:-5} + aj_max=20 + timer_starts_at=10 # min seconds for timer + timer_blacklist="less*|vi*|man*|emacs*|fg*" # prefix-match on the command line ##################################################################### post config @@ -190,7 +195,7 @@ cwd_truncate() { # trunc middle if over limit if [[ ${#path_middle} -gt $(( $cwd_middle_max + ${#elipses_marker} + 5 )) ]]; then - + # truncate middle_tail=${path_middle:${#path_middle}-${cwd_middle_max}} @@ -227,7 +232,7 @@ set_shell_label() { screen_label "$*" ;; - xterm* | rxvt* | gnome-terminal | konsole | eterm | wterm ) + xterm* | rxvt* | gnome* | konsole | eterm | wterm ) # is there a capability which we can to test # for "set term title-bar" and its escapes? xterm_label "$plain_who_where $@" @@ -261,7 +266,7 @@ set_shell_label() { # we don't need tty name under X11 case $TERM in - xterm* | rxvt* | gnome-terminal | konsole | eterm | wterm ) unset tty ;; + xterm* | rxvt* | gnome* | konsole | eterm | wterm ) unset tty ;; *);; esac @@ -389,7 +394,10 @@ parse_git_complete() { return fi - complete -f -W "$( + # only define a git completion if none exists + # the one provided by the system or git itself + # is more elaborate than this one. + complete -p git 1>/dev/null 2>&1 || complete -f -W "$( echo `git branch -a | sed -e s/[\ \*]//g | cut -f 1 -d ' ' | uniq`; \ echo `git remote | sed -e s/[\ \*]//g | cut -f 1 -d ' ' | uniq`; \ echo `git | tail -23 | head -21 | cut -d ' ' -f 4`; \ @@ -411,46 +419,55 @@ parse_git_status() { [[ -n ${git_dir/./} ]] || return 1 vcs=git - parse_git_complete ########################################################## GIT STATUS - file_regex='\([^/]*\/\{0,1\}\).*' - added_files=() - modified_files=() - untracked_files=() + added_files=() + modified_files=() + untracked_files=() + [[ $rawhex_len -gt 0 ]] && freshness="$dim=" + unset branch status modified added clean init added mixed untracked op detached - # quoting hell + # work around for VTE bug (hang on printf) + unset VTE_VERSION + + # info not in porcelain status eval " $( - git status 2>/dev/null | + LANG=C git status 2>/dev/null | sed -n ' - s/^# On branch /branch=/p - s/^nothing to commit (working directory clean)/clean=clean/p - s/^# Initial commit/init=init/p - - /^# Changes to be committed:/,/^# [A-Z]/ { - s/^# Changes to be committed:/added=added;/p - - s/^# modified: '"$file_regex"'/ [[ \" ${added_files[*]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\"/p - s/^# new file: '"$file_regex"'/ [[ \" ${added_files[*]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\"/p - s/^# renamed:[^>]*> '"$file_regex"'/ [[ \" ${added_files[*]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\"/p - s/^# copied:[^>]*> '"$file_regex"'/ [[ \" ${added_files[*]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\"/p - } - - /^# Changed but not updated:/,/^# [A-Z]/ { - s/^# Changed but not updated:/modified=modified;/p - s/^# modified: '"$file_regex"'/ [[ \" ${modified_files[*]} \" =~ \" \1 \" ]] || modified_files[${#modified_files[@]}]=\"\1\"/p - s/^# unmerged: '"$file_regex"'/ [[ \" ${modified_files[*]} \" =~ \" \1 \" ]] || modified_files[${#modified_files[@]}]=\"\1\"/p - } - - /^# Untracked files:/,/^[^#]/{ - s/^# Untracked files:/untracked=untracked;/p - s/^# '"$file_regex"'/ [[ \" ${untracked_files[*]} ${modified_files[*]} ${added_files[*]} \" =~ \" \1 \" ]] || untracked_files[${#untracked_files[@]}]=\"\1\"/p - } + s/^\(# \)*On branch /branch=/p + s/^nothing to commi.*/clean=clean/p + s/^\(# \)*Initial commi.*/init=init/p + s/^\(# \)*Your branch is ahead of \(.\).\+\1 by [[:digit:]]\+ commit.*/freshness=${WHITE}↑/p + s/^\(# \)*Your branch is behind \(.\).\+\1 by [[:digit:]]\+ commit.*/freshness=${YELLOW}↓/p + s/^\(# \)*Your branch and \(.\).\+\1 have diverged.*/freshness=${YELLOW}↕/p ' )" - if ! grep -q "^ref:" $git_dir/HEAD 2>/dev/null; then + # porcelain file list + # TODO: sed-less -- http://tldp.org/LDP/abs/html/arrays.html -- Example 27-5 + + # git bug: (was reported to git@vger.kernel.org ) + # echo 1 > "with space" + # git status --porcelain + # ?? with space <------------ NO QOUTES + # git add with\ space + # git status --porcelain + # A "with space" <------------- WITH QOUTES + + eval " $( + LANG=C git status --porcelain 2>/dev/null | + sed -n ' + s,^[MARC]. \([^\"][^/]*/\?\).*, added=added; [[ \" ${added_files[@]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\",p + s,^[MARC]. \"\([^/]\+/\?\).*\"$, added=added; [[ \" ${added_files[@]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\",p + s,^.[MAU] \([^\"][^/]*/\?\).*, modified=modified; [[ \" ${modified_files[@]} \" =~ \" \1 \" ]] || modified_files[${#modified_files[@]}]=\"\1\",p + s,^.[MAU] \"\([^/]\+/\?\).*\"$, modified=modified; [[ \" ${modified_files[@]} \" =~ \" \1 \" ]] || modified_files[${#modified_files[@]}]=\"\1\",p + s,^?? \([^\"][^/]*/\?\).*, untracked=untracked; [[ \" ${untracked_files[@]} \" =~ \" \1 \" ]] || untracked_files[${#untracked_files[@]}]=\"\1\",p + s,^?? \"\([^/]\+/\?\).*\"$, untracked=untracked; [[ \" ${untracked_files[@]} \" =~ \" \1 \" ]] || untracked_files[${#untracked_files[@]}]=\"\1\",p + ' # |tee /dev/tty + )" + + if ! grep -q "^ref:" "$git_dir/HEAD" 2>/dev/null; then detached=detached fi @@ -497,12 +514,16 @@ parse_git_status() { #### GET GIT HEX-REVISION - rawhex=`git rev-parse HEAD 2>/dev/null` - rawhex=${rawhex/HEAD/} - rawhex=${rawhex:0:6} + if [[ $rawhex_len -gt 0 ]] ; then + rawhex=`git rev-parse HEAD 2>/dev/null` + rawhex=${rawhex/HEAD/} + rawhex="$hex_vcs_color${rawhex:0:$rawhex_len}" + else + rawhex="" + fi #### branch - branch=${branch/master/M} + branch=${branch/#master/M} # another method of above: # branch=$(git symbolic-ref -q HEAD || { echo -n "detached:" ; git name-rev --name-only HEAD 2>/dev/null; } ) @@ -510,7 +531,7 @@ parse_git_status() { ### compose vcs_info - if [[ $init ]]; then + if [[ $init ]]; then vcs_info=${white}init else @@ -525,7 +546,7 @@ parse_git_status() { fi #branch="<$branch>" fi - vcs_info="$branch$white=$rawhex" + vcs_info="$branch$freshness$rawhex" fi } @@ -581,9 +602,15 @@ parse_vcs_status() { ### file list unset file_list - [[ ${added_files[0]} ]] && file_list+=" "$added_vcs_color${added_files[@]} - [[ ${modified_files[0]} ]] && file_list+=" "$modified_vcs_color${modified_files[@]} - [[ ${untracked_files[0]} ]] && file_list+=" "$untracked_vcs_color${untracked_files[@]} + if [[ $count_only = "on" ]] ; then + [[ ${added_files[0]} ]] && file_list+=" "${added_vcs_color}+${#added_files[@]} + [[ ${modified_files[0]} ]] && file_list+=" "${modified_vcs_color}*${#modified_files[@]} + [[ ${untracked_files[0]} ]] && file_list+=" "${untracked_vcs_color}?${#untracked_files[@]} + else + [[ ${added_files[0]} ]] && file_list+=" "$added_vcs_color${added_files[@]} + [[ ${modified_files[0]} ]] && file_list+=" "$modified_vcs_color${modified_files[@]} + [[ ${untracked_files[0]} ]] && file_list+=" "$untracked_vcs_color${untracked_files[@]} + fi [[ ${vim_files} ]] && file_list+=" "${RED}vim:${vim_files} file_list=${file_list:+:$file_list} @@ -603,16 +630,63 @@ parse_vcs_status() { #tail_local="${tail_local+$vcs_color $tail_local}${dir_color}" } -disable_set_shell_label() { +# this is stolen from http://www.twistedmatrix.com/users/glyph/preexec.bash.txt +# do nothing we could do in the trap hook directly +preexec_invoke_exec () { + if [[ -z "$preexec_interactive_mode" ]] + then + # We're doing something related to displaying the prompt. Let the + # prompt set the title instead of me. + return + else + # If we're in a subshell, then the prompt won't be re-displayed to put + # us back into interactive mode, so let's not set the variable back. + # In other words, if you have a subshell like + # (sleep 1; sleep 2) + # You want to see the 'sleep 2' as a set_command_title as well. + if [[ 0 -eq "$BASH_SUBSHELL" ]] + then + preexec_interactive_mode="" + fi + fi + if [[ "prompt_command_function" == "$BASH_COMMAND" ]] + then + # Sadly, there's no cleaner way to detect two prompts being displayed + # one after another. This makes it important that PROMPT_COMMAND + # remain set _exactly_ as below in preexec_install. Let's switch back + # out of interactive mode and not trace any of the commands run in + # precmd. + + # Given their buggy interaction between BASH_COMMAND and debug traps, + # versions of bash prior to 3.1 can't detect this at all. + preexec_interactive_mode="" + return + fi + + # In more recent versions of bash, this could be set via the "BASH_COMMAND" + # variable, but using history here is better in some ways: for example, "ps + # auxf | less" will show up with both sides of the pipe if we use history, + # but only as "ps auxf" if not. + local this_command=`history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`; + + # If none of the previous checks have earlied out of this function, then + # the command is in fact interactive and we should invoke the user's + # preexec hook with the running command as an argument. + if [[ -n "$this_command" ]] ; then + preexec_command_function "$this_command" + fi + } + +disable_preexec_command_function() { trap - DEBUG >& /dev/null } -# show currently executed command in label -enable_set_shell_label() { - disable_set_shell_label - # check for BASH_SOURCE being empty, no point running set_shell_label on every line of .bashrc - trap '[[ -z "$BASH_SOURCE" && ($BASH_COMMAND != prompt_command_function) ]] && - set_shell_label $BASH_COMMAND' DEBUG >& /dev/null + +# register a preexec hook eg to show currently executed command in label +enable_preexec_command_function() { + disable_preexec_command_function + + trap '[[ -z "$COMP_LINE" ]] && preexec_invoke_exec' DEBUG >& /dev/null } # autojump (see http://wiki.github.com/joelthelion/autojump) @@ -633,13 +707,17 @@ alias jumpstart='echo ${aj_dir_list[@]}' ###################################################################### PROMPT_COMMAND +preexec_interactive_mode="" prompt_command_function() { rc="$?" + timer_stop + preexec_interactive_mode="yes" + if [[ "$rc" == "0" ]]; then rc="" else - rc="$rc_color$rc$colors_reset$bell " + rc="$rc_color[$rc]$colors_reset$bell " fi cwd=${PWD/$HOME/\~} # substitute "~" @@ -661,9 +739,75 @@ prompt_command_function() { unset head_local tail_local pwd } - PROMPT_COMMAND=prompt_command_function +timer_start() { + local command=$1 + # unless applying the blacklist as prefix remove pattern results + # in and empty string (it matches) + if [ -n "${command##+(${TIMER_BLACKLIST:-timer_blacklist})}" ] ; then + export TIMER_COMMAND="$1" + export TIMER_STARTED_AT=$(date +'%s') + fi +} + +timer_stop() { + if [ -z "$TIMER_COMMAND" ] ; then + return # no command to time + fi + + local stopped_at=$(date +'%s') + local started_at=${TIMER_STARTED_AT:-$stopped_at} + let elapsed=$stopped_at-$started_at + local min=${TIMER_STARTS_AT:-${timer_starts_at:-10}} + if [ $elapsed -gt $min ]; then + timer_message + fi + export TIMER_COMMAND="" +} + +timer_message() { + local stamper='' + if [ $elapsed -gt 60 ] ; then + if [ $elapsed -gt 3600 ] ; then + stamper="%-Hh, %-Mm, %-Ss" + else + stamper="%-Mm, %-Ss" + fi + else + stamper="%-S seconds" + fi + local human_elapsed=$(date -d "@$elapsed" +"$stamper") + local message="took $human_elapsed" + + # decide which status to use + if [ "$?" == "0" ] ; then + result="completed" + else + result="FAILED ($status)" + fi + local title="$TIMER_COMMAND $result" + + # TODO coloring + echo "$human_elapsed: $TIMER_COMMAND" + + if type -P notify-send >&/dev/null ; then + notify-send -i terminal "$title" "$message" + fi + if type -P growlnotify >&/dev/null ; then + growlnotify -s "$title" -m "$message" + fi +} + +preexec_command_function() { + local this_command="$1" + + set_shell_label "$this_command" + timer_start "$this_command" +} + + + PROMPT_COMMAND="prompt_command_function" - enable_set_shell_label + enable_preexec_command_function unset rc id tty modified_files file_list