diff --git a/plugins/vcs/vcs.plugin.zsh b/plugins/vcs/vcs.plugin.zsh new file mode 100644 index 000000000..5b0a277e8 --- /dev/null +++ b/plugins/vcs/vcs.plugin.zsh @@ -0,0 +1,623 @@ +# =====[ NAME ]================================================================= +# +# VCS Plugin + +# =====[ DESCRIPTION ]========================================================== +# +# Uniform prompt status for work trees originating from multiple vcs systems. +# +# =====[ FUNCTIONS ]============================================================ +# +# DISPLAY +# $(vcs_status_prompt) : top-level status indicator +# +# $(vcs_dirt_status) : indicator of and count for each type of dirty file +# : simple form can indicate just clean or dirty +# $(vcs_dirt_age) : time since last significant action on the work tree +# : helpful for determining staleness of a tree +# $(vcs_rev_short) : abbreviated form of current revision +# $(vcs_rev_long) : more verbose current revision information +# +# ------------------------------------------ +# FEATURE SUPPORT BY VCS +# ------------------------------------------ +# +# feature | git | hg | svn | bzr | +# ---------------------------------------------- +# vcs_status_prompt | O | O | O | X | +# vcs_dirt_status | O | O | O | X | +# vcs_dirt_age | O | O | O | X | +# vcs_rev_short | O | O | O | X | +# vcs_rev_long | O | O | O | X | +# +# O = implemented +# X = unimplemented + +# DETECTION +# $(vcs_is_git) : true if cwd is in a git work tree +# : (but returns false if in an ignored subdirectory) +# $(vcs_is_hg) : true if cwd is in a mercurial work tree +# $(vcs_is_svn) : true if cwd is in a subversion work tree +# +# CUSTOMIZATION +# see default implementations below for examples +# implement your own function with these names to override default behavior +# +# _vcs_prompt_git +# _vcs_prompt_hg +# _vcs_prompt_svn +# +# _vcs_rev_short_git +# _vcs_rev_short_hg +# _vcs_rev_short_svn +# +# _vcs_rev_long_git +# _vcs_rev_long_hg +# _vcs_rev_long_svn + +# =====[ CONFIGURABLE SETTINGS (and default values) ]=========================== +# Associative array for plugin settings +typeset -gA VCS_PLUGIN + +# $VCS_PLUGIN[separator] : [string] separates elements in the vcs prompt +VCS_PLUGIN[separator]=" | " + +# $VCS_PLUGIN[prompt_prefix] : [string] precedes contents of the vcs prompt +VCS_PLUGIN[prompt_prefix]="(" +# $$VCS_PLUGIN[prompt_suffix] : [string] proceeds contents of the vcs prompt +VCS_PLUGIN[prompt_suffix]=")" + +# $VCS_PLUGIN[dirt_status_verbosity] : [string] show symbols each type of dirty file +# : or a single dirty or clean symbol +VCS_PLUGIN[dirt_status_verbosity]="full" +#VCS_PLUGIN[dirt_status_verbosity]="simple" + +# $VCS_PLUGIN[is_clean_symbol] : [string] show when the work tree has no dirty files +VCS_PLUGIN[is_clean_symbol]="%{$fg[green]%}" +VCS_PLUGIN[is_clean_symbol]+="✔" +VCS_PLUGIN[is_clean_symbol]+="%{$reset_color%}" + +# $VCS_PLUGIN[is_dirty_symbol] : [string] show when the work tree has dirty files +VCS_PLUGIN[is_dirty_symbol]="%{$fg[red]%}" +VCS_PLUGIN[is_dirty_symbol]+="✖" +VCS_PLUGIN[is_dirty_symbol]+="%{$reset_color%}" + +#VCS_PLUGIN[untracked_is_dirty] : [flag] "true" to consider untracked files as dirty +VCS_PLUGIN[untracked_is_dirty]= +#VCS_PLUGIN[untracked_is_dirty]=true + +# $VCS_PLUGIN[untracked_symbol] : [string] show when there is at least one untracked file +VCS_PLUGIN[untracked_symbol]="%{$fg_bold[white]%}" +VCS_PLUGIN[untracked_symbol]+="✭" +VCS_PLUGIN[untracked_symbol]+="%{$reset_color%}" + +# $VCS_PLUGIN[added_symbol] : [string] show when there is at least one added file +VCS_PLUGIN[added_symbol]="%{$fg[green]%}" +VCS_PLUGIN[added_symbol]+="✚" +VCS_PLUGIN[added_symbol]+="%{$reset_color%}" + +# $VCS_PLUGIN[modified_symbol] : [string] show when there is at least one modified file +VCS_PLUGIN[modified_symbol]="%{$fg_bold[green]%}" +VCS_PLUGIN[modified_symbol]+="✹" +VCS_PLUGIN[modified_symbol]+="%{$reset_color%}" + +# $VCS_PLUGIN[renamed_symbol] : [string] show when there is at least one renamed file +VCS_PLUGIN[renamed_symbol]="%{$fg[yellow]%}" +VCS_PLUGIN[renamed_symbol]+="➜" +VCS_PLUGIN[renamed_symbol]+="%{$reset_color%}" + +# $VCS_PLUGIN[deleted_symbol] : [string] show when there is at least one deleted file +VCS_PLUGIN[deleted_symbol]="%{$fg[red]%}" +VCS_PLUGIN[deleted_symbol]+="✖" +VCS_PLUGIN[deleted_symbol]+="%{$reset_color%}" + +# $VCS_PLUGIN[unmerged_symbol] : [string] show when there is at least one unmerged or conflicted file +VCS_PLUGIN[unmerged_symbol]="%{$fg[blue]%}" +VCS_PLUGIN[unmerged_symbol]+="═" +VCS_PLUGIN[unmerged_symbol]+="%{$reset_color%}" + +# $VCS_PLUGIN[copied_symbol] : [string] show when there is at least one copied file +VCS_PLUGIN[copied_symbol]="%{fg_bold[blue]%}" +VCS_PLUGIN[copied_symbol]+="ⓒ" +VCS_PLUGIN[copied_symbol]+="%{$reset_color%}" + +# $VCS_PLUGIN[include_dirty_counts] : [flag] "true" to include counts preceding status symbols +VCS_PLUGIN[include_dirty_counts]=true +#VCS_PLUGIN[include_dirty_counts]= + +# $VCS_PLUGIN[git_vcs_symbol] : [string] show when the cwd is in a git work tree +VCS_PLUGIN[git_vcs_symbol]="%F{154}" +VCS_PLUGIN[git_vcs_symbol]+="±" +VCS_PLUGIN[git_vcs_symbol]+="%f" + +# $VCS_PLUGIN[hg_vcs_symbol] : [string] show when the cwd is in an hg work tree +VCS_PLUGIN[hg_vcs_symbol]="%{$fg_bold[red]%}" +VCS_PLUGIN[hg_vcs_symbol]+="☿" +VCS_PLUGIN[hg_vcs_symbol]+="%{$reset_color%} " +# +# $VCS_PLUGIN[svn_vcs_symbol] : [string] show when the cwd is in an svn work tree +VCS_PLUGIN[svn_vcs_symbol]="Ⓢ " +#VCS_PLUGIN[svn_vcs_symbol]="⚡ " + +# $VCS_PLUGIN[no_vcs_symbol] : [string] show when cwd is not associated with a vcs +VCS_PLUGIN[no_vcs_symbol]="◯ " + +# $VCS_PLUGIN[branch_prefix] +VCS_PLUGIN[branch_prefix]="%{$fg[white]%}" +# $VCS_PLUGIN[branch_suffix] +VCS_PLUGIN[branch_suffix]="%{$reset_color%}" + +# $VCS_PLUGIN[rev_prefix] +VCS_PLUGIN[rev_prefix]= +# $VCS_PLUGIN[rev_suffix] +VCS_PLUGIN[rev_suffix]= + +# $VCS_PLUGIN[ahead_by_prefix] +VCS_PLUGIN[ahead_by_prefix]= +# $VCS_PLUGIN[ahead_by_symbol] : [string] shown when there are local commits not yet pushed upstream +VCS_PLUGIN[ahead_by_symbol]="%{$fg_bold[blue]%}" +VCS_PLUGIN[ahead_by_symbol]+="⬆" +VCS_PLUGIN[ahead_by_symbol]+="%{$reset_color%}" +# $VCS_PLUGIN[ahead_by_suffix] +VCS_PLUGIN[ahead_by_suffix]= + +# $VCS_PLUGIN[behind_by_prefix] +VCS_PLUGIN[behind_by_prefix]= +# $VCS_PLUGIN[behind_by_symbol] : [string] shown when there are commits not yet merged in from upstream +VCS_PLUGIN[behind_by_symbol]="%{$fg_bold[red]%}" +VCS_PLUGIN[behind_by_symbol]+="⬇" +VCS_PLUGIN[behind_by_symbol]+="%{$reset_color%}" +#VCS_PLUGIN[behind_by_suffix +VCS_PLUGIN[behind_by_suffix]= + +# $VCS_PLUGIN[ahead_behind_prefix] : for combined ahead and behind indicators +VCS_PLUGIN[ahead_behind_prefix]= +# $VCS_PLUGIN[ahead_behind_separator] : [string] appears between ahead and behind status indicators if both are present +VCS_PLUGIN[ahead_behind_separator]=" " +# $VCS_PLUGIN[ahead_behind_suffix] +VCS_PLUGIN[ahead_behind_suffix]=$VCS_PLUGIN[separator] + +# $VCS_PLUGIN[long_age_threshold] : [number] time in minutes a work tree can be stale before its age is considered long +VCS_PLUGIN[long_age_threshold]="360" +# $VCS_PLUGIN[medium_age_threshold] : [number] time in minutes a work tree can be stale before its age is considered medium +# : anything less stale will be considered short +# : a directory is not considered stale (is neutral) if it is not dirty +VCS_PLUGIN[medium_age_threshold]="120" +# $VCS_PLUGIN[time_since_commit_neutral] : [color] used for neutral indicator +VCS_PLUGIN[time_since_commit_neutral]="%{$fg[white]%}" +# $VCS_PLUGIN[time_since_commit_short] : [color] used for short age +VCS_PLUGIN[time_since_commit_short]="%{$fg[green]%}" +# $VCS_PLUGIN[time_short_commit_medium] : [color] used for medium age +VCS_PLUGIN[time_short_commit_medium]="%{$fg[yellow]%}" +# $VCS_PLUGIN[time_since_commit_long] : [color] used for long age +VCS_PLUGIN[time_since_commit_long]="%{$fg[red]%}" +# $VCS_PLUGIN[time_verbose] : [flag] "true" to see a more verbose age indicator +VCS_PLUGIN[time_verbose]= +#VCS_PLUGIN[time_verbose]=true + +# VCS_DETECTION_ORDER : [hash] order in which cwd will be inspected for vcs type +VCS_DETECTION_ORDER=(svn git hg) + +# =====[ IMPLEMENTATION ]======================================================= + +# =====[ detection ]============================================================ +function vcs_is_git() { + # this invocation handles ignored subdirectories of git work trees + [[ -n $(git ls-files --exclude-standard 2> /dev/null) ]] +} + +function vcs_is_hg() { + hg root >/dev/null 2>/dev/null && true +} + +function vcs_is_svn() { + [[ -d .svn ]] && true +} + +# =====[ display ]============================================================== + +function vcs_status_prompt() { + for vcs in $VCS_DETECTION_ORDER; do + vcs_is_${vcs} && _vcs_prompt ${vcs} && return + done + echo "$VCS_PLUGIN[no_vcs_symbol]" +} + +function _vcs_prompt() { + local vcs=$1 + local result='' + result+="$VCS_PLUGIN[prompt_prefix]" + result+=$(_vcs_prompt_${vcs}) + result+="$VCS_PLUGIN[prompt_suffix]" + echo $result +} + +# Default status prompts; override by defining your own functions of the same +# name in your theme but still use $(vcs_status_prompt) in your PROMPT. + +function _vcs_prompt_git() { + local result='' + result+="$(vcs_dirt_age)" + result+="$VCS_PLUGIN[separator]" + result+="$(vcs_ahead_behind)" + result+="$(vcs_dirt_status) " + result+="$VCS_PLUGIN[git_vcs_symbol]" + result+="$(vcs_branch)" + echo $result +} + +function _vcs_prompt_svn() { + local result='' + result+="$(vcs_dirt_age)" + result+="$VCS_PLUGIN[separator]" + result+="$(vcs_dirt_status) " + result+="$VCS_PLUGIN[svn_vcs_symbol]" + result+="${$(vcs_branch)#trunk}" # show nothing if "trunk" + result+="$(vcs_rev_long)" + echo $result +} + +function _vcs_prompt_hg() { + local result='' + result+="$(vcs_dirt_age)" + result+="$VCS_PLUGIN[separator]" + result+="$(vcs_dirt_status) " + result+="$VCS_PLUGIN[hg_vcs_symbol]" + result+="$(vcs_branch)" + result+="$(vcs_rev_long)" + echo $result +} + +# =====[ branch information ]=================================================== + +function vcs_branch() { + local length=$1 + local value='' + for vcs in $VCS_DETECTION_ORDER; do + vcs_is_${vcs} && value+=$(_vcs_branch_${vcs}) && break + done + [[ -z $value ]] && return + local result='' + result+="$VCS_PLUGIN[branch_prefix]" + result+=$value + result+="$VCS_PLUGIN[branch_suffix]" + echo $result +} + +function _vcs_branch_git() { + echo ${$(git symbolic-ref HEAD 2> /dev/null)#refs/heads/} || return +} + +function _vcs_branch_hg() { + echo $(hg branch) || return +} + +function _vcs_branch_svn() { + # Strip out '/trunk/' + echo $(svn info | awk '/Root/{root=$3 "\/(trunk/)?"};/URL/{url=$2};END{gsub(root,"",url);print url}') +} + +# =====[ current revision ]===================================================== + +function vcs_rev_short() { _vcs_rev "short"; } + +function vcs_rev_long() { _vcs_rev "long"; } + +function _vcs_rev() { + local length=$1 + local value='' + for vcs in $VCS_DETECTION_ORDER; do + vcs_is_${vcs} && value+=$(_vcs_rev_${length}_${vcs}) && break + done + [[ -z $value ]] && return + local result='' + result+="$VCS_PLUGIN[rev_prefix]" + result+=$value + result+="$VCS_PLUGIN[rev_suffix]" + echo $result +} + +function _vcs_rev_short_git() { echo $(git rev-parse --short HEAD 2> /dev/null); } + +function _vcs_rev_short_hg() { echo $(hg log -l 1 | awk '/changeset/{split($2,revs,":")};END{print revs[1]}'); } + +function _vcs_rev_short_svn() { echo $(svn info | awk '/Revision/{print $2}'); } + +function _vcs_rev_long_git() { echo $(git rev-parse HEAD 2> /dev/null); } + +function _vcs_rev_long_hg() { echo $(hg log -l 1 | awk '/changeset/{print $2}'); } + +function _vcs_rev_long_svn() { echo "r"$(_vcs_rev_short_svn); } + +function vcs_ahead_behind() { + local ahead_by="$(vcs_ahead_by)" + local behind_by="$(vcs_behind_by)" + [[ -z "${ahead_by}${behind_by}" ]] && return + local result='' + result+="$VCS_PLUGIN[ahead_behind_prefix]" + result+=$ahead_by + [[ -n $ahead_by && -n $behind_by ]] && result+="$VCS_PLUGIN[ahead_behind_separator]" + result+=$behind_by + result+="$VCS_PLUGIN[ahead_behind_suffix]" + echo $result +} + +function vcs_ahead_by() { + local value='' + for vcs in $VCS_DETECTION_ORDER; do + vcs_is_${vcs} && value+="$(_vcs_ahead_by_${vcs})" && break + done + [[ -z $value ]] && return + local result='' + result+="$VCS_PLUGIN[ahead_by_prefix]" + result+=$value + result+="$VCS_PLUGIN[ahead_by_symbol]" + result+="$VCS_PLUGIN[ahead_by_suffix]" + echo $result +} + +function _vcs_ahead_by_git() { git status --porcelain --branch --short | awk -F'[' '/ahead/{sub(/ahead /,"",$2);sub(/[],].*/,"",$2); print $2}'; } + +function _vcs_ahead_by_svn() { return } # FIXME: unimplemented + +function _vcs_ahead_by_hg() { return } # FIXME: unimplemented + +function vcs_behind_by() { + local value='' + for vcs in $VCS_DETECTION_ORDER; do + vcs_is_${vcs} && value+="$(_vcs_behind_by_${vcs})" && break + done + [[ -z $value ]] && return + local result='' + result+="$VCS_PLUGIN[behind_by_prefix]" + result+=$value + result+="$VCS_PLUGIN[behind_by_symbol]" + result+="$VCS_PLUGIN[behind_by_suffix]" + echo $result +} + +function _vcs_behind_by_git() { git status --porcelain --branch --short | awk -F'[' '/behind/{sub(/.*behind /,"",$2);sub(/]/,"",$2)};END{print $2}'; } + +function _vcs_behind_by_svn() { return } # FIXME: unimplemented + +function _vcs_behind_by_hg() { return } # FIXME: unimplemented + +# =====[ dirt status ]========================================================== +# "simple": Show a clean or dirty status indicator. +# "full": Show the count of files for each type of non-clean status type next to an +# indicator of that type. e.g `6✭ 1✚ 4✹` for 6 untracked, 1 new, and 4 modified +# files. + +function vcs_dirt_status() { + for vcs in $VCS_DETECTION_ORDER; do + vcs_is_${vcs} && _vcs_dirt_status_${VCS_PLUGIN[dirt_status_verbosity]} ${vcs} && return + done +} + +function _vcs_dirt_status_simple() { + local vcs=$1 + if _vcs_is_clean_${vcs}; then + echo "$VCS_PLUGIN[is_clean_symbol]" + else + echo "$VCS_PLUGIN[is_dirty_symbol]" + fi +} + +function _vcs_is_clean_git() { + local untracked='' + [[ -z $VCS_PLUGIN[untracked_is_dirty] ]] && untracked+="--untracked=no" + [[ -z $(git status --short --porcelain $untracked 2> /dev/null) ]] && true +} + +function _vcs_is_clean_svn() { + local untracked='' + [[ -z $VCS_PLUGIN[untracked_is_dirty] ]] && untracked+="-q" + [[ -z $(svn status $untracked 2> /dev/null) ]] && true +} + + +function _vcs_is_clean_hg() { + local untracked='' + [[ -z $VCS_PLUGIN[untracked_is_dirty] ]] && untracked+="-q" + [[ -z $(hg status $untracked 2> /dev/null) ]] && true +} + +function _vcs_dirt_status_full() { + local vcs=$1 + echo $(_vcs_dirt_status_${vcs}) +} + +function _vcs_dirt_status_git() { + git status --porcelain 2> /dev/null | awk " + + /^\?\? / { untracked++ ; total++ }; + /^[AM] / { added++ ; total++ }; + /^[A ][MT] / { modified++ ; total++ }; + /^R / { renamed++ ; total++ }; + /^[A ]D / { deleted++ ; total++ }; + /^UU / { unmerged++ ; total++ }; + /^C / { copied++ ; total++ }; + + END { + if (total+0 == 0) { printf(\"%s\", \"$VCS_PLUGIN[is_clean_symbol]\"); exit; } + + count[\"untracked\"] = untracked+0 ; symbol[\"untracked\"] = \"$VCS_PLUGIN[untracked_symbol]\"; + count[\"added\"] = added+0 ; symbol[\"added\"] = \"$VCS_PLUGIN[added_symbol]\" ; + count[\"modified\"] = modified+0 ; symbol[\"modified\"] = \"$VCS_PLUGIN[modified_symbol]\" ; + count[\"renamed\"] = renamed+0 ; symbol[\"renamed\"] = \"$VCS_PLUGIN[renamed_symbol]\" ; + count[\"deleted\"] = deleted+0 ; symbol[\"deleted\"] = \"$VCS_PLUGIN[deleted_symbol]\" ; + count[\"unmerged\"] = unmerged+0 ; symbol[\"unmerged\"] = \"$VCS_PLUGIN[unmerged_symbol]\" ; + count[\"copied\"] = copied+0 ; symbol[\"copied\"] = \"$VCS_PLUGIN[copied_symbol]\" ; + + for (i in count) { + if (count[i]+0 != 0) { + if (\"${VCS_PLUGIN[include_dirty_counts]}\" == \"\") { + printf(\"%s\", symbol[i]); + } else { + printf(\"%s%s \", count[i], symbol[i]); + } + } + } + } + " | sed -e 's/ $//' & +} + +function _vcs_dirt_status_hg() { + hg status 2> /dev/null | awk " + + /^\? / { untracked++ ; total++ }; + /^A / { added++ ; total++ }; + /^M / { modified++ ; total++ }; + /^! / { renamed++ ; total++ }; + /^R / { deleted++ ; total++ }; + /^_ / { unmerged++ ; total++ }; + /^_ / { copied++ ; total++ }; + + END { + if (total+0 == 0) { printf(\"%s\", \"$VCS_PLUGIN[is_clean_symbol]\"); exit; } + + count[\"untracked\"] = untracked+0 ; symbol[\"untracked\"] = \"$VCS_PLUGIN[untracked_symbol]\"; + count[\"added\"] = added+0 ; symbol[\"added\"] = \"$VCS_PLUGIN[added_symbol]\" ; + count[\"modified\"] = modified+0 ; symbol[\"modified\"] = \"$VCS_PLUGIN[modified_symbol]\" ; + count[\"renamed\"] = renamed+0 ; symbol[\"renamed\"] = \"$VCS_PLUGIN[renamed_symbol]\" ; + count[\"deleted\"] = deleted+0 ; symbol[\"deleted\"] = \"$VCS_PLUGIN[deleted_symbol]\" ; + count[\"unmerged\"] = unmerged+0 ; symbol[\"unmerged\"] = \"$VCS_PLUGIN[unmerged_symbol]\" ; + count[\"copied\"] = copied+0 ; symbol[\"copied\"] = \"$VCS_PLUGIN[copied_symbol]\" ; + + for (i in count) { + if (count[i]+0 != 0) { + if (\"$VCS_PLUGIN[include_dirty_counts]\" == \"\") { + printf(\"%s\", symbol[i]); + } else { + printf(\"%s%s \", count[i], symbol[i]); + } + } + } + } + " | sed -e 's/ $//' & +} + +function _vcs_dirt_status_svn() { + svn status 2> /dev/null | awk " + + /^\? / { untracked++ ; total++ }; + /^A / { added++ ; total++ }; + /^M / { modified++ ; total++ }; + /^! / { renamed++ ; total++ }; + /^D / { deleted++ ; total++ }; + /^C / { unmerged++ ; total++ }; + /^R / { copied++ ; total++ }; + + END { + if (total+0 == 0) { printf(\"%s\", \"$VCS_PLUGIN[is_clean_symbol]\"); exit; } + + count[\"untracked\"] = untracked+0 ; symbol[\"untracked\"] = \"$VCS_PLUGIN[untracked_symbol]\"; + count[\"added\"] = added+0 ; symbol[\"added\"] = \"$VCS_PLUGIN[added_symbol]\" ; + count[\"modified\"] = modified+0 ; symbol[\"modified\"] = \"$VCS_PLUGIN[modified_symbol]\" ; + count[\"renamed\"] = renamed+0 ; symbol[\"renamed\"] = \"$VCS_PLUGIN[renamed_symbol]\" ; + count[\"deleted\"] = deleted+0 ; symbol[\"deleted\"] = \"$VCS_PLUGIN[deleted_symbol]\" ; + count[\"unmerged\"] = unmerged+0 ; symbol[\"unmerged\"] = \"$VCS_PLUGIN[unmerged_symbol]\" ; + count[\"copied\"] = copied+0 ; symbol[\"copied\"] = \"$VCS_PLUGIN[copied_symbol]\" ; + + for (i in count) { + if (count[i]+0 != 0) { + if (\"${VCS_PLUGIN[include_dirty_counts]}\" == \"\") { + printf(\"%s\", symbol[i]); + } else { + printf(\"%s%s \", count[i], symbol[i]); + } + } + } + } + " | sed -e 's/ $//' & +} + +# =====[ dirt age ]============================================================= +# Determine the time since last commit. If branch is clean, +# use a neutral color, otherwise colors will vary according to time. + +function vcs_dirt_age() { + for vcs in $VCS_DETECTION_ORDER; do + vcs_is_${vcs} && _vcs_dirt_age ${vcs} && return + done +} + +function _vcs_dirt_age() { + local vcs=$1 + echo $(_vcs_dirt_age_${vcs}) +} + +function _vcs_dirt_age_hg() { + local changed_date="$(hg log -l 1 --template '{date|hgdate}' | awk '{print $1 + $2}')" + _vcs_is_clean_hg && local is_dirty=false + echo $(_vcs_dirt_age_calculate ${changed_date} ${is_dirty:-true}) +} + +function _vcs_dirt_age_svn() { + local changed_date="$(svn info | awk -F'e: ' '/Changed Date:/{gsub(" \\(.*", "", $2); print $2}')" + _vcs_is_clean_svn && local is_dirty=false + echo $(_vcs_dirt_age_calculate $(date -j -f "%Y-%m-%d %T %z" "${changed_date}" "+%s") ${is_dirty:-true}) +} + +function _vcs_dirt_age_git() { + if git rev-parse --git-dir > /dev/null 2>&1; then + # Only proceed if there is actually a commit. + if [[ $(git log 2>&1 > /dev/null | grep -c "^fatal: bad default revision") == 0 ]]; then + # Get the last commit. + local last_commit=$(git log --pretty=format:'%at' -1 2> /dev/null) + _vcs_is_clean_git && local is_dirty=false + echo $(_vcs_dirt_age_calculate $last_commit ${is_dirty:-true}) + fi + fi +} + +function _vcs_dirt_age_calculate() { + local last_commit=$1 + local is_dirty=$2 + local result='' + local now=$(date +%s) + local seconds_since_last_commit=$((now-last_commit)) + + # totals + local minutes=$((seconds_since_last_commit / 60)) + local hours=$((seconds_since_last_commit/3600)) + local years=$((seconds_since_last_commit / (86400 * 365) )) + + # sub-hours and sub-minutes + local days=$((seconds_since_last_commit / 86400)) + local sub_hours=$((hours % 24)) + local sub_minutes=$((minutes % 60)) + + if $is_dirty; then + if [ "$minutes" -gt "$VCS_PLUGIN[long_age_threshold]" ]; then + color="$VCS_PLUGIN[time_since_commit_long]" + elif [ "$minutes" -gt "$VCS_PLUGIN[long_age_threshold]" ]; then + color="$VCS_PLUGIN[time_short_commit_medium]" + else + color="$VCS_PLUGIN[time_since_commit_short]" + fi + else + color="$VCS_PLUGIN[time_since_commit_neutral]" + fi + + if [ "$days" -gt 365 ]; then + result=${years}y + else + if [ "$hours" -gt 24 ]; then + if [[ -n $VCS_PLUGIN[time_verbose] ]]; then + result="${days}d${sub_hours}h${sub_minutes}m" + else + result="${days}d" + fi + elif [ "$minutes" -gt 60 ]; then + if [[ -n $VCS_PLUGIN[time_verbose] ]]; then + result="${hours}h${sub_minutes}m" + else + result="${hours}h" + fi + else + result="${minutes}m" + fi + fi + echo "${color}${result}%{$reset_color%}" + }