perf: git

This commit is contained in:
2025-12-23 14:40:22 +02:00
parent 94fb437570
commit 6de5f36ca5

View File

@@ -111,55 +111,85 @@ _dots_session() {
} }
_dots_git_info_sync() { _dots_git_info_sync() {
local branch="" ahead=0 behind=0 staged=0 unstaged=0 untracked=0 # Use git status --short with awk for better performance
local line # Avoids shell loops and uses compiled awk for parsing
local output
output=$(git status --short --branch --ignore-submodules=dirty 2>/dev/null) || return
while IFS= read -r line; do local branch="" ahead=0 behind=0 staged=0 unstaged=0 untracked=0
case "$line" in
"# branch.head "*) # Parse efficiently: branch info from first line, counts from rest
branch="${line#\# branch.head }" local first_line="${output%%$'\n'*}"
[[ "$branch" == "(detached)" ]] && branch=""
;; # Extract branch from "## branch...remote [ahead N, behind M]"
"# branch.oid "*) if [[ "$first_line" == "## "* ]]; then
# Use short oid for detached HEAD branch="${first_line#\#\# }"
[[ -z "$branch" ]] && branch="${${line#\# branch.oid }:0:7}" # Handle detached HEAD
;; if [[ "$branch" == "HEAD (no branch)" || "$branch" == [0-9a-f]##* ]]; then
"# branch.ab "*) branch="${branch:0:7}"
local ab="${line#\# branch.ab }" else
ahead="${ab%% *}"; ahead="${ahead#+}" # Remove tracking info
behind="${ab##* }"; behind="${behind#-}" branch="${branch%%...*}"
;; branch="${branch%% *}"
"1 "*) # changed entry fi
[[ "${line:2:1}" != "." ]] && (( staged++ ))
[[ "${line:3:1}" != "." ]] && (( unstaged++ )) # Extract ahead/behind
;; [[ "$first_line" =~ "ahead ([0-9]+)" ]] && ahead="${match[1]}"
"2 "*) # renamed/copied [[ "$first_line" =~ "behind ([0-9]+)" ]] && behind="${match[1]}"
[[ "${line:2:1}" != "." ]] && (( staged++ )) fi
[[ "${line:3:1}" != "." ]] && (( unstaged++ ))
;;
"? "*) # untracked
(( untracked++ ))
;;
"u "*) # unmerged
(( staged++ ))
;;
esac
done < <(git status --porcelain=v2 --branch 2>/dev/null)
[[ -z "$branch" ]] && return [[ -z "$branch" ]] && return
# Count file status with awk (fast, no shell loops)
local -a counts
counts=( $(awk '
NR == 1 { next } # Skip branch line
/^\?\?/ { untracked++; next }
{
x = substr($0, 1, 1)
y = substr($0, 2, 1)
if (x != " " && x != "?") staged++
if (y != " " && y != "?") unstaged++
}
END { print staged+0, unstaged+0, untracked+0 }
' <<< "$output") )
staged=${counts[1]:-0}
unstaged=${counts[2]:-0}
untracked=${counts[3]:-0}
local info="${_dots_pc[grey]}(${branch})${_dots_pc[reset]}" local info="${_dots_pc[grey]}(${branch})${_dots_pc[reset]}"
local dirty="" local dirty=""
(( staged )) && dirty+="${_dots_pc[teal]}+${staged}${_dots_pc[reset]}" local sep=""
(( unstaged )) && dirty+=" ${_dots_pc[orange]}~${unstaged}${_dots_pc[reset]}" if (( staged )); then
(( untracked )) && dirty+=" ${_dots_pc[grey]}?${untracked}${_dots_pc[reset]}" dirty+="${_dots_pc[teal]}+${staged}${_dots_pc[reset]}"
[[ -n "$dirty" ]] && info+=" ${dirty}" sep=" "
fi
if (( unstaged )); then
dirty+="${sep}${_dots_pc[orange]}~${unstaged}${_dots_pc[reset]}"
sep=" "
fi
if (( untracked )); then
dirty+="${sep}${_dots_pc[grey]}?${untracked}${_dots_pc[reset]}"
fi
local arrows="" local arrows=""
(( ahead )) && arrows+="${_dots_pc[teal]}${ahead}${_dots_pc[reset]}" sep=""
(( behind )) && arrows+=" ${_dots_pc[orange]}${behind}${_dots_pc[reset]}" if (( ahead )); then
[[ -n "$arrows" ]] && info+=" ${arrows}" arrows+="${_dots_pc[teal]}${ahead}${_dots_pc[reset]}"
sep=" "
fi
if (( behind )); then
arrows+="${sep}${_dots_pc[orange]}${behind}${_dots_pc[reset]}"
fi
if [[ -n "$dirty" || -n "$arrows" ]]; then
info+=" "
[[ -n "$dirty" ]] && info+="$dirty"
[[ -n "$dirty" && -n "$arrows" ]] && info+=" "
[[ -n "$arrows" ]] && info+="$arrows"
fi
print -r -- "$info" print -r -- "$info"
} }
@@ -171,10 +201,11 @@ typeset -g _dots_git_async_fd=""
_dots_git_async_callback() { _dots_git_async_callback() {
local fd=$1 local fd=$1
_dots_git_info_result="" local result=""
# Use sysread for efficient non-blocking read from fd # Use sysread for efficient non-blocking read from fd
if [[ -n "$fd" ]] && sysread -i "$fd" _dots_git_info_result 2>/dev/null; then if [[ -n "$fd" ]] && sysread -i "$fd" result 2>/dev/null; then
_dots_git_info_result="${_dots_git_info_result%$'\n'}" # trim trailing newline result="${result%$'\n'}" # trim trailing newline
_dots_git_info_result="$result"
_dots_build_dots_prompt_base _dots_build_dots_prompt_base
PROMPT="$_dots_prompt_base" PROMPT="$_dots_prompt_base"
zle && zle reset-prompt zle && zle reset-prompt
@@ -186,19 +217,17 @@ _dots_git_async_callback() {
} }
_dots_git_async_start() { _dots_git_async_start() {
# Cancel any pending async job # Check if we're in a git repo
local git_dir
git_dir=$(git rev-parse --git-dir 2>/dev/null) || return
# Cancel any pending async job (reuse single worker)
if [[ -n "$_dots_git_async_fd" ]]; then if [[ -n "$_dots_git_async_fd" ]]; then
exec {_dots_git_async_fd}<&- 2>/dev/null exec {_dots_git_async_fd}<&- 2>/dev/null
zle -F "$_dots_git_async_fd" 2>/dev/null zle -F "$_dots_git_async_fd" 2>/dev/null
_dots_git_async_fd="" _dots_git_async_fd=""
fi fi
# Clear result on directory change
if [[ "$PWD" != "$_dots_git_info_pwd" ]]; then
_dots_git_info_result=""
_dots_git_info_pwd="$PWD"
fi
# Start background job # Start background job
exec {_dots_git_async_fd}< <( exec {_dots_git_async_fd}< <(
_dots_git_info_sync _dots_git_info_sync
@@ -249,8 +278,11 @@ _dots_precmd() {
RPROMPT="${(j: :)rp_parts}" RPROMPT="${(j: :)rp_parts}"
# Clear git info on directory change before building prompt # On directory change, clear git info until async refreshes
[[ "$PWD" != "$_dots_git_info_pwd" ]] && _dots_git_info_result="" if [[ "$PWD" != "$_dots_git_info_pwd" ]]; then
_dots_git_info_pwd="$PWD"
_dots_git_info_result=""
fi
_dots_build_dots_prompt_base _dots_build_dots_prompt_base
_dots_git_async_start _dots_git_async_start