Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@jart
Last active November 29, 2023 00:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jart/9e3b4c60337f03851b58a1b2db8504e1 to your computer and use it in GitHub Desktop.
Save jart/9e3b4c60337f03851b58a1b2db8504e1 to your computer and use it in GitHub Desktop.
PS1 with low latency git branch detection
#!/bin/bash
# jartps1.sh - Enhanced bash prompt
# Author: Justine Tunney <jart@google.com>
# Modified: 2023-11-28
#
# LIST OF BASH PROMPT ENHANCEMENTS
#
# - No latency issues when holding down enter key
# - Doesn't look unfamiliar compared to bash defaults
# - Show current git branch
# - Show background job count if \j > 0
# - Show process exit code if $? != 0
#
# TRY IT OUT
#
# JARTPS1_THEME=dark source .jartps1.sh
#
# CUSTOMIZING
#
# Copy/paste the `PS1="..."` line from this file and add it to your bashrc
# after the line where you source this file and refer to the `man bash`
# section titled "PROMPTING".
#
# If you're using a black background terminal then you might want to
# customize the color and make things look more fabulous. You just have to
# figure out the ANSI escape codes for the colors you want. Don't remove the
# 0's because they're used to reset the current formatting. Also don't remove
# the \[...\] escapes or else bash will print artifacts when you do things
# like reverse search. A library called Fabulous will help you generate the
# escape codes:
#
# $ sudo apt-get install python-pip
# $ sudo pip install fabulous
# $ python
# >>> from fabulous import color
# >>> color.red('hello')
# u'\x1b[31mhello\x1b[39m'
# >>> color.fg256('#ff6699', 'hello')
# u'\x1b[38;5;204mhello\x1b[39m'
# >>> color.bold(color.flip(color.fg256('indianred', 'hello')))
# u'\x1b[1;7;38;5;167mhello\x1b[39;27;22m'
#
# See also: https://github.com/jart/fabulous/blob/master/fabulous/color.py
#
# NOTES
#
# This script will leave a tiny temp file behind for each terminal you
# open. This isn't a problem because `/etc/cron.daily/tmpreaper` will delete
# them after seven days or so.
#
# CHANGELOG
#
# 2023-11-28 Justine Tunney <jart@google.com>
#
# * Support shells other than bash
# * Support /Users convention on MacOS
#
# 2014-06-20 Justine Tunney <jart@google.com>
#
# * Conform to go/bashstyle
#
# 2013-12-10 Justine Tunney <jart@google.com>
#
# * Fixed a bug which caused bash to display a warning each time the prompt
# is displayed if a certain /tmp file got deleted by a weekly cleanup
# cron job.
#
# * Fixed ps1_error() and ps1_jobs() now that I understand bash arithmetic
# expressions better.
#
ps1_error() {
local e=$?
if [ $e -ne 0 ]; then
case $e in
129) printf "\$?=SIGHUP " ;;
130) printf "\$?=SIGINT " ;;
131) printf "\$?=SIGQUIT " ;;
132) printf "\$?=SIGILL " ;;
133) printf "\$?=SIGTRAP " ;;
134) printf "\$?=SIGABRT " ;;
135) printf "\$?=SIGBUS " ;;
136) printf "\$?=SIGFPE " ;;
137) printf "\$?=SIGKILL " ;;
138) printf "\$?=SIGUSR1 " ;;
139) printf "\$?=SIGSEGV " ;;
140) printf "\$?=SIGUSR2 " ;;
141) printf "\$?=SIGPIPE " ;;
142) printf "\$?=SIGALRM " ;;
143) printf "\$?=SIGTERM " ;;
145) printf "\$?=SIGCHLD " ;;
146) printf "\$?=SIGCONT " ;;
147) printf "\$?=SIGSTOP " ;;
148) printf "\$?=SIGTSTP " ;;
149) printf "\$?=SIGTTIN " ;;
150) printf "\$?=SIGTTOU " ;;
151) printf "\$?=SIGURG " ;;
152) printf "\$?=SIGXCPU " ;;
153) printf "\$?=SIGXFSZ " ;;
154) printf "\$?=SIGVTALRM " ;;
155) printf "\$?=SIGPROF " ;;
156) printf "\$?=SIGWINCH " ;;
157) printf "\$?=SIGIO " ;;
159) printf "\$?=SIGSYS " ;;
*) printf "\$?=%d " $e ;;
esac
fi
}
ps1_jobs() {
local count=$1
if [ $count -gt 0 ]; then
printf "jobs=%d " ${count}
fi
}
ps1_git() {
if jartps1_gitbranch_find; then
printf "%s " "${jartps1_gitbranch}"
fi
}
ps1_dir() {
local path="$PWD"
case "${path}" in
${HOME})
path="~"
;;
${HOME}/*)
path="~/${path#$HOME/}"
;;
esac
printf "%s" "${path}"
}
if [ -n "${BASH_VERSION}" ]; then
case ${JARTPS1_THEME:-default} in
default)
PS1="\[\e[1;31m\]\$(ps1_error)\[\e[34m\]\$(ps1_jobs \j)\[\e[32m\]\$(ps1_git)\[\e[0;95m\]\u@\h\[\e[m\]:\$(ps1_dir)\$ "
PS2='> '
PS4='+ '
;;
dark)
PS1="\[\e[1;31m\]\$(ps1_error)\[\e[34m\]\$(ps1_jobs \j)\[\e[32m\]\$(ps1_git)\[\e[0;95m\]\u@\h\[\e[m\]:\[\e[0;96m\]\$(ps1_dir)\[\e[0m\]\$ "
PS2='> '
PS4='+ '
;;
*)
printf "Invalid \$JARTPS1_THEME: %s\n" "${JARTPS1_THEME}" >&2
;;
esac
else
PS1="\$(ps1_error)\$(ps1_git)\$USER@$(hostname -s):\$(ps1_dir)\$ "
PS2='> '
PS4='+ '
fi
# Caches last directory for which `jartps1_gitdir_find` failed.
export JARTPS1_GITDIR_TMP="$(mktemp /tmp/jartps1.XXXXXX)"
################################################################################
# Swiftly determine name of current Git branch.
#
# Arguments:
# None
# Returns:
# 0 on success, storing result to ${jartps1_gitbranch}.
# 1 if ${PWD} isn't inside a Git repo.
# Globals:
# jartps1_gitbranch - Used to store result.
################################################################################
jartps1_gitbranch_find() {
if jartps1_gitdir_find; then
local head
if read head <"${jartps1_gitdir}/.git/HEAD" >/dev/null 2>&1; then
jartps1_gitbranch="${head##*/}"
if [ x"${jartps1_gitbranch}" != x"" ]; then
return 0
fi
fi
fi
return 1
}
################################################################################
# Swiftly locate root of current Git repository.
#
# This is the fastest possible solution. Rather than the naïve approach of
# executing the git command (which could take hundreds of milliseconds), this
# routine launches no processes and only requires a few stat() system calls. In
# some cases it can avoid lookup or cache the result.
#
# Arguments:
# dir - Optional, defaults to ${PWD}
# Returns:
# 0 on success, storing result to ${jartps1_gitdir}.
# 1 if dir isn't inside a Git repo.
# Globals:
# jartps1_gitdir - Used to store result.
# JARTPS1_GITDIR_TMP - Temp file used to cache last failed search.
################################################################################
jartps1_gitdir_find() {
local dir="$1"
if [ -z "${dir}" ]; then
dir="${PWD}"
case "${dir}" in
/) return 1 ;;
${HOME}) return 1 ;;
/google/*) return 1 ;;
/home/build/*) return 1 ;;
/usr/local/google${HOME}) return 1 ;;
esac
if [ -f "${JARTPS1_GITDIR_TMP}" ]; then
local notgit
if read notgit <"${JARTPS1_GITDIR_TMP}" >/dev/null 2>&1; then
if [ "${dir}" == "${notgit}" ]; then
return 1
fi
fi
fi
fi
if [ -d "${dir}/.git" ]; then
jartps1_gitdir="${dir}"
else
local parent="${dir%/*}"
if [ -z "${parent}" ]; then
printf "%s" "$PWD" >"${JARTPS1_GITDIR_TMP}"
return 1
fi
jartps1_gitdir_find "${parent}"
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment