169 lines
5.3 KiB
Bash
169 lines
5.3 KiB
Bash
#!/bin/bash
|
||
|
||
# Cache symbols (generate once)
|
||
declare -A GIT_SYMBOLS
|
||
_init_git_symbols() {
|
||
[[ -n "${GIT_SYMBOLS[clean]:-}" ]] && return # Already initialized
|
||
|
||
_colors_bash "$@"
|
||
|
||
GIT_SYMBOLS[pipe]="\x7C"
|
||
GIT_SYMBOLS[clean]="✔"
|
||
GIT_SYMBOLS[dirty]="∗"
|
||
GIT_SYMBOLS[ahead]="↑"
|
||
GIT_SYMBOLS[behind]="↓"
|
||
GIT_SYMBOLS[diverged]="⇅"
|
||
GIT_SYMBOLS[untracked]="?"
|
||
GIT_SYMBOLS[added]="+"
|
||
GIT_SYMBOLS[deleted]="D"
|
||
GIT_SYMBOLS[modified]="M"
|
||
GIT_SYMBOLS[renamed]="R"
|
||
GIT_SYMBOLS[staged]="●"
|
||
}
|
||
|
||
_get_git_branch() {
|
||
git symbolic-ref --short HEAD 2>/dev/null || echo "(no branch)"
|
||
}
|
||
|
||
_get_git_progress() {
|
||
local git_dir
|
||
git_dir="$(git rev-parse --git-dir 2>/dev/null)" || return 0
|
||
|
||
[[ -f "$git_dir/MERGE_HEAD" ]] && echo " [merge]" && return 0
|
||
[[ -d "$git_dir/rebase-apply" ]] && echo " [rebase]" && return 0
|
||
[[ -d "$git_dir/rebase-merge" ]] && echo " [rebase]" && return 0
|
||
[[ -f "$git_dir/CHERRY_PICK_HEAD" ]] && echo " [cherry-pick]" && return 0
|
||
[[ -f "$git_dir/BISECT_LOG" ]] && echo " [bisect]" && return 0
|
||
[[ -f "$git_dir/REVERT_HEAD" ]] && echo " [revert]" && return 0
|
||
|
||
return 0
|
||
}
|
||
|
||
# Single git status call - parse everything at once
|
||
_parse_git_status() {
|
||
local status_output ahead_behind
|
||
|
||
# Single git status call
|
||
status_output="$(git status --porcelain=v1 2>/dev/null)"
|
||
|
||
# Get ahead/behind counts - use more reliable method
|
||
if git rev-parse --abbrev-ref "@{upstream}" >/dev/null 2>&1; then
|
||
# Use separate commands for more reliable parsing
|
||
GIT_STATUS[ahead]="$(git rev-list --count HEAD ^"@{upstream}" 2>/dev/null || echo 0)"
|
||
GIT_STATUS[behind]="$(git rev-list --count "@{upstream}" ^HEAD 2>/dev/null || echo 0)"
|
||
|
||
# Fallback to original method if separate commands fail
|
||
if [[ "${GIT_STATUS[ahead]}" == "0" && "${GIT_STATUS[behind]}" == "0" ]]; then
|
||
ahead_behind="$(git rev-list --left-right --count "@{upstream}"...HEAD 2>/dev/null)"
|
||
if [[ "$ahead_behind" =~ ^([0-9]+)[[:space:]]+([0-9]+)$ ]]; then
|
||
GIT_STATUS[behind]="${BASH_REMATCH[1]}"
|
||
GIT_STATUS[ahead]="${BASH_REMATCH[2]}"
|
||
fi
|
||
fi
|
||
else
|
||
GIT_STATUS[behind]=0
|
||
GIT_STATUS[ahead]=0
|
||
fi
|
||
|
||
# Parse status output
|
||
GIT_STATUS[dirty]=0
|
||
GIT_STATUS[staged]=0
|
||
GIT_STATUS[untracked]=0
|
||
GIT_STATUS[modified]=0
|
||
GIT_STATUS[added]=0
|
||
GIT_STATUS[deleted]=0
|
||
GIT_STATUS[renamed]=0
|
||
|
||
[[ -z "$status_output" ]] && return
|
||
|
||
local line
|
||
while IFS= read -r line; do
|
||
[[ -z "$line" ]] && continue
|
||
|
||
local index_status="${line:0:1}"
|
||
local work_status="${line:1:1}"
|
||
|
||
# Count changes
|
||
((GIT_STATUS[dirty]++))
|
||
|
||
# Index (staged) changes
|
||
case "$index_status" in
|
||
[MADRC]) ((GIT_STATUS[staged]++)) ;;
|
||
esac
|
||
|
||
# Working tree changes
|
||
case "$work_status" in
|
||
M) ((GIT_STATUS[modified]++)) ;;
|
||
D) ((GIT_STATUS[deleted]++)) ;;
|
||
esac
|
||
|
||
# Special cases
|
||
case "$line" in
|
||
"??*") ((GIT_STATUS[untracked]++)) ;;
|
||
"A "*) ((GIT_STATUS[added]++)) ;;
|
||
"R "*) ((GIT_STATUS[renamed]++)) ;;
|
||
esac
|
||
|
||
done <<< "$status_output"
|
||
}
|
||
|
||
# Fast git status generation
|
||
_get_git_status_fast() {
|
||
_init_git_symbols "$@"
|
||
|
||
declare -A GIT_STATUS
|
||
_parse_git_status
|
||
|
||
local output=""
|
||
|
||
# Check if completely clean (no dirty files AND no ahead/behind)
|
||
if [[ ${GIT_STATUS[dirty]} -eq 0 && ${GIT_STATUS[ahead]} -eq 0 && ${GIT_STATUS[behind]} -eq 0 ]]; then
|
||
echo -n "${BOLD}${CYAN}${GIT_SYMBOLS[clean]}${RESET}"
|
||
return
|
||
fi
|
||
|
||
# Start with dirty indicator if there are dirty files
|
||
if [[ ${GIT_STATUS[dirty]} -gt 0 ]]; then
|
||
output="${BOLD}${RED}${GIT_SYMBOLS[dirty]}${GIT_STATUS[dirty]}${RESET}"
|
||
fi
|
||
|
||
# Add ahead/behind status
|
||
if [[ ${GIT_STATUS[ahead]} -gt 0 && ${GIT_STATUS[behind]} -gt 0 ]]; then
|
||
output+="${BOLD}${YELLOW}${GIT_SYMBOLS[diverged]}${GIT_STATUS[ahead]}/${GIT_STATUS[behind]}${RESET}"
|
||
elif [[ ${GIT_STATUS[ahead]} -gt 0 ]]; then
|
||
output+="${BOLD}${GREEN}${GIT_SYMBOLS[ahead]}${GIT_STATUS[ahead]}${RESET}"
|
||
elif [[ ${GIT_STATUS[behind]} -gt 0 ]]; then
|
||
output+="${BOLD}${RED}${GIT_SYMBOLS[behind]}${GIT_STATUS[behind]}${RESET}"
|
||
fi
|
||
|
||
# File status indicators
|
||
[[ ${GIT_STATUS[staged]} -gt 0 ]] && output+="${BOLD}${GREEN}${GIT_SYMBOLS[staged]}${RESET}"
|
||
[[ ${GIT_STATUS[untracked]} -gt 0 ]] && output+="${BOLD}${RED}${GIT_SYMBOLS[untracked]}${RESET}"
|
||
[[ ${GIT_STATUS[added]} -gt 0 ]] && output+="${BOLD}${GREEN}${GIT_SYMBOLS[added]}${RESET}"
|
||
[[ ${GIT_STATUS[deleted]} -gt 0 ]] && output+="${BOLD}${RED}${GIT_SYMBOLS[deleted]}${RESET}"
|
||
[[ ${GIT_STATUS[renamed]} -gt 0 ]] && output+="${BOLD}${BLUE}${GIT_SYMBOLS[renamed]}${RESET}"
|
||
|
||
echo -n "$output"
|
||
}
|
||
|
||
_prompt_get_git_info_fast() {
|
||
_colors_bash "$@"
|
||
|
||
local branch
|
||
branch="$(_get_git_branch)"
|
||
|
||
if [[ -n "$branch" ]]; then
|
||
local status
|
||
status="$(_get_git_status_fast "$@")"
|
||
printf '%b%s%s%b' "${BOLD}${YELLOW}" "git:($branch" "$status" "${BOLD}${YELLOW})"
|
||
fi
|
||
}
|
||
|
||
__prompt_git() {
|
||
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||
echo -n "${BOLD}${WHITE} on ${RESET}"
|
||
_prompt_get_git_info_fast "$@"
|
||
echo -n "${BOLD}${RED}$(_get_git_progress)${RESET}"
|
||
fi
|
||
}
|