prompt-git.bash (11331B)
1 # blesh/contrib/prompt-git.bash (C) 2020-2021, akinomyoga 2 3 # bleopt prompt_rps1='\q{contrib/git-info}' 4 # bleopt prompt_rps1='\q{contrib/git-name}' 5 # bleopt prompt_rps1='\q{contrib/git-hash}' 6 # bleopt prompt_rps1='\q{contrib/git-branch}' 7 # bleopt prompt_rps1='\q{contrib/git-path}' 8 9 ble-import contrib/prompt-defer 10 11 #------------------------------------------------------------------------------ 12 13 _ble_contrib_prompt_git_data=() 14 _ble_contrib_prompt_git_base= 15 _ble_contrib_prompt_git_base_dir= 16 _ble_contrib_prompt_git_vars=(git_base git_base_dir hash branch) 17 18 ## @fn ble/contrib/prompt-git/.check-gitdir path 19 ## @var[out] git_base git_base_dir 20 function ble/contrib/prompt-git/.check-gitdir { 21 local path=$1 22 [[ -f $path/.git/HEAD ]] || return 1 23 ble/prompt/unit/assign _ble_contrib_prompt_git_base "$path" 24 ble/prompt/unit/assign _ble_contrib_prompt_git_base_dir "$path/.git" 25 return 0 26 } 27 ## @fn ble/contrib/prompt-git/.check-submodule path 28 ## @var[out] git_base git_base_dir 29 function ble/contrib/prompt-git/.check-submodule { 30 local path=$1 content 31 [[ -f $path/.git ]] || return 1 32 ble/util/mapfile content < "$path/.git" 33 [[ $content == 'gitdir:'* ]] || return 1 34 local git_base=$path 35 local git_base_dir=${content#'gitdir:'} 36 git_base_dir=${git_base_dir#' '} 37 [[ $git_base_dir == /* ]] || 38 git_base_dir=$path/$git_base_dir 39 [[ -f $git_base_dir/HEAD ]] 40 ble/prompt/unit/assign _ble_contrib_prompt_git_base "$git_base" 41 ble/prompt/unit/assign _ble_contrib_prompt_git_base_dir "$git_base_dir" 42 return 0 43 } 44 function ble/prompt/unit:_ble_contrib_prompt_git/update { 45 ble/prompt/unit/add-hash '$PWD' 46 47 type git &>/dev/null || return 1 48 # [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) ]] 49 local path=$PWD found= 50 while 51 if ble/contrib/prompt-git/.check-gitdir "$path"; then 52 [[ $prompt_unit_changed ]] 53 return "$?" 54 elif ble/contrib/prompt-git/.check-submodule "$path"; then 55 [[ $prompt_unit_changed ]] 56 return "$?" 57 fi 58 [[ $path == */* ]] 59 do path=${path%/*}; done 60 61 ble/prompt/unit/assign _ble_contrib_prompt_git_base '' 62 [[ $prompt_unit_changed ]] 63 } 64 65 ## @fn ble/contrib/prompt-git/initialize 66 ## @var[out] git_base git_base_dir 67 function ble/contrib/prompt-git/initialize { 68 ble/prompt/unit#update _ble_contrib_prompt_git 69 ble/util/restore-vars _ble_contrib_prompt_ git_base git_base_dir 70 [[ $git_base ]] 71 } 72 73 ## @fn ble/contrib/prompt-git/check-dirty 74 ## 現在の working tree に編輯があるかどうかを非同期で取得します。 75 ## @var[in] git_base 76 _ble_contrib_prompt_git_dirty=0 77 ble/contrib/prompt-defer/clear _ble_contrib_prompt_git_dirty 78 function ble/contrib/prompt-defer:_ble_contrib_prompt_git_dirty/clear { _ble_contrib_prompt_git_dirty=0; } 79 function ble/contrib/prompt-defer:_ble_contrib_prompt_git_dirty/worker { 80 # git 1.7.2 (2010-09) supports --ignore-submodules=untracked 81 git status --porcelain --ignore-submodules=untracked | ble/bin/awk ' 82 /^[^ ?]./ { staged = 1;} 83 /^.[^ ?]/ { unstaged = 1;} 84 /^\?\?/ { untracked = 1; } 85 END { 86 if (unstaged) exit 1; 87 if (staged) exit 2; 88 if (untracked) exit 3; 89 exit 0 90 } 91 ' 92 } 93 function ble/contrib/prompt-defer:_ble_contrib_prompt_git_dirty/callback { _ble_contrib_prompt_git_dirty=$?; } 94 function ble/contrib/prompt-git/check-dirty { 95 [[ $_ble_contrib_prompt_git_base ]] || return 0 96 ble/contrib/prompt-defer/submit _ble_contrib_prompt_git_dirty "$_ble_contrib_prompt_git_base" '' 97 ble/prompt/unit/add-hash '$_ble_contrib_prompt_git_dirty' 98 return "$_ble_contrib_prompt_git_dirty" 99 } 100 function ble/contrib/prompt-git/is-dirty { ble/contrib/prompt-git/check-dirty; (($?!=0&&$?!=3)); } 101 function ble/contrib/prompt-git/get-dirty-mark { 102 dirty_mark= 103 ble/contrib/prompt-git/check-dirty; local ext=$? 104 if [[ :$1: == *:colored:* ]]; then 105 case $ext in 106 (1) dirty_mark=$'\e[1;38:5:202m*\e[m' ;; 107 (2) dirty_mark=$'\e[1;32m*\e[m' ;; 108 (3) dirty_mark=$'\e[1;94m+\e[m' ;; 109 esac 110 else 111 case $ext in 112 (1) dirty_mark='*' ;; 113 (2) dirty_mark='^' ;; 114 (3) dirty_mark='+' ;; 115 esac 116 fi 117 } 118 119 ## @fn ble/contrib/prompt-git/update-head-information 120 ## @var[out] hash branch 121 function ble/contrib/prompt-git/update-head-information { 122 [[ $hash || $branch ]] && return 0 123 124 local head_file=$git_base_dir/HEAD 125 [[ -s $head_file ]] || return 0 126 local content; ble/util/mapfile content < "$head_file" 127 128 if [[ $content == *'ref: refs/heads/'* ]]; then 129 branch=${content#*refs/heads/} 130 content= 131 132 local branch_file=$git_base_dir/refs/heads/$branch 133 if [[ -s $branch_file ]]; then 134 ble/util/mapfile content < "$branch_file" 135 elif local refs_file=$git_base_dir/info/refs; [[ -s $refs_file ]]; then 136 local lines line 137 ble/util/mapfile lines < "$refs_file" 138 for line in "${lines[@]}"; do 139 if [[ $line == *["$_ble_term_IFS"]refs/heads/"$branch" ]]; then 140 content=${line%%[$_ble_term_IFS]*} 141 break 142 fi 143 done 144 fi 145 fi 146 147 [[ $content && ! ${content//[0-9a-fA-F]} ]] && hash=$content 148 return 0 149 } 150 ## @fn ble/contrib/prompt-git/get-tag-name 151 ## @var[out] tag 152 function ble/contrib/prompt-git/get-tag-name { 153 # ble/util/assign-array tag 'git describe --tags --exact-match 2>/dev/null' 154 tag= 155 ble/contrib/prompt-git/update-head-information # -> hash, branch 156 [[ $hash ]] || return 1 157 158 local file tagsdir=$git_base_dir/refs/tags hash1 159 local files ret; ble/util/eval-pathname-expansion '"$tagsdir"/*'; files=("${ret[@]}") 160 for file in "${files[@]}"; do 161 local tag1=${file#$tagsdir/} 162 [[ -s $file ]] || continue 163 ble/util/mapfile hash1 < "$file" 164 if [[ $hash1 == "$hash" ]]; then 165 tag=$tag1 166 return 0 167 fi 168 done 169 170 if local refs_file=$git_base_dir/info/refs; [[ -s $refs_file ]]; then 171 local lines line 172 ble/util/mapfile lines < "$refs_file" 173 for line in "${lines[@]}"; do 174 if [[ $line == "$hash"["$_ble_term_IFS"]refs/tags/* ]]; then 175 tag=${line##refs/tags/*} 176 return 0 177 fi 178 done 179 fi 180 } 181 182 ## @fn ble/contrib/prompt-git/get-state opts 183 ## @var[out] ret 184 function ble/contrib/prompt-git/get-state { 185 local opts=$1 186 # https://github.com/git/git/blob/4fd6c5e44459e6444c2cd93383660134c95aabd1/contrib/completion/git-prompt.sh#L452-L475 187 # https://github.com/git/git/blob/4fd6c5e44459e6444c2cd93383660134c95aabd1/contrib/completion/git-prompt.sh#L312-L333 188 if [[ -d $git_base_dir/rebase-merge ]]; then 189 ret=REBASE 190 elif [[ -d $git_base_dir/rebase-apply ]]; then 191 if [[ -f $git_base_dir/rebase-apply/rebasing ]]; then 192 ret=REBASE 193 elif [[ -f $git_base_dir/rebase-apply/applying ]]; then 194 ret=AM 195 else 196 ret=AM/REBASE 197 fi 198 elif [[ -f $git_base_dir/MERGE_HEAD ]]; then 199 ret=MERGING 200 elif [[ -f $git_base_dir/CHERRY_PICK_HEAD ]]; then 201 ret=CHERRY-PICKING 202 elif [[ -f $git_base_dir/REVERT_HEAD ]]; then 203 ret=REVERTING 204 elif 205 local todo= rest IFS=$_ble_term_IFS 206 [[ -f $git_base_dir/sequencer/todo ]] && 207 ble/bash/read todo rest < "$git_base_dir/sequencer/todo" 208 [[ $todo == p || $todo == pick ]]; then 209 ret=CHERRY-PICKING 210 elif [[ $todo == revert ]]; then 211 ret=REVERTING 212 elif [[ -f $git_base_dir/BISECT_LOG ]]; then 213 ret=BISECTING 214 elif ble/contrib/prompt-git/update-head-information; [[ ! $branch ]]; then 215 ret=DETACHED 216 elif [[ $branch && ! $hash ]]; then 217 ret=ORPHAN 218 else 219 ret= 220 fi 221 222 if [[ :$opts: == *:colored:* ]]; then 223 case $ret in 224 (REBASE) ret=$'\e[1;48;5;27;38;5;231m '$ret$' \e[m' ;; 225 (AM | AM/REBASE) ret=$'\e[1;48;5;34;38;5;231m '$ret$' \e[m' ;; 226 (MERGING) ret=$'\e[1;48;5;172;38;5;231m '$ret$' \e[m' ;; 227 (CHERRY-PICKING) ret=$'\e[1;48;5;200;38;5;231m '$ret$' \e[m' ;; 228 (REVERTING) ret=$'\e[1;48;5;124;38;5;231m '$ret$' \e[m' ;; 229 (BISECTING) ret=$'\e[1;48;5;93;38;5;231m '$ret$' \e[m' ;; 230 (DETACHED) ret=$'\e[91m'$ret$'\e[m' ;; 231 (ORPHAN) ret=$'\e[38;5;70m'$ret$'\e[m' ;; 232 (?*) ret=$'\e[1;48;5;242;38;5;231m '$ret$' \e[m' ;; 233 esac 234 fi 235 236 [[ $ret ]] 237 } 238 239 function ble/contrib/prompt-git/describe-head { 240 local opts=:$1: 241 ret= 242 243 local dirty_mark= 244 [[ $opts == *:check-dirty:* ]] && 245 ble/contrib/prompt-git/get-dirty-mark colored 246 247 ble/contrib/prompt-git/update-head-information # -> hash, branch 248 if [[ $branch ]]; then 249 local sgr=$'\e[1;34m' sgr0=$'\e[m' 250 local out=$sgr$branch$sgr0 251 if [[ $opts == *:add-hash:* && $hash ]]; then 252 out="$out (${hash::7}$dirty_mark)" 253 else 254 out=$out$dirty_mark 255 fi 256 257 [[ $opts == *:check-state:* ]] && 258 ble/contrib/prompt-git/get-state colored && 259 out="$ret $out" 260 ret=$out 261 return 0 262 fi 263 264 local tag 265 ble/contrib/prompt-git/get-tag-name 266 if [[ $tag ]]; then 267 local sgr=$'\e[1;32m' sgr0=$'\e[m' 268 local out=$sgr$tag$sgr0 269 [[ $opts == *:add-hash:* && $hash ]] && 270 out="$out ${hash::7}" 271 out=$out$dirty_mark 272 [[ $opts == *:check-state:* ]] && 273 ble/contrib/prompt-git/get-state colored && 274 out="$ret ($out)" 275 ret=$out 276 return 0 277 fi 278 279 # "master~23" 等の分かりにくい説明なのでこれは使わない 280 # ble/util/assign-array ret 'git describe --contains --all 2>/dev/null' 281 # if [[ $ret ]]; then 282 # local label_detached=$'\e[91mDETACHED\e[m' 283 # local sgr=$'\e[32m' sgr0=$'\e[m' 284 # ret="($label_detached at $sgr$ret$sgr0)" 285 # return 0 286 # fi 287 288 if [[ $hash ]]; then 289 local out=${hash::7}$dirty_mark 290 [[ $opts == *:check-state:* ]] && 291 ble/contrib/prompt-git/get-state colored && 292 out="$ret ($out)" 293 ret=$out 294 return 0 295 fi 296 297 ret=$'\e[91mUNKNOWN\e[m' 298 } 299 300 #------------------------------------------------------------------------------ 301 302 function ble/prompt/backslash:contrib/git-info { 303 local "${_ble_contrib_prompt_git_vars[@]/%/=}" # WA #D1570 checked 304 if ble/contrib/prompt-git/initialize; then 305 local sgr=$'\e[1m' sgr0=$'\e[m' 306 local name=$sgr${git_base##*?/}$sgr0 307 local ret; ble/contrib/prompt-git/describe-head add-hash:check-dirty:check-state; local branch=$ret 308 ble/prompt/print "$name $branch" 309 [[ $PWD == "$git_base"/?* ]] && 310 ble/prompt/print " /${PWD#$git_base/}" 311 return 0 312 else 313 return 1 314 fi 315 } 316 function ble/prompt/backslash:contrib/git-name { 317 local "${_ble_contrib_prompt_git_vars[@]/%/=}" # WA #D1570 checked 318 if ble/contrib/prompt-git/initialize; then 319 local name=${git_base%.git} 320 name=${name%/} 321 name=${name##*?/} 322 ble/prompt/print "${git_base##*?/}" 323 fi 324 } 325 function ble/prompt/backslash:contrib/git-hash { 326 local "${_ble_contrib_prompt_git_vars[@]/%/=}" # WA #D1570 checked 327 if ble/contrib/prompt-git/initialize; then 328 ble/contrib/prompt-git/update-head-information 329 ble/prompt/print "${hash::${1:-7}}" 330 fi 331 } 332 function ble/prompt/backslash:contrib/git-branch { 333 local "${_ble_contrib_prompt_git_vars[@]/%/=}" # WA #D1570 checked 334 if ble/contrib/prompt-git/initialize; then 335 local ret; ble/contrib/prompt-git/describe-head check-dirty 336 ble/prompt/print "$ret" 337 fi 338 } 339 function ble/prompt/backslash:contrib/git-path { 340 local "${_ble_contrib_prompt_git_vars[@]/%/=}" # WA #D1570 checked 341 if ble/contrib/prompt-git/initialize; then 342 if [[ $PWD == "$git_base"/?* ]]; then 343 ble/prompt/print "/${PWD#$git_base/}" 344 elif [[ $PWD == "$git_base" ]]; then 345 ble/prompt/print / 346 fi 347 fi 348 }