Improve git prompt performance

This commit is contained in:
Justin Hileman
2026-05-15 02:33:01 -04:00
parent 72a89d4246
commit 5f9fa8e3ab
9 changed files with 752 additions and 238 deletions

View File

@@ -208,6 +208,14 @@ This feature is disabled by default. Use `yes` to show the currently executing [
Use `no` to disable Git integration. If you're doing this for performance reasons, try some of the options below before disabling it entirely!
#### `set -g theme_display_git_async no`
Git status is rendered asynchronously by default in interactive shells. Bobthefish shows the prompt immediately, computes branch and status flags in the background, then repaints the prompt when the Git metadata is ready. Use `no` to restore synchronous Git rendering. Async rendering is currently disabled when `theme_git_worktree_support` is `yes`.
#### `set -g theme_git_async_refresh_interval 0`
Bobthefish waits at least one second between async Git refresh workers by default. If a prompt render is skipped during that interval, Bobthefish schedules one trailing refresh. Set this to `0` to check Git state on every prompt render.
#### `set -g theme_display_git_dirty no`
Use `no` to hide Git dirty state. Set the Git `bash.showDirtyState` option on a per-repository basis to disable it just for especially large repos.

View File

@@ -19,6 +19,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo green $colorfg
set -x color_repo_work_tree black $colorfg --bold
set -x color_repo_pending black $colorfg --bold
set -x color_repo_dirty brred $colorfg
set -x color_repo_staged yellow $colorfg
@@ -55,6 +56,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo green $colorfg
set -x color_repo_work_tree white $colorfg --bold
set -x color_repo_pending white $colorfg --bold
set -x color_repo_dirty brred $colorfg
set -x color_repo_staged yellow $colorfg
@@ -91,6 +93,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo green $colorfg
set -x color_repo_work_tree brgrey $colorfg --bold
set -x color_repo_pending brgrey $colorfg --bold
set -x color_repo_dirty brred $colorfg
set -x color_repo_staged yellow $colorfg
@@ -127,6 +130,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo green $colorfg
set -x color_repo_work_tree grey $colorfg --bold
set -x color_repo_pending grey $colorfg --bold
set -x color_repo_dirty brred $colorfg
set -x color_repo_staged yellow $colorfg
@@ -169,6 +173,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $grey
set -x color_repo_work_tree $grey $grey --bold
set -x color_repo_pending $grey $grey --bold
set -x color_repo_dirty $red $grey
set -x color_repo_staged $yellow $grey
@@ -222,6 +227,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $base0B $colorfg
set -x color_repo_work_tree $base06 $colorfg --bold
set -x color_repo_pending $base06 $colorfg --bold
set -x color_repo_dirty $base08 $colorfg
set -x color_repo_staged $base09 $colorfg
@@ -275,6 +281,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $base0B $colorfg
set -x color_repo_work_tree $base02 $colorfg --bold
set -x color_repo_pending $base02 $colorfg --bold
set -x color_repo_dirty $base08 $colorfg
set -x color_repo_staged $base09 $colorfg
@@ -328,6 +335,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $colorfg
set -x color_repo_work_tree $base2 $colorfg --bold
set -x color_repo_pending $base2 $colorfg --bold
set -x color_repo_dirty $red $colorfg
set -x color_repo_staged $yellow $colorfg
@@ -381,6 +389,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $colorfg
set -x color_repo_work_tree $base02 $colorfg --bold
set -x color_repo_pending $base02 $colorfg --bold
set -x color_repo_dirty $red $colorfg
set -x color_repo_staged $yellow $colorfg
@@ -427,6 +436,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green[1] $green[3]
set -x color_repo_work_tree $grey[1] $white --bold
set -x color_repo_pending $grey[1] $white --bold
set -x color_repo_dirty $red[2] $white
set -x color_repo_staged $orange[1] $orange[3]
@@ -472,6 +482,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green[2] $bg[1]
set -x color_repo_work_tree $bg[1] $fg[2] --bold
set -x color_repo_pending $bg[1] $fg[2] --bold
set -x color_repo_dirty $red[2] $fg[2]
set -x color_repo_staged $yellow[1] $bg[1]
@@ -516,7 +527,8 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_path_nowrite_basename $red[1] $bg[2] --bold
set -x color_repo $green[2] $fg[1]
set -x color_repo_work_tree $bg[1] $fg[2] --bold
set -x color_repo_work_tree $bg[1] $fg[2] --bold
set -x color_repo_pending $bg[1] $fg[2] --bold
set -x color_repo_dirty $orange[2] $bg[2]
set -x color_repo_staged $yellow[1] $bg[1]
@@ -562,6 +574,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $bg
set -x color_repo_work_tree $selection $fg --bold
set -x color_repo_pending $selection $fg --bold
set -x color_repo_dirty $red $bg
set -x color_repo_staged $yellow $bg
@@ -615,6 +628,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $base0E $colorfg
set -x color_repo_work_tree $base02 $colorfg --bold
set -x color_repo_pending $base02 $colorfg --bold
set -x color_repo_dirty $base0B $colorfg
set -x color_repo_staged $base0D $colorfg
@@ -675,6 +689,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $mantle
set -x color_repo_work_tree $surface2 $mantle --bold
set -x color_repo_pending $surface2 $mantle --bold
set -x color_repo_dirty $red $mantle
set -x color_repo_staged $yellow $mantle
@@ -736,6 +751,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $mantle
set -x color_repo_work_tree $surface2 $mantle --bold
set -x color_repo_pending $surface2 $mantle --bold
set -x color_repo_dirty $red $mantle
set -x color_repo_staged $yellow $mantle
@@ -797,6 +813,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $mantle
set -x color_repo_work_tree $surface2 $mantle --bold
set -x color_repo_pending $surface2 $mantle --bold
set -x color_repo_dirty $red $mantle
set -x color_repo_staged $yellow $mantle
@@ -858,6 +875,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $mantle
set -x color_repo_work_tree $surface2 $mantle --bold
set -x color_repo_pending $surface2 $mantle --bold
set -x color_repo_dirty $red $mantle
set -x color_repo_staged $yellow $mantle
@@ -917,7 +935,8 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_path_nowrite_basename $dark_grey $red_orange --bold
set -x color_repo $light_green $darker_grey
set -x color_repo_work_tree $dark_grey $light_grey --bold
set -x color_repo_work_tree $dark_grey $light_grey --bold
set -x color_repo_pending $dark_grey $light_grey --bold
set -x color_repo_dirty $red $darker_grey
set -x color_repo_staged $pale_gold $darker_grey
@@ -962,6 +981,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green[3] $white[4]
set -x color_repo_work_tree $grey[4] $grey[4] --bold
set -x color_repo_pending $grey[4] $grey[4] --bold
set -x color_repo_dirty $red[3] $grey[4]
set -x color_repo_staged $yellow[3] $grey[4]
@@ -1009,6 +1029,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green[1] $green[3]
set -x color_repo_work_tree $grey[3] $white --bold
set -x color_repo_pending $grey[3] $white --bold
set -x color_repo_dirty $red[2] $white
set -x color_repo_staged $orange[1] $orange[3]
@@ -1030,6 +1051,9 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_desk $blue[2] $grey[1] --bold
set -x color_nix $blue[2] $grey[1] --bold
end
set -q color_repo_pending
or set -x color_repo_pending $color_repo_work_tree
end
function __bobthefish_user_color_scheme_deprecated
@@ -1043,6 +1067,7 @@ function __bobthefish_user_color_scheme_deprecated
set -q __color_path_nowrite_basename; or set -l __color_path_nowrite_basename 660000 cc9999 --bold
set -q __color_repo; or set -l __color_repo addc10 0c4801
set -q __color_repo_work_tree; or set -l __color_repo_work_tree 333333 ffffff --bold
set -q __color_repo_pending; or set -l __color_repo_pending 333333 ffffff --bold
set -q __color_repo_dirty; or set -l __color_repo_dirty ce000f ffffff
set -q __color_repo_staged; or set -l __color_repo_staged f6b117 3a2a03
set -q __color_vi_mode_default; or set -l __color_vi_mode_default 999999 333333 --bold
@@ -1081,6 +1106,7 @@ function __bobthefish_user_color_scheme_deprecated
set -x color_path_nowrite_basename $__color_path_nowrite_basename
set -x color_repo $__color_repo
set -x color_repo_work_tree $__color_repo_work_tree
set -x color_repo_pending $__color_repo_pending
set -x color_repo_dirty $__color_repo_dirty
set -x color_repo_staged $__color_repo_staged
set -x color_vi_mode_default $__color_vi_mode_default

View File

@@ -0,0 +1,210 @@
function __bobthefish_git_async_enabled -S -d 'Check whether async git prompt updates should be used'
if [ "$argv[1]" = --init ]
if status is-interactive; and __bobthefish_version_at_least 3.0.0 $version
# The git metadata var must be universal: the detached background fish
# writes it, and a universal variable is the only channel that can deliver
# a value back into this (the parent) shell. It's namespaced by pid so
# concurrent sessions don't clobber each other.
set -g __bobthefish_git_async_var __bobthefish_git_async_$fish_pid
set -U $__bobthefish_git_async_var
# Fires when the background worker publishes new metadata. The pending
# flag tells the next fish_prompt that this render is the async repaint
# (so it must not spawn another worker).
function __bobthefish_git_async_repaint --on-variable $__bobthefish_git_async_var
set -g __bobthefish_git_async_repaint_pending 1
commandline --function repaint
end
function __bobthefish_git_async_cleanup --on-event fish_exit
set -q __bobthefish_git_async_pid
and command kill $__bobthefish_git_async_pid 2>/dev/null
set -q __bobthefish_git_async_trailing_pid
and command kill $__bobthefish_git_async_trailing_pid 2>/dev/null
set -q __bobthefish_git_async_var
and set -e $__bobthefish_git_async_var
end
__bobthefish_git_async_gc
end
# The git metadata var must be universal: the detached background fish
# writes it, and a universal variable is the only channel that can deliver
# a value back into this (the parent) shell. It's namespaced by pid so
# concurrent sessions don't clobber each other.
set -g __bobthefish_git_async_var __bobthefish_git_async_$fish_pid
set -U $__bobthefish_git_async_var
# Fires when the background worker publishes new metadata. The pending
# flag tells the next fish_prompt that this render is the async repaint
# (so it must not spawn another worker).
function __bobthefish_git_async_repaint --on-variable $__bobthefish_git_async_var
set -g __bobthefish_git_async_repaint_pending 1
commandline --function repaint
end
function __bobthefish_git_async_cleanup --on-event fish_exit
set -q __bobthefish_git_async_pid
and command kill $__bobthefish_git_async_pid 2>/dev/null
set -q __bobthefish_git_async_trailing_pid
and command kill $__bobthefish_git_async_trailing_pid 2>/dev/null
set -q __bobthefish_git_async_poll_pid
and command kill $__bobthefish_git_async_poll_pid 2>/dev/null
set -q __bobthefish_git_async_var
and set -e $__bobthefish_git_async_var
end
__bobthefish_git_async_gc
end
function __bobthefish_git_async_enabled -S -d 'Check whether async git prompt updates should be used'
[ "$theme_display_git_async" = no ]
and return 1
[ "$theme_git_worktree_support" = yes ]
and return 1
__bobthefish_version_at_least 3.0.0 $version
and status is-interactive
and set -q __bobthefish_git_async_var
and command -q fish
and __bobthefish_git_async_worker_path >/dev/null
end
function __bobthefish_git_async_worker_path -S -d 'Resolve and cache the async git worker file'
set -q __bobthefish_git_async_worker_path_cache
and echo $__bobthefish_git_async_worker_path_cache
and return
set -l prompt_path (functions --details fish_prompt)
[ -f "$prompt_path" ]
or return
set -l resolved (dirname "$prompt_path")/__bobthefish_git_async_worker.fish
[ -f "$resolved" ]
or return
set -g __bobthefish_git_async_worker_path_cache $resolved
echo $resolved
end
function __bobthefish_git_async_fish -S -d 'Resolve and cache the fish binary used for async git workers'
set -q __bobthefish_git_async_fish_path
or set -g __bobthefish_git_async_fish_path (command -s fish)
echo $__bobthefish_git_async_fish_path
end
function __bobthefish_git_async_gc -S -d 'Remove async git metadata left behind by dead fish sessions'
set -l prefix __bobthefish_git_async_
for var in (set --names --universal | string match "$prefix*")
[ "$var" = "$__bobthefish_git_async_var" ]
and continue
set -l pid (string replace $prefix '' -- $var)
string match -qr '^\d+$' -- "$pid"
or continue
command kill -0 $pid 2>/dev/null
or set -e $var
end
end
function __bobthefish_git_async_refresh_interval -S -d 'Get the minimum seconds between async git workers'
set -l interval 1
string match -qr '^\d+$' -- "$theme_git_async_refresh_interval"
and set interval $theme_git_async_refresh_interval
echo $interval
end
function __bobthefish_git_async_schedule_trailing -S -a git_root_dir -a delay -d 'Schedule a trailing async git refresh'
[ "$delay" -gt 0 ]
or return
set -q __bobthefish_git_async_var
or return
set -l worker_path (__bobthefish_git_async_worker_path)
or return
set -l fish_bin (__bobthefish_git_async_fish)
if set -q __bobthefish_git_async_trailing_pid
command kill -0 $__bobthefish_git_async_trailing_pid 2>/dev/null
and [ "$__bobthefish_git_async_trailing_root_dir" = "$git_root_dir" ]
and return
__bobthefish_git_async_clear_trailing
end
command $fish_bin --private -c '
sleep $argv[1]
source $argv[2]
__bobthefish_git_async_worker $argv[3] $argv[4] $argv[5] $argv[6] $argv[7] $argv[8]
' $delay $worker_path $__bobthefish_git_async_var $git_root_dir "$theme_display_git_dirty" "$theme_display_git_dirty_verbose" "$theme_display_git_untracked" "$theme_display_git_stashed_verbose" &
builtin -q disown
and builtin disown
set -g __bobthefish_git_async_trailing_pid $last_pid
set -g __bobthefish_git_async_trailing_root_dir $git_root_dir
end
function __bobthefish_git_async_start -S -a git_root_dir -d 'Start a background git prompt metadata update'
set -q __bobthefish_git_async_var
or return
# Keep at most one worker active per prompt session. Re-rendering while a
# worker is still collecting git state should not kill the worker that will
# publish the fresh cache.
if set -q __bobthefish_git_async_pid
if command kill -0 $__bobthefish_git_async_pid 2>/dev/null
[ "$__bobthefish_git_async_root_dir" = "$git_root_dir" ]
and return
command kill $__bobthefish_git_async_pid 2>/dev/null
end
set -e __bobthefish_git_async_pid
end
if set -q __bobthefish_git_async_trailing_pid
if [ "$__bobthefish_git_async_trailing_root_dir" != "$git_root_dir" ]
__bobthefish_git_async_clear_trailing
else if not command kill -0 $__bobthefish_git_async_trailing_pid 2>/dev/null
set -e __bobthefish_git_async_trailing_pid
end
end
set -l now (date +%s)
set -l interval (__bobthefish_git_async_refresh_interval)
# Git state can change inside a repo between prompts, so this is a short
# debounce rather than a same-repo cache. Set theme_git_async_refresh_interval
# to 0 to spawn a worker on every eligible prompt render.
if [ "$interval" -gt 0 -a "$__bobthefish_git_async_root_dir" = "$git_root_dir" -a -n "$__bobthefish_git_async_last_start" ]
set -l elapsed (math $now - $__bobthefish_git_async_last_start)
if [ "$elapsed" -lt "$interval" ]
__bobthefish_git_async_schedule_trailing $git_root_dir (math $interval - $elapsed)
return
end
end
__bobthefish_git_async_clear_trailing
set -l worker_path (__bobthefish_git_async_worker_path)
or return
set -l fish_bin (__bobthefish_git_async_fish)
set -l cmd source (string escape -- $worker_path) ';' __bobthefish_git_async_worker \
(string escape -- $__bobthefish_git_async_var) \
(string escape -- $git_root_dir) \
(string escape -- "$theme_display_git_dirty") \
(string escape -- "$theme_display_git_dirty_verbose") \
(string escape -- "$theme_display_git_untracked") \
(string escape -- "$theme_display_git_stashed_verbose")
set -g __bobthefish_git_async_root_dir $git_root_dir
set -g __bobthefish_git_async_last_start $now
command $fish_bin --private --command "$cmd" &
builtin -q disown
and builtin disown
set -g __bobthefish_git_async_pid $last_pid
end

View File

@@ -0,0 +1,110 @@
# __bobthefish_git_async_counts / __bobthefish_git_async_dirty_verbose are
# deliberate copies of __bobthefish_git_counts / __bobthefish_git_dirty_verbose
# from __bobthefish_prompt_git_sync.fish. The worker runs in a non-interactive
# `fish --private -c` that bootstraps by sourcing only this file, so it must not
# rely on the theme's autoload path being configured (some shells load omf for
# interactive sessions only). Keep these in sync with their sync counterparts.
function __bobthefish_git_async_counts -S -d 'Print behind and ahead counts for the current branch'
set -l counts (command git rev-list --left-right --count '@{upstream}...HEAD' 2>/dev/null)
or return
set counts (string split \t -- $counts)
if [ (count $counts) -lt 2 ]
set counts (string split ' ' -- $counts)
end
[ (count $counts) -ge 2 ]
or return
printf '%s\n%s\n' $counts[1] $counts[2]
end
function __bobthefish_git_async_dirty_verbose -S -d 'Print a more verbose dirty state for the current working tree'
set -l diff_stats (command git diff --numstat)
or return
set -l added 0
set -l removed 0
for line in $diff_stats
set -l fields (string split \t -- $line)
string match -rq '^\d+$' -- $fields[1]
and set added (math $added + $fields[1])
string match -rq '^\d+$' -- $fields[2]
and set removed (math $removed + $fields[2])
end
set -l changes "+$added/-$removed"
echo "$changes " | string replace -r '(\+0/(-0)?|/-0)' ''
end
function __bobthefish_git_async_worker -S -a git_async_var -a git_root_dir -a display_dirty -a display_dirty_verbose -a display_untracked -a display_stashed_verbose -d 'Compute git prompt metadata in a background fish'
set -l tag (command git describe --tags --exact-match 2>/dev/null)
set -l branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
set -l detached
[ -z "$tag" -a -z "$branch" ]
and set detached (command git rev-parse --short HEAD 2>/dev/null)
# Only resolve the configured default branch when it's actually needed
set -l configured_default_branch
[ -n "$branch" -a -z "$theme_git_default_branches" ]
and set configured_default_branch (command git config init.defaultBranch 2>/dev/null)
set -l dirty
set -l dirty_stats
if [ "$display_dirty" != no ]
set -l show_dirty (command git config --bool bash.showDirtyState 2>/dev/null)
if [ "$show_dirty" != false ]
command git diff --no-ext-diff --quiet --exit-code 2>/dev/null
or begin
set dirty 1
[ "$display_dirty_verbose" = yes ]
and set dirty_stats (__bobthefish_git_async_dirty_verbose)
end
end
end
set -l staged
command git diff --cached --no-ext-diff --quiet --exit-code 2>/dev/null
or set staged 1
set -l stashed
if [ "$display_stashed_verbose" = yes ]
set stashed (command git rev-list --walk-reflogs --count refs/stash 2>/dev/null)
or set stashed
else
command git rev-parse --verify --quiet refs/stash >/dev/null
and set stashed 1
end
set -l ahead 0
set -l behind 0
set -l counts (__bobthefish_git_async_counts)
and begin
set behind $counts[1]
set ahead $counts[2]
end
set -l new
if [ "$display_untracked" != no ]
set -l show_untracked (command git config --bool bash.showUntrackedFiles 2>/dev/null)
if [ "$show_untracked" != false ]
set new (command git ls-files --other --exclude-standard --directory --no-empty-directory "$git_root_dir" 2>/dev/null)
[ "$new" ]
and set new 1
end
end
# Field order is a contract shared with __bobthefish_prompt_git_async,
# which unpacks these by position. Keep the two in sync.
set -l next "$git_root_dir" "$tag" "$branch" "$detached" "$dirty" "$dirty_stats" "$staged" "$stashed" "$ahead" "$behind" "$new" "$configured_default_branch"
# Skip the universal-variable write (disk I/O + a repaint broadcast to
# every fish session) when nothing actually changed since the last run.
set -l current $$git_async_var
set -l next_key (string join \x1e -- $next)
set -l current_key (string join \x1e -- $current)
[ "$next_key" = "$current_key" ]
and return
set -U $git_async_var $next
end

View File

@@ -0,0 +1,25 @@
function __bobthefish_git_branch_from_parts -S -a tag -a branch -a detached -a configured_default_branch -d 'Format a git branch from git metadata'
[ -n "$tag" ]
and echo "$tag_glyph $tag "
if [ -n "$branch" ]
[ -n "$theme_git_default_branches" ]
or set -l theme_git_default_branches master main $configured_default_branch
[ "$theme_display_git_master_branch" != yes -a "$theme_display_git_default_branch" != yes ]
and contains $branch $theme_git_default_branches
and echo $branch_glyph
and return
set -l truncname $branch
[ "$theme_use_abbreviated_branch_name" = yes ]
and set truncname (string replace -r '^(.{17}).{3,}(.{5})$' "\$1…\$2" $branch)
echo $branch_glyph $truncname
and return
end
if [ -z "$tag" -a -n "$detached" ]
echo "$detached_glyph $detached"
end
end

View File

@@ -0,0 +1,137 @@
function __bobthefish_git_count_or_zero -S -a count -d 'Normalize a git count for numeric comparisons'
string match -qr '^\d+$' -- "$count"
and echo $count
or echo 0
end
function __bobthefish_git_pending_branch -S -a git_root_dir -d 'Get cheap branch metadata while async git state is pending'
# The placeholder is only shown until the worker first publishes for this
# repo. Caching by root keeps rapid renders (e.g. holding Enter in a fresh
# repo) from re-forking git on every prompt during that brief window.
if set -q __bobthefish_git_pending_root
and [ "$__bobthefish_git_pending_root" = "$git_root_dir" ]
echo $__bobthefish_git_pending_branch_cache
return
end
set -l rendered
set -l branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
if [ -n "$branch" ]
# Keep the common branch path cheap. We intentionally do not call
# `git config init.defaultBranch` here; the worker will apply the full
# default-branch rules when it publishes complete metadata.
set rendered (__bobthefish_git_branch_from_parts '' "$branch" '')
else
set -l detached (command git rev-parse --short HEAD 2>/dev/null)
[ -n "$detached" ]
and set rendered (__bobthefish_git_branch_from_parts '' '' "$detached")
end
set -g __bobthefish_git_pending_root $git_root_dir
set -g __bobthefish_git_pending_branch_cache $rendered
echo $rendered
end
function __bobthefish_prompt_git_pending -S -a git_root_dir -a real_pwd -d 'Display an indeterminate repo segment while async git state is pending'
__bobthefish_path_segment $git_root_dir project
__bobthefish_start_segment $color_repo_pending
echo -ns (__bobthefish_git_pending_branch $git_root_dir) ' '
set_color normal
__bobthefish_prompt_git_pwd $git_root_dir $real_pwd
end
function __bobthefish_prompt_git_async -S -a git_root_dir -a real_pwd -a repaint -d 'Display git state from cached async metadata'
if [ "$repaint" != 1 ]
__bobthefish_git_async_start $git_root_dir
end
# Field order is the contract published by __bobthefish_git_async_worker.
set -l git_data $$__bobthefish_git_async_var
if [ "$git_data[1]" != "$git_root_dir" ]
if [ "$repaint" = 1 ]
__bobthefish_git_async_start $git_root_dir
end
set -l budget_ms (__bobthefish_git_async_first_render_budget_ms)
set -l waited_ms 0
while [ "$budget_ms" -gt 0 -a "$waited_ms" -lt "$budget_ms" ]
sleep 0.01
set git_data $$__bobthefish_git_async_var
[ "$git_data[1]" = "$git_root_dir" ]
and break
set waited_ms (math $waited_ms + 10)
end
end
if [ "$git_data[1]" != "$git_root_dir" ]
__bobthefish_prompt_git_pending $git_root_dir $real_pwd
return
end
set -l tag $git_data[2]
set -l branch $git_data[3]
set -l detached $git_data[4]
set -l dirty $git_data[5]
set -l dirty_stats $git_data[6]
set -l staged $git_data[7]
set -l stashed $git_data[8]
set -l ahead (__bobthefish_git_count_or_zero $git_data[9])
set -l behind (__bobthefish_git_count_or_zero $git_data[10])
set -l new $git_data[11]
set -l configured_default_branch $git_data[12]
[ "$dirty" ]
and set dirty "$git_dirty_glyph$dirty_stats"
[ "$staged" ]
and set staged "$git_staged_glyph"
if [ "$stashed" ]
[ "$theme_display_git_stashed_verbose" = yes ]
and set stashed "$git_stashed_glyph$stashed"
or set stashed "$git_stashed_glyph"
end
set -l ahead_flag
set -l verbose
[ "$theme_display_git_ahead_verbose" = yes ]
and set verbose 1
switch "$ahead $behind"
case '0 0' # equal to upstream
case '* 0' # ahead of upstream
[ -n "$verbose" ]
and set ahead_flag "$git_ahead_glyph$ahead"
or set ahead_flag "$git_plus_glyph"
case '0 *' # behind upstream
[ -n "$verbose" ]
and set ahead_flag "$git_behind_glyph$behind"
or set ahead_flag "$git_minus_glyph"
case '*' # diverged from upstream
[ -n "$verbose" ]
and set ahead_flag "$git_ahead_glyph$ahead$git_behind_glyph$behind"
or set ahead_flag "$git_plus_minus_glyph"
end
[ "$new" ]
and set new "$git_untracked_glyph"
set -l flags "$dirty$staged$stashed$ahead_flag$new"
[ "$flags" ]
and set flags " $flags"
set -l flag_colors $color_repo
if [ "$dirty" ]
set flag_colors $color_repo_dirty
else if [ "$staged" ]
set flag_colors $color_repo_staged
end
__bobthefish_path_segment $git_root_dir project
__bobthefish_start_segment $flag_colors
echo -ns (__bobthefish_git_branch_from_parts "$tag" "$branch" "$detached" "$configured_default_branch") $flags ' '
set_color normal
__bobthefish_prompt_git_pwd $git_root_dir $real_pwd
end

View File

@@ -0,0 +1,13 @@
function __bobthefish_prompt_git_pwd -S -a git_root_dir -a real_pwd -d 'Display the in-project path segment'
set -l project_pwd (__bobthefish_project_pwd $git_root_dir $real_pwd)
[ "$project_pwd" ]
or return
if [ -w "$real_pwd" ]
__bobthefish_start_segment $color_path
else
__bobthefish_start_segment $color_path_nowrite
end
echo -ns $project_pwd ' '
end

View File

@@ -0,0 +1,212 @@
function __bobthefish_git_counts -S -d 'Print behind and ahead counts for the current branch'
set -l counts (command git rev-list --left-right --count '@{upstream}...HEAD' 2>/dev/null)
or return
set counts (string split \t -- $counts)
if [ (count $counts) -lt 2 ]
set counts (string split ' ' -- $counts)
end
[ (count $counts) -ge 2 ]
or return
printf '%s\n%s\n' $counts[1] $counts[2]
end
function __bobthefish_git_branch -S -d 'Get the current git branch (or commitish)'
set -l tag (command git describe --tags --exact-match 2>/dev/null)
set -l branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
set -l detached
[ -z "$tag" -a -z "$branch" ]
and set detached (command git rev-parse --short HEAD 2>/dev/null)
# Only resolve the configured default branch when it's actually needed
set -l configured_default_branch
[ -n "$branch" -a -z "$theme_git_default_branches" ]
and set configured_default_branch (command git config init.defaultBranch 2>/dev/null)
__bobthefish_git_branch_from_parts "$tag" "$branch" "$detached" "$configured_default_branch"
end
function __bobthefish_git_ahead -S -d 'Print the ahead/behind state for the current branch'
if [ "$theme_display_git_ahead_verbose" = yes ]
__bobthefish_git_ahead_verbose
return
end
set -l counts (__bobthefish_git_counts)
or return
set -l behind $counts[1]
set -l ahead $counts[2]
if [ $ahead -gt 0 -a $behind -gt 0 ]
echo "$git_plus_minus_glyph"
else if [ $ahead -gt 0 ]
echo "$git_plus_glyph"
else if [ $behind -gt 0 ]
echo "$git_minus_glyph"
end
end
function __bobthefish_git_ahead_verbose -S -d 'Print a more verbose ahead/behind state for the current branch'
set -l counts (__bobthefish_git_counts)
or return
set -l behind $counts[1]
set -l ahead $counts[2]
switch "$ahead $behind"
case '' # no upstream
case '0 0' # equal to upstream
return
case '* 0' # ahead of upstream
echo "$git_ahead_glyph$ahead"
case '0 *' # behind upstream
echo "$git_behind_glyph$behind"
case '*' # diverged from upstream
echo "$git_ahead_glyph$ahead$git_behind_glyph$behind"
end
end
function __bobthefish_git_dirty_verbose -S -d 'Print a more verbose dirty state for the current working tree'
set -l diff_stats (command git diff --numstat)
or return
set -l added 0
set -l removed 0
for line in $diff_stats
set -l fields (string split \t -- $line)
string match -rq '^\d+$' -- $fields[1]
and set added (math $added + $fields[1])
string match -rq '^\d+$' -- $fields[2]
and set removed (math $removed + $fields[2])
end
set -l changes "+$added/-$removed"
echo "$changes " | string replace -r '(\+0/(-0)?|/-0)' ''
end
function __bobthefish_git_stashed -S -d 'Print the stashed state for the current branch'
if [ "$theme_display_git_stashed_verbose" = yes ]
set -l stashed (command git rev-list --walk-reflogs --count refs/stash 2>/dev/null)
or return
echo -n "$git_stashed_glyph$stashed"
else
command git rev-parse --verify --quiet refs/stash >/dev/null
and echo -n "$git_stashed_glyph"
end
end
function __bobthefish_prompt_git_sync -S -a git_root_dir -a real_pwd -d 'Display the actual git state synchronously'
set -l dirty ''
if [ "$theme_display_git_dirty" != no ]
set -l show_dirty (command git config --bool bash.showDirtyState 2>/dev/null)
if [ "$show_dirty" != false ]
set dirty (command git diff --no-ext-diff --quiet --exit-code 2>/dev/null; or echo -n "$git_dirty_glyph")
if [ "$dirty" -a "$theme_display_git_dirty_verbose" = yes ]
set dirty "$dirty"(__bobthefish_git_dirty_verbose)
end
end
end
set -l staged (command git diff --cached --no-ext-diff --quiet --exit-code 2>/dev/null; or echo -n "$git_staged_glyph")
set -l stashed (__bobthefish_git_stashed)
set -l ahead (__bobthefish_git_ahead)
set -l new ''
if [ "$theme_display_git_untracked" != no ]
set -l show_untracked (command git config --bool bash.showUntrackedFiles 2>/dev/null)
if [ "$show_untracked" != false ]
set new (command git ls-files --other --exclude-standard --directory --no-empty-directory "$git_root_dir" 2>/dev/null)
if [ "$new" ]
set new "$git_untracked_glyph"
end
end
end
set -l flags "$dirty$staged$stashed$ahead$new"
[ "$flags" ]
and set flags " $flags"
set -l flag_colors $color_repo
if [ "$dirty" ]
set flag_colors $color_repo_dirty
else if [ "$staged" ]
set flag_colors $color_repo_staged
end
__bobthefish_path_segment $git_root_dir project
__bobthefish_start_segment $flag_colors
echo -ns (__bobthefish_git_branch) $flags ' '
set_color normal
if [ "$theme_git_worktree_support" != yes ]
__bobthefish_prompt_git_pwd $git_root_dir $real_pwd
return
end
set -l project_pwd (command git rev-parse --show-prefix 2>/dev/null | string trim --right --chars=/)
set -l work_dir (command git rev-parse --show-toplevel 2>/dev/null)
# only show work dir if it's a parent…
if [ "$work_dir" ]
switch $real_pwd/
case $work_dir/\*
string match "$git_root_dir*" $work_dir >/dev/null
and set work_dir (string sub -s (math 1 + (string length $git_root_dir)) $work_dir)
case \*
set -e work_dir
end
end
if [ "$project_pwd" -o "$work_dir" ]
set -l colors $color_path
if not [ -w "$real_pwd" ]
set colors $color_path_nowrite
end
__bobthefish_start_segment $colors
# handle work_dir != project dir
if [ "$work_dir" ]
set -l work_parent (__bobthefish_dirname $work_dir)
if [ "$work_parent" ]
echo -n "$work_parent/"
end
set_color normal
set_color -b $color_repo_work_tree
echo -n (__bobthefish_basename $work_dir)
set_color normal
set_color -b $colors
[ "$project_pwd" ]
and echo -n /
end
echo -ns $project_pwd ' '
else
set project_pwd $real_pwd
string match "$git_root_dir*" $project_pwd >/dev/null
and set project_pwd (string sub -s (math 1 + (string length $git_root_dir)) $project_pwd)
set project_pwd (string trim --left --chars=/ -- $project_pwd)
if [ "$project_pwd" ]
set -l colors $color_path
if not [ -w "$real_pwd" ]
set colors $color_path_nowrite
end
__bobthefish_start_segment $colors
echo -ns $project_pwd ' '
end
end
end

View File

@@ -65,35 +65,7 @@ function __bobthefish_version_at_least -S -a required -a actual -d 'Compare dott
return 0
end
function __bobthefish_git_branch -S -d 'Get the current git branch (or commitish)'
set -l tag (command git describe --tags --exact-match 2>/dev/null)
and echo "$tag_glyph $tag "
set -l branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
and begin
[ -n "$theme_git_default_branches" ]
or set -l theme_git_default_branches master main (command git config init.defaultBranch 2>/dev/null)
[ "$theme_display_git_master_branch" != yes -a "$theme_display_git_default_branch" != yes ]
and contains $branch $theme_git_default_branches
and echo $branch_glyph
and return
# truncate the middle of the branch name, but only if it's 25+ characters
set -l truncname $branch
[ "$theme_use_abbreviated_branch_name" = yes ]
and set truncname (string replace -r '^(.{17}).{3,}(.{5})$' "\$1…\$2" $branch)
echo $branch_glyph $truncname
and return
end
# If we've already shown a tag we don't need to show a detached branch
if [ -z "$tag" ]
set -l branch (command git rev-parse --short HEAD 2>/dev/null)
echo "$detached_glyph $branch"
end
end
__bobthefish_git_async_init
function __bobthefish_fossil_branch -S -d 'Get the current fossil branch'
set -l branch (command fossil branch 2>/dev/null | string trim --left --chars=' *')
@@ -271,96 +243,6 @@ function __bobthefish_project_pwd -S -a project_root_dir -a real_pwd -d 'Print t
string replace -ar '(\.?[^/]{'"$theme_project_dir_length"'})[^/]*/' '$1/' $project_dir
end
function __bobthefish_git_ahead -S -d 'Print the ahead/behind state for the current branch'
if [ "$theme_display_git_ahead_verbose" = yes ]
__bobthefish_git_ahead_verbose
return
end
set -l ahead 0
set -l behind 0
for line in (command git rev-list --left-right '@{upstream}...HEAD' 2>/dev/null)
switch "$line"
case '>*'
if [ $behind -eq 1 ]
echo '±'
return
end
set ahead 1
case '<*'
if [ $ahead -eq 1 ]
echo "$git_plus_minus_glyph"
return
end
set behind 1
end
end
if [ $ahead -eq 1 ]
echo "$git_plus_glyph"
else if [ $behind -eq 1 ]
echo "$git_minus_glyph"
end
end
function __bobthefish_git_ahead_verbose -S -d 'Print a more verbose ahead/behind state for the current branch'
set -l commits (command git rev-list --left-right '@{upstream}...HEAD' 2>/dev/null)
or return
set -l behind 0
set -l ahead 0
for commit in $commits
switch "$commit"
case '<*'
set behind (math $behind + 1)
case '>*'
set ahead (math $ahead + 1)
end
end
switch "$ahead $behind"
case '' # no upstream
case '0 0' # equal to upstream
return
case '* 0' # ahead of upstream
echo "$git_ahead_glyph$ahead"
case '0 *' # behind upstream
echo "$git_behind_glyph$behind"
case '*' # diverged from upstream
echo "$git_ahead_glyph$ahead$git_behind_glyph$behind"
end
end
function __bobthefish_git_dirty_verbose -S -d 'Print a more verbose dirty state for the current working tree'
set -l diff_stats (command git diff --numstat)
or return
set -l added 0
set -l removed 0
for line in $diff_stats
set -l fields (string split \t -- $line)
string match -rq '^\d+$' -- $fields[1]
and set added (math $added + $fields[1])
string match -rq '^\d+$' -- $fields[2]
and set removed (math $removed + $fields[2])
end
set -l changes "+$added/-$removed"
echo "$changes " | string replace -r '(\+0/(-0)?|/-0)' ''
end
function __bobthefish_git_stashed -S -d 'Print the stashed state for the current branch'
if [ "$theme_display_git_stashed_verbose" = yes ]
set -l stashed (command git rev-list --walk-reflogs --count refs/stash 2>/dev/null)
or return
echo -n "$git_stashed_glyph$stashed"
else
command git rev-parse --verify --quiet refs/stash >/dev/null
and echo -n "$git_stashed_glyph"
end
end
# ==============================
# Segment functions
# ==============================
@@ -1179,123 +1061,11 @@ function __bobthefish_prompt_screen -S -d 'Display the screen name'
set_color normal
end
function __bobthefish_prompt_git -S -a git_root_dir -a real_pwd -d 'Display the actual git state'
set -l dirty ''
if [ "$theme_display_git_dirty" != no ]
set -l show_dirty (command git config --bool bash.showDirtyState 2>/dev/null)
if [ "$show_dirty" != false ]
set dirty (command git diff --no-ext-diff --quiet --exit-code 2>/dev/null; or echo -n "$git_dirty_glyph")
if [ "$dirty" -a "$theme_display_git_dirty_verbose" = yes ]
set dirty "$dirty"(__bobthefish_git_dirty_verbose)
end
end
end
set -l staged (command git diff --cached --no-ext-diff --quiet --exit-code 2>/dev/null; or echo -n "$git_staged_glyph")
set -l stashed (__bobthefish_git_stashed)
set -l ahead (__bobthefish_git_ahead)
set -l new ''
if [ "$theme_display_git_untracked" != no ]
set -l show_untracked (command git config --bool bash.showUntrackedFiles 2>/dev/null)
if [ "$show_untracked" != false ]
set new (command git ls-files --other --exclude-standard --directory --no-empty-directory "$git_root_dir" 2>/dev/null)
if [ "$new" ]
set new "$git_untracked_glyph"
end
end
end
set -l flags "$dirty$staged$stashed$ahead$new"
[ "$flags" ]
and set flags " $flags"
set -l flag_colors $color_repo
if [ "$dirty" ]
set flag_colors $color_repo_dirty
else if [ "$staged" ]
set flag_colors $color_repo_staged
end
__bobthefish_path_segment $git_root_dir project
__bobthefish_start_segment $flag_colors
echo -ns (__bobthefish_git_branch) $flags ' '
set_color normal
if [ "$theme_git_worktree_support" != yes ]
set -l project_pwd (__bobthefish_project_pwd $git_root_dir $real_pwd)
if [ "$project_pwd" ]
if [ -w "$real_pwd" ]
__bobthefish_start_segment $color_path
else
__bobthefish_start_segment $color_path_nowrite
end
echo -ns $project_pwd ' '
end
return
end
set -l project_pwd (command git rev-parse --show-prefix 2>/dev/null | string trim --right --chars=/)
set -l work_dir (command git rev-parse --show-toplevel 2>/dev/null)
# only show work dir if it's a parent…
if [ "$work_dir" ]
switch $real_pwd/
case $work_dir/\*
string match "$git_root_dir*" $work_dir >/dev/null
and set work_dir (string sub -s (math 1 + (string length $git_root_dir)) $work_dir)
case \*
set -e work_dir
end
end
if [ "$project_pwd" -o "$work_dir" ]
set -l colors $color_path
if not [ -w "$real_pwd" ]
set colors $color_path_nowrite
end
__bobthefish_start_segment $colors
# handle work_dir != project dir
if [ "$work_dir" ]
set -l work_parent (__bobthefish_dirname $work_dir)
if [ "$work_parent" ]
echo -n "$work_parent/"
end
set_color normal
set_color -b $color_repo_work_tree
echo -n (__bobthefish_basename $work_dir)
set_color normal
set_color -b $colors
[ "$project_pwd" ]
and echo -n /
end
echo -ns $project_pwd ' '
function __bobthefish_prompt_git -S -a git_root_dir -a real_pwd -a repaint -d 'Display the actual git state'
if __bobthefish_git_async_enabled
__bobthefish_prompt_git_async $git_root_dir $real_pwd $repaint
else
set project_pwd $real_pwd
string match "$git_root_dir*" $project_pwd >/dev/null
and set project_pwd (string sub -s (math 1 + (string length $git_root_dir)) $project_pwd)
set project_pwd (string trim --left --chars=/ -- $project_pwd)
if [ "$project_pwd" ]
set -l colors $color_path
if not [ -w "$real_pwd" ]
set colors $color_path_nowrite
end
__bobthefish_start_segment $colors
echo -ns $project_pwd ' '
end
__bobthefish_prompt_git_sync $git_root_dir $real_pwd
end
end
@@ -1328,6 +1098,9 @@ end
function fish_prompt -d 'bobthefish, a fish theme optimized for awesome'
# Save the last status for later (do this before anything else)
set -l last_status $status
set -l git_async_repaint
set -e __bobthefish_git_async_repaint_pending
and set git_async_repaint 1
# Use a simple prompt on dumb terminals.
if [ "$TERM" = dumb ]
@@ -1382,7 +1155,7 @@ function fish_prompt -d 'bobthefish, a fish theme optimized for awesome'
case ''
__bobthefish_prompt_dir $real_pwd
case "$git_root_dir"
__bobthefish_prompt_git $git_root_dir $real_pwd
__bobthefish_prompt_git $git_root_dir $real_pwd $git_async_repaint
case "$hg_root_dir"
__bobthefish_prompt_hg $hg_root_dir $real_pwd
case "$fossil_root_dir"