#!/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]="|" 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}${YELLOW}${GIT_SYMBOLS[dirty]}${GIT_STATUS[dirty]}${RESET}" fi # Add ahead/behind status with semantic colors if [[ ${GIT_STATUS[ahead]} -gt 0 && ${GIT_STATUS[behind]} -gt 0 ]]; then output+="${BOLD}${ORANGE}${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 with semantic colors [[ ${GIT_STATUS[staged]} -gt 0 ]] && output+="${BOLD}${GREEN}${GIT_SYMBOLS[staged]}${RESET}" [[ ${GIT_STATUS[untracked]} -gt 0 ]] && output+="${BOLD}${GREY}${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}${CYAN}${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 "$@")" # Use different colors for different parts printf '%bgit:(%b%s%b%s%b)%b' \ "${GREY}" \ "${BOLD}${LEMON}" "$branch" "${RESET}" \ "$status" \ "${GREY}" "${RESET}" 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 }