You've already forked bash-git-prompt
Fix Bash Git prompt cursor handling and stabilize rendering
This commit is contained in:
245
bash-git-prompt
245
bash-git-prompt
@@ -8,6 +8,13 @@
|
||||
#
|
||||
GIT_PROMPT_THEME="${GIT_PROMPT_THEME:-1}"
|
||||
|
||||
#
|
||||
# ANSI-safe base colors (cursor safe)
|
||||
#
|
||||
C_USER='\[\e[0;32m\]'
|
||||
C_PATH='\[\e[38;5;178m\]'
|
||||
C_RESET='\[\e[0m\]'
|
||||
|
||||
#
|
||||
# Theme icon definitions
|
||||
#
|
||||
@@ -67,186 +74,143 @@ set_git_prompt_theme_icons() {
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#
|
||||
# Theme-aware git colors (ANSI-safe)
|
||||
#
|
||||
set_git_prompt_theme_colors() {
|
||||
case "$GIT_PROMPT_THEME" in
|
||||
4|5)
|
||||
GC_BRANCH='\[\e[0;37m\]'
|
||||
GC_STAGED='\[\e[0;31m\]'
|
||||
GC_CONFLICT='\[\e[0;31m\]'
|
||||
GC_CHANGED='\[\e[0;33m\]'
|
||||
GC_UNTRACKED='\[\e[0;32m\]'
|
||||
GC_STASHED='\[\e[0;36m\]'
|
||||
GC_AHEAD='\[\e[0;37m\]'
|
||||
GC_BEHIND='\[\e[0;37m\]'
|
||||
GC_NOREMOTE='\[\e[0;37m\]'
|
||||
GC_CLEAN='\[\e[0;32m\]'
|
||||
GC_DIRTY='\[\e[0;31m\]'
|
||||
;;
|
||||
*)
|
||||
GC_BRANCH='\[\e[38;5;117m\]'
|
||||
GC_STAGED='\[\e[38;5;196m\]'
|
||||
GC_CONFLICT='\[\e[38;5;196m\]'
|
||||
GC_CHANGED='\[\e[38;5;69m\]'
|
||||
GC_UNTRACKED='\[\e[38;5;41m\]'
|
||||
GC_STASHED='\[\e[38;5;226m\]'
|
||||
GC_AHEAD='\[\e[0;37m\]'
|
||||
GC_BEHIND='\[\e[0;37m\]'
|
||||
GC_NOREMOTE='\[\e[38;5;250m\]'
|
||||
GC_CLEAN='\[\e[0;32m\]'
|
||||
GC_DIRTY='\[\e[38;5;196m\]'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
set_git_prompt_theme_icons
|
||||
set_git_prompt_theme_colors
|
||||
|
||||
#
|
||||
# Caching configuration and setup
|
||||
# Cache (per shell tree, auto-cleared on reboot)
|
||||
#
|
||||
GIT_CACHE_FILE="/tmp/git_prompt_cache.$$"
|
||||
|
||||
# Collected state variables
|
||||
GIT_STATE_HEAD=""
|
||||
GIT_STATE_STATUS=""
|
||||
GIT_STATE_STASH_LIST=""
|
||||
GIT_STATE_UNTRACKED=""
|
||||
GIT_STATE_FINGERPRINT=""
|
||||
|
||||
# Directories to skip for speed
|
||||
HEAVY_DIRS=(
|
||||
node_modules
|
||||
vendor
|
||||
dist
|
||||
build
|
||||
cache
|
||||
uploads
|
||||
public/uploads
|
||||
tmp
|
||||
.terraform
|
||||
.cache
|
||||
)
|
||||
GIT_CACHE_FILE="/dev/shm/git_prompt_${USER}_${PPID}.cache"
|
||||
|
||||
#
|
||||
# Curent state collection
|
||||
# State collection
|
||||
#
|
||||
git_collect_state() {
|
||||
|
||||
# HEAD
|
||||
GIT_STATE_HEAD=$(git rev-parse HEAD 2>/dev/null) || return 1
|
||||
|
||||
# Full status (branch + staged/modified)
|
||||
GIT_STATE_STATUS=$(GIT_OPTIONAL_LOCKS=0 git status --porcelain --branch 2>/dev/null) || return 1
|
||||
# Explicit porcelain contract
|
||||
GIT_STATE_STATUS=$(git status --porcelain=v1 --branch 2>/dev/null) || return 1
|
||||
|
||||
# Stash list
|
||||
GIT_STATE_STASH_LIST=$(GIT_OPTIONAL_LOCKS=0 git stash list 2>/dev/null || true)
|
||||
GIT_STATE_STASH_LIST=$(git stash list 2>/dev/null || true)
|
||||
|
||||
# Smart untracked detection
|
||||
# Much faster than git status because it only lists untracked, not modified
|
||||
local untracked_raw
|
||||
untracked_raw=$(git ls-files --others --exclude-standard 2>/dev/null || true)
|
||||
|
||||
# Apply directory skipping
|
||||
GIT_STATE_UNTRACKED=""
|
||||
while IFS= read -r f; do
|
||||
skip=0
|
||||
for d in "${HEAVY_DIRS[@]}"; do
|
||||
[[ "$f" == "$d/"* ]] && skip=1 && break
|
||||
for d in node_modules vendor dist build cache uploads public/uploads tmp .terraform .cache; do
|
||||
[[ "$f" == "$d/"* ]] && continue 2
|
||||
done
|
||||
(( skip == 0 )) && GIT_STATE_UNTRACKED+="$f"$'\n'
|
||||
GIT_STATE_UNTRACKED+="$f"$'\n'
|
||||
done <<< "$untracked_raw"
|
||||
|
||||
# Build fingerprint
|
||||
if command -v sha1sum >/dev/null; then
|
||||
GIT_STATE_FINGERPRINT=$(
|
||||
printf '%s\n%s\n%s\n%s\n%s\n' \
|
||||
"$GIT_STATE_HEAD" \
|
||||
"$GIT_STATE_STATUS" \
|
||||
"$GIT_STATE_STASH_LIST" \
|
||||
"$GIT_STATE_UNTRACKED" \
|
||||
"$GIT_PROMPT_THEME" \
|
||||
| sha1sum | awk '{print $1}'
|
||||
)
|
||||
else
|
||||
GIT_STATE_FINGERPRINT=$(printf '%s\n%s\n%s\n%s\n' "$GIT_STATE_HEAD" "$GIT_STATE_STATUS" "$GIT_STATE_STASH_LIST" "$GIT_STATE_UNTRACKED")
|
||||
fi
|
||||
|
||||
return 0
|
||||
# POSIX fingerprint
|
||||
GIT_STATE_FINGERPRINT=$(printf '%s%s%s%s%s' \
|
||||
"$GIT_STATE_HEAD" \
|
||||
"$GIT_STATE_STATUS" \
|
||||
"$GIT_STATE_STASH_LIST" \
|
||||
"$GIT_STATE_UNTRACKED" \
|
||||
"$GIT_PROMPT_THEME" | cksum | awk '{print $1}')
|
||||
}
|
||||
|
||||
#
|
||||
# Render logic
|
||||
# Render logic (colored + cursor safe)
|
||||
#
|
||||
actual_git_prompt_info_logic() {
|
||||
|
||||
local branch
|
||||
branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "(detached)")
|
||||
|
||||
local status="$GIT_STATE_STATUS"
|
||||
local stash_list="$GIT_STATE_STASH_LIST"
|
||||
|
||||
local staged conflicts changed untracked stashed is_clean has_remote
|
||||
staged=$(grep -cE '^[AMDR] ' <<<"$status")
|
||||
conflicts=$(grep -cE '^UU ' <<<"$status")
|
||||
changed=$(grep -cE '^.[MD] ' <<<"$status")
|
||||
local staged conflicts changed untracked stashed
|
||||
staged=$(grep -cE '^[AMDR] ' <<<"$GIT_STATE_STATUS")
|
||||
conflicts=$(grep -cE '^UU ' <<<"$GIT_STATE_STATUS")
|
||||
changed=$(grep -cE '^.[MD] ' <<<"$GIT_STATE_STATUS")
|
||||
untracked=$(grep -c . <<<"$GIT_STATE_UNTRACKED")
|
||||
stashed=$(grep -c . <<<"$stash_list")
|
||||
stashed=$(grep -c . <<<"$GIT_STATE_STASH_LIST")
|
||||
|
||||
# Parse branch/remote info
|
||||
local first_line=${status%%$'\n'*}
|
||||
local ahead=0 behind=0
|
||||
has_remote=0
|
||||
local first_line=${GIT_STATE_STATUS%%$'\n'*}
|
||||
local ahead=0 behind=0 has_remote=0
|
||||
[[ "$first_line" == *"..."* ]] && has_remote=1
|
||||
[[ "$first_line" =~ ahead\ ([0-9]+) ]] && ahead=${BASH_REMATCH[1]}
|
||||
[[ "$first_line" =~ behind\ ([0-9]+) ]] && behind=${BASH_REMATCH[1]}
|
||||
|
||||
if [[ "$first_line" == "## "* ]]; then
|
||||
[[ "$first_line" == *"..."* ]] && has_remote=1
|
||||
[[ "$first_line" =~ ahead\ ([0-9]+) ]] && ahead=${BASH_REMATCH[1]}
|
||||
[[ "$first_line" =~ behind\ ([0-9]+) ]] && behind=${BASH_REMATCH[1]}
|
||||
fi
|
||||
local p=""
|
||||
p+="${GC_BRANCH}${BRANCH_ICON}${branch}${C_RESET}"
|
||||
|
||||
is_clean=0
|
||||
(( staged == 0 && conflicts == 0 && changed == 0 && untracked == 0 )) && is_clean=1
|
||||
((staged > 0)) && p+=" ${GC_STAGED}${STAGED_ICON}${staged}${C_RESET}"
|
||||
((conflicts > 0)) && p+=" ${GC_CONFLICT}${CONFLICT_ICON}${conflicts}${C_RESET}"
|
||||
((changed > 0)) && p+=" ${GC_CHANGED}${CHANGED_ICON}${changed}${C_RESET}"
|
||||
((untracked > 0)) && p+=" ${GC_UNTRACKED}${UNTRACKED_ICON}${untracked}${C_RESET}"
|
||||
((stashed > 0)) && p+=" ${GC_STASHED}${STASHED_ICON}${stashed}${C_RESET}"
|
||||
((ahead > 0)) && p+=" ${GC_AHEAD}${AHEAD_ICON}${ahead}${C_RESET}"
|
||||
((behind > 0)) && p+=" ${GC_BEHIND}${BEHIND_ICON}${behind}${C_RESET}"
|
||||
((has_remote == 0)) && p+=" ${GC_NOREMOTE}${NO_REMOTE_ICON}${C_RESET}"
|
||||
|
||||
#
|
||||
# Render themes
|
||||
#
|
||||
if [[ "$GIT_PROMPT_THEME" == "2" ]]; then
|
||||
printf "\[\e[30;44m\]"
|
||||
printf "\[\e[97;44m\] %s%s " "$BRANCH_ICON" "$branch"
|
||||
((staged > 0)) && printf " %s%d" "$STAGED_ICON" "$staged"
|
||||
((conflicts > 0)) && printf " %s%d" "$CONFLICT_ICON" "$conflicts"
|
||||
((changed > 0)) && printf " %s%d" "$CHANGED_ICON" "$changed"
|
||||
((untracked > 0)) && printf " %s%d" "$UNTRACKED_ICON" "$untracked"
|
||||
((stashed > 0)) && printf " %s%d" "$STASHED_ICON" "$stashed"
|
||||
((ahead > 0)) && printf " %s%d" "$AHEAD_ICON" "$ahead"
|
||||
((behind > 0)) && printf " %s%d" "$BEHIND_ICON" "$behind"
|
||||
((has_remote == 0)) && printf " %s" "$NO_REMOTE_ICON"
|
||||
((is_clean)) && printf " %s" "$CLEAN_ICON" || printf " %s" "$DIRTY_ICON"
|
||||
printf "\[\e[34;107m\]"
|
||||
printf "\[\e[30;107m\] \w"
|
||||
printf "\[\e[97;49m\]"
|
||||
printf "\[\e[0m\]"
|
||||
elif [[ "$GIT_PROMPT_THEME" == "3" ]]; then
|
||||
printf "\n\[\e[0;37m\]┌──[\e[0m"
|
||||
printf "\e[38;5;117m%s%s\e[0m" "$BRANCH_ICON" "$branch"
|
||||
((staged > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$STAGED_ICON" "$staged"
|
||||
((conflicts > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$CONFLICT_ICON" "$conflicts"
|
||||
((changed > 0)) && printf " \e[38;5;69m%s%d\e[0m" "$CHANGED_ICON" "$changed"
|
||||
((untracked > 0)) && printf " \e[38;5;41m%s%d\e[0m" "$UNTRACKED_ICON" "$untracked"
|
||||
((stashed > 0)) && printf " \e[38;5;226m%s%d\e[0m" "$STASHED_ICON" "$stashed"
|
||||
((ahead > 0)) && printf " \e[0;37m%s%d\e[0m" "$AHEAD_ICON" "$ahead"
|
||||
((behind > 0)) && printf " \e[0;37m%s%d\e[0m" "$BEHIND_ICON" "$behind"
|
||||
((has_remote == 0)) && printf " \e[38;5;250m%s\e[0m" "$NO_REMOTE_ICON"
|
||||
((is_clean)) && printf " \e[0;32m%s\e[0m" "$CLEAN_ICON" || printf " \e[38;5;196m%s\e[0m" "$DIRTY_ICON"
|
||||
printf "\e[0;37m]\e[0m \[\e[38;5;178m\]\w\[\e[0m\]"
|
||||
printf "\n\[\e[0;37m\]└──\[\e[0m\]"
|
||||
if (( staged + conflicts + changed + untracked == 0 )); then
|
||||
p+=" ${GC_CLEAN}${CLEAN_ICON}${C_RESET}"
|
||||
else
|
||||
printf "\e[0;37m[\e[0m"
|
||||
printf "\e[38;5;117m%s%s\e[0m" "$BRANCH_ICON" "$branch"
|
||||
((staged > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$STAGED_ICON" "$staged"
|
||||
((conflicts > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$CONFLICT_ICON" "$conflicts"
|
||||
((changed > 0)) && printf " \e[38;5;69m%s%d\e[0m" "$CHANGED_ICON" "$changed"
|
||||
((untracked > 0)) && printf " \e[38;5;41m%s%d\e[0m" "$UNTRACKED_ICON" "$untracked"
|
||||
((stashed > 0)) && printf " \e[38;5;226m%s%d\e[0m" "$STASHED_ICON" "$stashed"
|
||||
((ahead > 0)) && printf " \e[0;37m%s%d\e[0m" "$AHEAD_ICON" "$ahead"
|
||||
((behind > 0)) && printf " \e[0;37m%s%d\e[0m" "$BEHIND_ICON" "$behind"
|
||||
((has_remote == 0)) && printf " \e[38;5;250m%s\e[0m" "$NO_REMOTE_ICON"
|
||||
((is_clean)) && printf " \e[0;32m%s\e[0m" "$CLEAN_ICON" || printf " \e[38;5;196m%s\e[0m" "$DIRTY_ICON"
|
||||
printf "\e[0;37m]\e[0m"
|
||||
p+=" ${GC_DIRTY}${DIRTY_ICON}${C_RESET}"
|
||||
fi
|
||||
|
||||
echo "$p"
|
||||
}
|
||||
|
||||
#
|
||||
# Main prompt logic
|
||||
# Prompt logic (cache-safe)
|
||||
#
|
||||
git_prompt_info() {
|
||||
git rev-parse --is-inside-work-tree &>/dev/null || return
|
||||
|
||||
git_collect_state || return
|
||||
|
||||
local fingerprint="$GIT_STATE_FINGERPRINT"
|
||||
|
||||
if [[ -f "$GIT_CACHE_FILE" ]]; then
|
||||
local cached_fp
|
||||
IFS= read -r cached_fp < "$GIT_CACHE_FILE"
|
||||
if [[ -n "$cached_fp" && "$cached_fp" == "$fingerprint" ]]; then
|
||||
read -r cached_fp < "$GIT_CACHE_FILE"
|
||||
if [[ "$cached_fp" == "$GIT_STATE_FINGERPRINT" ]]; then
|
||||
sed '1d' "$GIT_CACHE_FILE"
|
||||
return
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
local output
|
||||
output=$(actual_git_prompt_info_logic) || return
|
||||
output=$(actual_git_prompt_info_logic)
|
||||
|
||||
{
|
||||
echo "$fingerprint"
|
||||
echo "$GIT_STATE_FINGERPRINT"
|
||||
echo "$output"
|
||||
} > "$GIT_CACHE_FILE"
|
||||
|
||||
@@ -257,37 +221,28 @@ update_git_prompt() {
|
||||
local PROMPT_CHAR='$'
|
||||
[[ $EUID -eq 0 ]] && PROMPT_CHAR='#'
|
||||
|
||||
GIT_PS1="$(git_prompt_info)"
|
||||
|
||||
#
|
||||
# PS1 custom for all themes inside a git repository
|
||||
#
|
||||
local PS1_CUSTOM='\[\e[0;32m\]\u@\h\[\e[0m\]'
|
||||
local GIT_PS1
|
||||
GIT_PS1=$(git_prompt_info)
|
||||
|
||||
if [[ -n "$GIT_PS1" ]]; then
|
||||
if [[ "$GIT_PROMPT_THEME" == "3" ]]; then
|
||||
PS1="${PS1_CUSTOM} ${GIT_PS1} ${PROMPT_CHAR} "
|
||||
elif [[ "$GIT_PROMPT_THEME" == "2" ]]; then
|
||||
PS1="${PS1_CUSTOM} ${GIT_PS1}\[\e[0m\] ${PROMPT_CHAR} "
|
||||
else
|
||||
PS1="${PS1_CUSTOM} \[${GIT_PS1}\] \[\e[38;5;178m\]\w\[\e[0m\] ${PROMPT_CHAR} "
|
||||
fi
|
||||
|
||||
PS1="${C_USER}\u@\h${C_RESET} [${GIT_PS1}] ${C_PATH}\w${C_RESET} ${PROMPT_CHAR} "
|
||||
else
|
||||
#
|
||||
# PS1 cusom for all themes outside a git repository
|
||||
#
|
||||
PS1='\[\e[0;32m\]\u@\h\[\e[0m\]:\[\e[38;5;178m\]\w\[\e[0m\] '"${PROMPT_CHAR} "
|
||||
PS1="${C_USER}\u@\h${C_RESET} ${C_PATH}\w${C_RESET} ${PROMPT_CHAR} "
|
||||
fi
|
||||
}
|
||||
|
||||
PROMPT_COMMAND=update_git_prompt
|
||||
|
||||
#
|
||||
# Theme switcher
|
||||
#
|
||||
gpchange() {
|
||||
local theme="${1:-}"
|
||||
|
||||
if [[ "$theme" =~ ^[1-5]$ ]]; then
|
||||
export GIT_PROMPT_THEME="$theme"
|
||||
set_git_prompt_theme_icons
|
||||
set_git_prompt_theme_colors
|
||||
update_git_prompt
|
||||
echo "Switched to Git prompt theme $theme"
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user