Bash Prompt with Git Repository and Working Tree Status Information

Here are three confessions:

  1. I am a total command-line geek: I spend most of my life in the (bash) shell.
  2. I am a total Git convert: it is to Subversion as cell phones are to landlines.
  3. I love a good prompt.

So, those three confessions find mutual experession in the following extract from my ".bashrc" file. By default, shows the basics: user name, hostname and working directory. I tend to have deeply-nested directory structures, so the full path to the directory gets really cumbersome, so, by default it only shows the current directory basename instead of the full path. I hope back and forth between lots of hosts, and, usually have several, if not dozens, of terminal windows open at once. This can be quite dangerous if I do not make sure I'm actually on the host that I think I am before issuing a command, especially those "sudo" ones. So I've got the prompt set that if I'm not on my own computer, the hostname is underlined. I used to have it set that the entire prompt was highlighted a different color depending on the hostname, but that just got rather complicated. The real magic is the display of the current Git branch if you are in a Git repository, as well as the SHA1 of the current HEAD. Extremely useful is the flags indicating whether there are files staged for commiting in the index ("+I"), tracked files with modifications/changes not staged for committing ("+M"), and/or untracked files in the working tree ("+U"). Until you have this, you have no idea how great it is being able to take in the status of your working tree at a glance (forgotten to commit some edits? forgotten to start tracking files?), without the need to run "git status" to get this information.

For example:

[bin:master:b0c8637:+M+U]$

To get this prompt, add the following to your "~/.bashrc" file:

###############################################################################
# IDENTIFICATION OF LOCAL HOST: CHANGE TO YOUR COMPUTER NAME
###############################################################################

PRIMARYHOST="localhost"

###############################################################################
# PROMPT
###############################################################################

###############################################################################
# Terminal Title

set_terminal_title() {
    if [[ -z $@ ]]
    then
        TERMINAL_TITLE=$PWD
    else
        TERMINAL_TITLE=$@
    fi
}
alias stt='set_terminal_title'
STANDARD_PROMPT_COMMAND='history -a ; echo -ne "\033]0;${TERMINAL_TITLE}\007"'
PROMPT_COMMAND=$STANDARD_PROMPT_COMMAND

###############################################################################
# Parses Git info for prompt

function _set_git_envar_info {
    GIT_BRANCH=""
    GIT_HEAD=""
    GIT_STATE=""
    GIT_LEADER=""
    GIT_ROOT=""
    if [[ $(which git 2> /dev/null) ]]
    then
        local STATUS
        STATUS=$(git status 2>/dev/null)
        if [[ -z $STATUS ]]
        then
            return
        fi
        GIT_LEADER=":"
        GIT_BRANCH="$(git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')"
        GIT_HEAD=":$(git show --quiet --pretty=format:%h 2> /dev/null)"
        GIT_ROOT=./$(git rev-parse --show-cdup)
        if [[ "$STATUS" == *'working directory clean'* ]]
        then
            GIT_STATE=""
        else
            GIT_HEAD=$GIT_HEAD":"
            GIT_STATE=""
            if [[ "$STATUS" == *'Changes to be committed:'* ]]
            then
                GIT_STATE=$GIT_STATE'+I' # Index has files staged for commit
            fi
            if [[ "$STATUS" == *'Changed but not updated:'* ]]
            then
                GIT_STATE=$GIT_STATE"+M" # Working tree has files modified but unstaged
            fi
            if [[ "$STATUS" == *'Untracked files:'* ]]
            then
                GIT_STATE=$GIT_STATE'+U' # Working tree has untracked files
            fi
            GIT_STATE=$GIT_STATE''
        fi
    fi
}

