# Frontend to controlling prompt
prompt() {
# What's done next depends on the first argument to the function
case $1 in
# Turn complex, colored PS1 and debugging PS4 prompts on
on)
# Declare the PROMPT_RETURN variable as an integer
declare -i PROMPT_RETURN
# Set up pre-prompt command
PROMPT_COMMAND='PROMPT_RETURN=$? ; history -a'
# If Bash 4.0 is available, trim very long paths in prompt
((BASH_VERSINFO[0] >= 4)) && PROMPT_DIRTRIM=4
# Basic prompt shape depends on whether we're in SSH or not
PS1=
if [[ -n $SSH_CLIENT ]] || [[ -n $SSH_CONNECTION ]] ; then
PS1=$PS1'\u@\h:'
fi
PS1=$PS1'\w'
# Add sub-commands; VCS, job, and return status checks
PS1=$PS1'$(prompt vcs)$(prompt job)$(prompt ret)'
# Add prefix and suffix
PS1='${PROMPT_PREFIX}'$PS1'${PROMPT_SUFFIX}'
# Add terminating "$" or "#" sign
PS1=$PS1'\$'
# Count available colors
local -i colors
colors=$( {
tput colors || tput Co
} 2>/dev/null )
# Prepare reset code
local reset
reset=$( {
tput sgr0 || tput me
} 2>/dev/null )
# Decide prompt color formatting based on color availability
local format
case $colors in
# Check if we have non-bold bright green available
256)
format=$( {
: "${PROMPT_COLOR:=10}"
tput setaf "$PROMPT_COLOR" ||
tput setaf "$PROMPT_COLOR" 0 0 ||
tput AF "$PROMPT_COLOR" ||
tput AF "$PROMPT_COLOR" 0 0
} 2>/dev/null )
;;
# If we have only eight colors, use bold green
8)
format=$( {
: "${PROMPT_COLOR:=2}"
tput setaf "$PROMPT_COLOR" ||
tput AF "$PROMPT_COLOR"
tput bold || tput md
} 2>/dev/null )
;;
# For all other terminals, we assume non-color (!), and we just
# use bold
*)
format=$( {
tput bold || tput md
} 2>/dev/null )
;;
esac
# String it all together
PS1='\['"$format"'\]'"$PS1"'\['"$reset"'\] '
PS2='> '
PS3='? '
PS4='+<$?> ${BASH_SOURCE:-$BASH}:$FUNCNAME:$LINENO:'
;;
# Revert to simple inexpensive prompts
off)
unset -v PROMPT_COMMAND PROMPT_DIRTRIM PROMPT_RETURN
PS1='\$ '
PS2='> '
PS3='? '
PS4='+ '
;;
# Git prompt function
git)
# Bail if we're not in a work tree--or, implicitly, if we don't
# have git(1).
[[ $(git rev-parse --is-inside-work-tree 2>/dev/null) = true ]] ||
return
# Attempt to determine git branch, bail if we can't
local branch
branch=$( {
git symbolic-ref --quiet HEAD ||
git rev-parse --short HEAD
} 2>/dev/null )
[[ -n $branch ]] || return
branch=${branch##*/}
# Refresh index so e.g. git-diff-files(1) is accurate
git update-index --refresh >/dev/null
# Collect symbols representing repository state
local state
# Upstream HEAD has commits after local HEAD; we're "behind"
local -i behind
behind=$(git rev-list --count 'HEAD..@{u}' 2>/dev/null)
((behind)) && state=${state}'<'
# Local HEAD has commits after upstream HEAD; we're "ahead"
local -i ahead
ahead=$(git rev-list --count '@{u}..HEAD' 2>/dev/null)
((ahead)) && state=${state}'>'
# Tracked files are modified
git diff-files --quiet ||
state=${state}'!'
# Changes are staged
git diff-index --cached --quiet HEAD 2>/dev/null ||
state=${state}'+'
# There are some untracked and unignored files
[[ -n $(git ls-files --others --exclude-standard) ]] &&
state=${state}'?'
# There are stashed changes
git rev-parse --quiet --verify refs/stash >/dev/null &&
state=${state}'^'
# Print the status in brackets; add a git: prefix only if there
# might be another VCS prompt (because PROMPT_VCS is set)
printf '(%s%s%s)' \
"${PROMPT_VCS:+git:}" "${branch:-unknown}" "$state"
;;
# Subversion prompt function
svn)
# Determine the repository URL and root directory
local key value url root
while IFS=: read -r key value ; do
case $key in
'URL')
url=${value## }
;;
'Repository Root')
root=${value## }
;;
esac
done < <(svn info 2>/dev/null)
# Exit if we couldn't get either--or, implicitly, if we don't have
# svn(1).
[[ -n $url ]] || return
[[ -n $root ]] || return
# Remove the root from the URL to get what's hopefully the branch
# name, removing leading slashes and the 'branches' prefix, and any
# trailing content after a slash
local branch
branch=${url/"$root"}
branch=${branch#/}
branch=${branch#branches/}
branch=${branch%%/*}
# Parse the output of svn status to determine working copy state
local symbol
local -i modified untracked
while read -r symbol _ ; do
if [[ $symbol == *'?'* ]] ; then
untracked=1
else
modified=1
fi
done < <(svn status 2>/dev/null)
# Add appropriate state flags
local -a state
((modified)) && state[${#state[@]}]='!'
((untracked)) && state[${#state[@]}]='?'
# Print the state in brackets with an svn: prefix
(IFS= ; printf '(svn:%s%s)' \
"${branch:-unknown}" "${state[*]}")
;;
# VCS wrapper prompt function; print the first relevant prompt, if any
vcs)
local vcs
for vcs in "${PROMPT_VCS[@]:-git}" ; do
prompt "$vcs" && return
done
;;
# Show return status of previous command in angle brackets, if not zero
ret)
((PROMPT_RETURN)) && printf '<%u>' "$PROMPT_RETURN"
;;
# Show the count of background jobs in curly brackets, if not zero
job)
local -i jobc
while read -r ; do
((jobc++))
done < <(jobs -p)
((jobc)) && printf '{%u}' "$jobc"
;;
# No argument given, print prompt strings and vars
'')
declare -p PS1 PS2 PS3 PS4
;;
# Print error
*)
printf '%s: Unknown command %s\n' "$FUNCNAME" "$1" >&2
return 2
;;
esac
}
# Start with full-fledged prompt
prompt on