You've already forked bash-git-prompt
252 lines
6.1 KiB
Plaintext
252 lines
6.1 KiB
Plaintext
# Author : Allan Christensen
|
|
# First Created : 24-06-2025 (DD-MM-YYYY)
|
|
# Description : Bash Git prompt for Linux
|
|
# License : MIT License
|
|
|
|
#
|
|
# Set default theme to 1 if it's not already set
|
|
#
|
|
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
|
|
#
|
|
set_git_prompt_theme_icons() {
|
|
case "$GIT_PROMPT_THEME" in
|
|
2|3)
|
|
BRANCH_ICON=" "
|
|
STAGED_ICON=""
|
|
CONFLICT_ICON="✘"
|
|
CHANGED_ICON="±"
|
|
UNTRACKED_ICON=""
|
|
STASHED_ICON=""
|
|
AHEAD_ICON=""
|
|
BEHIND_ICON=""
|
|
NO_REMOTE_ICON=""
|
|
CLEAN_ICON="✔"
|
|
DIRTY_ICON="✘"
|
|
;;
|
|
4)
|
|
BRANCH_ICON="⎇ "
|
|
STAGED_ICON="o"
|
|
CONFLICT_ICON="!"
|
|
CHANGED_ICON="±"
|
|
UNTRACKED_ICON="…"
|
|
STASHED_ICON="☰"
|
|
AHEAD_ICON="⇡"
|
|
BEHIND_ICON="⇣"
|
|
NO_REMOTE_ICON="-"
|
|
CLEAN_ICON="✓"
|
|
DIRTY_ICON="✗"
|
|
;;
|
|
5)
|
|
BRANCH_ICON=":: "
|
|
STAGED_ICON="+"
|
|
CONFLICT_ICON="x"
|
|
CHANGED_ICON="*"
|
|
UNTRACKED_ICON="?"
|
|
STASHED_ICON="s"
|
|
AHEAD_ICON="a"
|
|
BEHIND_ICON="b"
|
|
NO_REMOTE_ICON="no"
|
|
CLEAN_ICON="ok"
|
|
DIRTY_ICON="!"
|
|
;;
|
|
*)
|
|
BRANCH_ICON=" "
|
|
STAGED_ICON=""
|
|
CONFLICT_ICON="✘"
|
|
CHANGED_ICON="±"
|
|
UNTRACKED_ICON=""
|
|
STASHED_ICON=""
|
|
AHEAD_ICON=""
|
|
BEHIND_ICON=""
|
|
NO_REMOTE_ICON=""
|
|
CLEAN_ICON="✔"
|
|
DIRTY_ICON="✘"
|
|
;;
|
|
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
|
|
|
|
#
|
|
# Cache (per shell tree, auto-cleared on reboot)
|
|
#
|
|
GIT_CACHE_FILE="/dev/shm/git_prompt_${USER}_${PPID}.cache"
|
|
|
|
#
|
|
# State collection
|
|
#
|
|
git_collect_state() {
|
|
|
|
GIT_STATE_HEAD=$(git rev-parse HEAD 2>/dev/null) || return 1
|
|
|
|
# Explicit porcelain contract
|
|
GIT_STATE_STATUS=$(git status --porcelain=v1 --branch 2>/dev/null) || return 1
|
|
|
|
GIT_STATE_STASH_LIST=$(git stash list 2>/dev/null || true)
|
|
|
|
local untracked_raw
|
|
untracked_raw=$(git ls-files --others --exclude-standard 2>/dev/null || true)
|
|
|
|
GIT_STATE_UNTRACKED=""
|
|
while IFS= read -r f; do
|
|
for d in node_modules vendor dist build cache uploads public/uploads tmp .terraform .cache; do
|
|
[[ "$f" == "$d/"* ]] && continue 2
|
|
done
|
|
GIT_STATE_UNTRACKED+="$f"$'\n'
|
|
done <<< "$untracked_raw"
|
|
|
|
# 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 (colored + cursor safe)
|
|
#
|
|
actual_git_prompt_info_logic() {
|
|
|
|
local branch
|
|
branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "(detached)")
|
|
|
|
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 . <<<"$GIT_STATE_STASH_LIST")
|
|
|
|
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]}
|
|
|
|
local p=""
|
|
p+="${GC_BRANCH}${BRANCH_ICON}${branch}${C_RESET}"
|
|
|
|
((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}"
|
|
|
|
if (( staged + conflicts + changed + untracked == 0 )); then
|
|
p+=" ${GC_CLEAN}${CLEAN_ICON}${C_RESET}"
|
|
else
|
|
p+=" ${GC_DIRTY}${DIRTY_ICON}${C_RESET}"
|
|
fi
|
|
|
|
echo "$p"
|
|
}
|
|
|
|
#
|
|
# Prompt logic (cache-safe)
|
|
#
|
|
git_prompt_info() {
|
|
git_collect_state || return
|
|
|
|
if [[ -f "$GIT_CACHE_FILE" ]]; then
|
|
local cached_fp
|
|
read -r cached_fp < "$GIT_CACHE_FILE"
|
|
if [[ "$cached_fp" == "$GIT_STATE_FINGERPRINT" ]]; then
|
|
sed '1d' "$GIT_CACHE_FILE"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
local output
|
|
output=$(actual_git_prompt_info_logic)
|
|
|
|
{
|
|
echo "$GIT_STATE_FINGERPRINT"
|
|
echo "$output"
|
|
} > "$GIT_CACHE_FILE"
|
|
|
|
echo "$output"
|
|
}
|
|
|
|
update_git_prompt() {
|
|
local PROMPT_CHAR='$'
|
|
[[ $EUID -eq 0 ]] && PROMPT_CHAR='#'
|
|
|
|
local GIT_PS1
|
|
GIT_PS1=$(git_prompt_info)
|
|
|
|
if [[ -n "$GIT_PS1" ]]; then
|
|
PS1="${C_USER}\u@\h${C_RESET} [${GIT_PS1}] ${C_PATH}\w${C_RESET} ${PROMPT_CHAR} "
|
|
else
|
|
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
|
|
echo "Usage: gpchange <1-5>"
|
|
fi
|
|
}
|