#!/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 in one call if git rev-parse --abbrev-ref "@{upstream}" >/dev/null 2>&1; then ahead_behind="$(git rev-list --left-right --count "@{upstream}"...HEAD 2>/dev/null)" GIT_STATUS[behind]="${ahead_behind% *}" GIT_STATUS[ahead]="${ahead_behind#* }" 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="" # Clean repo if [[ ${GIT_STATUS[dirty]} -eq 0 ]]; then echo -n "${BOLD}${CYAN}${GIT_SYMBOLS[clean]}${RESET}" return fi # Dirty count output="${BOLD}${RED}${GIT_SYMBOLS[dirty]}${GIT_STATUS[dirty]}${RESET}" # Ahead/behind 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 }