From 6b5ef98fc65062994405a0c6ae4fe5ed8bbb69d1 Mon Sep 17 00:00:00 2001 From: allan Date: Sun, 23 Nov 2025 15:17:39 +0000 Subject: [PATCH] changes to caching --- bash-git-prompt | 148 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 48 deletions(-) diff --git a/bash-git-prompt b/bash-git-prompt index b066634..7e01dc2 100644 --- a/bash-git-prompt +++ b/bash-git-prompt @@ -65,37 +65,78 @@ set_git_prompt_theme_icons() { } set_git_prompt_theme_icons -# Caching mechanism +# Caching mechanism (per-shell cache file) GIT_CACHE_FILE="/tmp/git_prompt_cache.$$" -GIT_CACHE_EXPIRY=2 # seconds + +# Internal state used for caching +GIT_STATE_HEAD="" +GIT_STATE_STATUS="" +GIT_STATE_STASH_LIST="" +GIT_STATE_FINGERPRINT="" + +# Collect git state and build a fingerprint of the working tree +git_collect_state() { + # HEAD (commit hash) + GIT_STATE_HEAD=$(git rev-parse HEAD 2>/dev/null) || return 1 + + # Full status including branch / ahead / behind + GIT_STATE_STATUS=$(GIT_OPTIONAL_LOCKS=0 git status --porcelain --branch 2>/dev/null) || return 1 + + # Stash list (for stash count) + GIT_STATE_STASH_LIST=$(GIT_OPTIONAL_LOCKS=0 git stash list 2>/dev/null || true) + + # Build fingerprint from head, status and stash list + if command -v sha1sum >/dev/null 2>&1; then + GIT_STATE_FINGERPRINT=$( + printf '%s\n%s\n%s\n' \ + "$GIT_STATE_HEAD" \ + "$GIT_STATE_STATUS" \ + "$GIT_STATE_STASH_LIST" \ + | sha1sum 2>/dev/null | awk '{print $1}' + ) + else + # Fallback if sha1sum is missing (very rare on Ubuntu) + GIT_STATE_FINGERPRINT=$(printf '%s\n%s\n%s\n' "$GIT_STATE_HEAD" "$GIT_STATE_STATUS" "$GIT_STATE_STASH_LIST") + fi + + return 0 +} actual_git_prompt_info_logic() { - git rev-parse --is-inside-work-tree &>/dev/null || return + # Require collected state + [[ -n "$GIT_STATE_STATUS" ]] || return local branch branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "(detached)") - local status - status=$(GIT_OPTIONAL_LOCKS=0 git status --porcelain --branch 2>/dev/null) || return + local status="$GIT_STATE_STATUS" + local stash_list="$GIT_STATE_STASH_LIST" local staged conflicts changed untracked stashed is_clean has_remote staged=$(grep -cE '^[AMDR] ' <<<"$status") conflicts=$(grep -cE '^UU ' <<<"$status") changed=$(grep -cE '^.[MD] ' <<<"$status") untracked=$(grep -cE '^\?\? ' <<<"$status") - stashed=$(GIT_OPTIONAL_LOCKS=0 git stash list 2>/dev/null | grep -c .) + stashed=$(grep -c . <<<"$stash_list") - local upstream - upstream=$(git rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>/dev/null) - [[ -n "$upstream" ]] && has_remote=1 || has_remote=0 + # Parse the first status line to detect remote and ahead/behind + local first_line + first_line=${status%%$'\n'*} - local ahead=0 behind=0 - if (( has_remote )); then - local counts - counts=$(git rev-list --left-right --count "${upstream}...HEAD" 2>/dev/null | tr -s '[:space:]' ' ') - read -r behind ahead <<< "$counts" - ahead=${ahead:-0} - behind=${behind:-0} + local ahead=0 + local behind=0 + has_remote=0 + + if [[ "$first_line" == "## "* ]]; then + # If there's a tracking branch, the line will contain "..." + [[ "$first_line" == *"..."* ]] && has_remote=1 + + if [[ "$first_line" =~ ahead\ ([0-9]+) ]]; then + ahead=${BASH_REMATCH[1]} + fi + if [[ "$first_line" =~ behind\ ([0-9]+) ]]; then + behind=${BASH_REMATCH[1]} + fi fi is_clean=0 @@ -106,13 +147,13 @@ actual_git_prompt_info_logic() { if [[ "$GIT_PROMPT_THEME" == "2" ]]; then printf "\[\e[30;44m\]" printf "\[\e[97;44m\] %s%s" "$BRANCH_ICON" "$branch" - ((staged > 0)) && printf " %s%d" "$STAGED_ICON" "$staged" - ((conflicts > 0)) && printf " %s%d" "$CONFLICT_ICON" "$conflicts" - ((changed > 0)) && printf " %s%d" "$CHANGED_ICON" "$changed" - ((untracked > 0)) && printf " %s%d" "$UNTRACKED_ICON" "$untracked" - ((stashed > 0)) && printf " %s%d" "$STASHED_ICON" "$stashed" - ((ahead > 0)) && printf " %s%d" "$AHEAD_ICON" "$ahead" - ((behind > 0)) && printf " %s%d" "$BEHIND_ICON" "$behind" + ((staged > 0)) && printf " %s%d" "$STAGED_ICON" "$staged" + ((conflicts > 0)) && printf " %s%d" "$CONFLICT_ICON" "$conflicts" + ((changed > 0)) && printf " %s%d" "$CHANGED_ICON" "$changed" + ((untracked > 0)) && printf " %s%d" "$UNTRACKED_ICON" "$untracked" + ((stashed > 0)) && printf " %s%d" "$STASHED_ICON" "$stashed" + ((ahead > 0)) && printf " %s%d" "$AHEAD_ICON" "$ahead" + ((behind > 0)) && printf " %s%d" "$BEHIND_ICON" "$behind" ((has_remote == 0)) && printf " %s" "$NO_REMOTE_ICON" ((is_clean)) && printf " %s" "$CLEAN_ICON" || printf " %s" "$DIRTY_ICON" printf "\[\e[34;107m\]" @@ -122,29 +163,29 @@ actual_git_prompt_info_logic() { elif [[ "$GIT_PROMPT_THEME" == "3" ]]; then printf "\n\[\e[0;37m\]┌──[\e[0m" printf "\e[38;5;117m%s%s\e[0m" "$BRANCH_ICON" "$branch" - ((staged > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$STAGED_ICON" "$staged" - ((conflicts > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$CONFLICT_ICON" "$conflicts" - ((changed > 0)) && printf " \e[38;5;69m%s%d\e[0m" "$CHANGED_ICON" "$changed" - ((untracked > 0)) && printf " \e[38;5;41m%s%d\e[0m" "$UNTRACKED_ICON" "$untracked" - ((stashed > 0)) && printf " \e[38;5;226m%s%d\e[0m" "$STASHED_ICON" "$stashed" - ((ahead > 0)) && printf " \e[0;37m%s%d\e[0m" "$AHEAD_ICON" "$ahead" - ((behind > 0)) && printf " \e[0;37m%s%d\e[0m" "$BEHIND_ICON" "$behind" - ((has_remote == 0)) && printf " \e[38;5;250m%s\e[0m" "$NO_REMOTE_ICON" + ((staged > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$STAGED_ICON" "$staged" + ((conflicts > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$CONFLICT_ICON" "$conflicts" + ((changed > 0)) && printf " \e[38;5;69m%s%d\e[0m" "$CHANGED_ICON" "$changed" + ((untracked > 0)) && printf " \e[38;5;41m%s%d\e[0m" "$UNTRACKED_ICON" "$untracked" + ((stashed > 0)) && printf " \e[38;5;226m%s%d\e[0m" "$STASHED_ICON" "$stashed" + ((ahead > 0)) && printf " \e[0;37m%s%d\e[0m" "$AHEAD_ICON" "$ahead" + ((behind > 0)) && printf " \e[0;37m%s%d\e[0m" "$BEHIND_ICON" "$behind" + ((has_remote == 0)) && printf " \e[38;5;250m%s\e[0m" "$NO_REMOTE_ICON" ((is_clean)) && printf " \e[0;32m%s\e[0m" "$CLEAN_ICON" || printf " \e[38;5;196m%s\e[0m" "$DIRTY_ICON" printf "\e[0;37m]\e[0m \[\e[38;5;178m\]\w\[\e[0m\]" printf "\n\[\e[0;37m\]└──\[\e[0m\]" else printf "\e[0;37m[\e[0m" printf "\e[38;5;117m%s%s\e[0m" "$BRANCH_ICON" "$branch" - ((staged > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$STAGED_ICON" "$staged" - ((conflicts > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$CONFLICT_ICON" "$conflicts" - ((changed > 0)) && printf " \e[38;5;69m%s%d\e[0m" "$CHANGED_ICON" "$changed" - ((untracked > 0)) && printf " \e[38;5;41m%s%d\e[0m" "$UNTRACKED_ICON" "$untracked" - ((stashed > 0)) && printf " \e[38;5;226m%s%d\e[0m" "$STASHED_ICON" "$stashed" - ((ahead > 0)) && printf " \e[0;37m%s%d\e[0m" "$AHEAD_ICON" "$ahead" - ((behind > 0)) && printf " \e[0;37m%s%d\e[0m" "$BEHIND_ICON" "$behind" - ((has_remote == 0)) && printf " \e[38;5;250m%s\e[0m" "$NO_REMOTE_ICON" - ((is_clean)) && printf " \e[0;32m%s\e[0m" "$CLEAN_ICON" || printf " \e[38;5;196m%s\e[0m" "$DIRTY_ICON" + ((staged > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$STAGED_ICON" "$staged" + ((conflicts > 0)) && printf " \e[38;5;196m%s%d\e[0m" "$CONFLICT_ICON" "$conflicts" + ((changed > 0)) && printf " \e[38;5;69m%s%d\e[0m" "$CHANGED_ICON" "$changed" + ((untracked > 0)) && printf " \e[38;5;41m%s%d\e[0m" "$UNTRACKED_ICON" "$untracked" + ((stashed > 0)) && printf " \e[38;5;226m%s%d\e[0m" "$STASHED_ICON" "$stashed" + ((ahead > 0)) && printf " \e[0;37m%s%d\e[0m" "$AHEAD_ICON" "$ahead" + ((behind > 0)) && printf " \e[0;37m%s%d\e[0m" "$BEHIND_ICON" "$behind" + ((has_remote == 0)) && printf " \e[38;5;250m%s\e[0m" "$NO_REMOTE_ICON" + ((is_clean)) && printf "\e[0;32m%s\e[0m" "$CLEAN_ICON" || printf " \e[38;5;196m%s\e[0m" "$DIRTY_ICON" printf "\e[0;37m]\e[0m" fi } @@ -152,18 +193,29 @@ actual_git_prompt_info_logic() { git_prompt_info() { git rev-parse --is-inside-work-tree &>/dev/null || return - local now=$(date +%s) - local last_mod=0 - [[ -f "$GIT_CACHE_FILE" ]] && last_mod=$(stat -c %Y "$GIT_CACHE_FILE" 2>/dev/null || echo 0) + git_collect_state || return - if (( now - last_mod < GIT_CACHE_EXPIRY )); then - cat "$GIT_CACHE_FILE" - return + local fingerprint="$GIT_STATE_FINGERPRINT" + + if [[ -f "$GIT_CACHE_FILE" ]]; then + local cached_fp + IFS= read -r cached_fp < "$GIT_CACHE_FILE" + if [[ -n "$cached_fp" && "$cached_fp" == "$fingerprint" ]]; then + # Fingerprint unchanged → reuse cached rendered prompt + sed '1d' "$GIT_CACHE_FILE" + return + fi fi + # Recompute prompt and refresh cache local output - output=$(actual_git_prompt_info_logic) - echo "$output" > "$GIT_CACHE_FILE" + output=$(actual_git_prompt_info_logic) || return + + { + echo "$fingerprint" + echo "$output" + } > "$GIT_CACHE_FILE" + echo "$output" }