perf: git
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user