###############################################################################
# Composes prompt.
function setps1 {

    # Help message.
    local USAGE="Usage: setps1 [none] [screen=<0|1>] [user=<0|1>] [dir=<0|1|2>] [git=<0|1>] [wrap=<0|1>] [which-python=<0|1>]"

    if [[ (-z $@) || ($@ == "*-h*") || ($@ == "*--h*") ]]
    then
        echo $USAGE
        return
    fi

    # Prompt colors.
    local CLEAR="\[\033[0m\]"
    local STY_COLOR='\[\033[1;37;41m\]'
    local PROMPT_COLOR='\[\033[1;94m\]'
    local USER_HOST_COLOR='\[\033[1;30m\]'
    local PROMPT_DIR_COLOR='\[\033[1;94m\]'
    local GIT_LEADER_COLOR='\[\033[1;30m\]'
    local GIT_BRANCH_COLOR=$CLEAR'\[\033[1;90m\]\[\033[4;90m\]'
    local GIT_HEAD_COLOR=$CLEAR'\[\033[1;32m\]'
    local GIT_STATE_COLOR=$CLEAR'\[\033[1;31m\]'

    # Hostname-based colors in prompt.
    if [[ $HOSTNAME != $PRIMARYHOST ]]
    then
        USER_HOST_COLOR=$REMOTE_USER_HOST_COLOR
    fi

    # Start with empty prompt.
    local PROMPTSTR=""

    # Set screen session id.
    if [[ $@ == *screen=1* ]]
    then
        ## Decorate prompt with indication of screen session ##
        if [[ -z "$STY" ]] # if screen session variable is not defined
        then
            local SCRTAG=""
        else
            local SCRTAG="$STY_COLOR(STY ${STY%%.*})$CLEAR" # get screen session number
        fi
    fi

    # Set user@host.
    if [[ $@ == *user=1* ]]
    then
         PROMPTSTR=$PROMPTSTR"$USER_HOST_COLOR\\u@\\h$CLEAR"
    fi

    # Set directory.
    if [[ -n $PROMPTSTR && ($@ == *dir=1* || $@ == *dir=2*) ]]
    then
            PROMPTSTR=$PROMPTSTR"$PROMPT_COLOR:"
    fi

    if [[ $@ == *dir=1* ]]
    then
        PROMPTSTR=$PROMPTSTR"$PROMPT_DIR_COLOR\W$CLEAR"
    elif [[ $@ == *dir=2* ]]
    then
        PROMPTSTR=$PROMPTSTR"$PROMPT_DIR_COLOR\$(pwd -P)$CLEAR"
    fi

#     if [[ $@ == *dir=1* ]]
#     then
#         PROMPTSTR=$PROMPTSTR"$PROMPT_DIR_COLOR\W$CLEAR"
#     elif [[ $@ == *dir=2* ]]
#     then
#         PROMPTSTR=$PROMPTSTR"$PROMPT_DIR_COLOR\w$CLEAR"
#     fi
#
    # Set git.
    if [[ $@ == *git=1* ]]
    then
        PROMPT_COMMAND="$STANDARD_PROMPT_COMMAND && _set_git_envar_info"
        PROMPTSTR=$PROMPTSTR"$BG_COLOR$GIT_LEADER_COLOR\$GIT_LEADER$GIT_BRANCH_COLOR"
        PROMPTSTR=$PROMPTSTR"\$GIT_BRANCH$GIT_HEAD_COLOR\$GIT_HEAD$GIT_STATE_COLOR\$GIT_STATE$CLEAR"
    else
        PROMPT_COMMAND=$STANDARD_PROMPT_COMMAND
    fi

    # Set wrap.
    if [[ $@ == *wrap=1* ]]
    then
        local WRAP="$CLEAR\n"
    else
        local WRAP=""
    fi

    # Set wrap.
    if [[ $@ == *which-python=1* ]]
    then
        local WHICHPYTHON="$CLEAR\n(python is '\$(which python)')$CLEAR\n"
    else
        local WHICHPYTHON=""
    fi

    # Finalize.
    if [[ -z $PROMPTSTR || $@ == none ]]
    then
        PROMPTSTR="\$ "
    else
        PROMPTSTR="$TITLEBAR\n$SCRTAG${PROMPT_COLOR}[$CLEAR$PROMPTSTR$PROMPT_COLOR]$WRAP$WHICHPYTHON$PROMPT_COLOR\$$CLEAR "
    fi

    # Set.
    PS1=$PROMPTSTR
    PS2='> '
    PS4='+ '
}

alias setps1-long='setps1 screen=1 user=1 dir=2 git=1 wrap=1'
alias setps1-short='setps1 screen=1 user=1 dir=1 git=1 wrap=0'
alias setps1-default='setps1-short'
alias setps1-plain='setps1 screen=0 user=0 dir=0 git=0 wrap=0'
alias setps1-nogit='setps1 screen=0 user=1 dir=1 git=0 wrap=0'
alias setps1-local-long='setps1 screen=1 user=0 dir=2 git=1 wrap=1'
alias setps1-local-short='setps1 screen=0 user=0 dir=1 git=1 wrap=0'
alias setps1-local='setps1-local-short'
alias setps1-dev-short='setps1 screen=0 user=0 dir=1 git=1 wrap=0 which-python=1'
alias setps1-dev-long='setps1 screen=0 user=1 dir=2 git=1 wrap=0 which-python=1'
alias setps1-dev-remote='setps1 screen=0 user=1 dir=1 git=1 wrap=0 which-python=1'
if [[ "$HOSTNAME" = "$PRIMARYHOST" ]]
then
    setps1 screen=0 user=0 dir=1 git=1 wrap=0 which-python=0
else
    setps1 screen=1 user=1 dir=1 git=1 wrap=0 which-python=0
fi