1# The git prompt's git commands are read-only and should not interfere with 2# other processes. This environment variable is equivalent to running with `git 3# --no-optional-locks`, but falls back gracefully for older versions of git. 4# See git(1) for and git-status(1) for a description of that flag. 5# 6# We wrap in a local function instead of exporting the variable directly in 7# order to avoid interfering with manually-run git commands by the user. 8function __git_prompt_git() { 9 GIT_OPTIONAL_LOCKS=0 command git "$@" 10} 11 12function git_prompt_info() { 13 # If we are on a folder not tracked by git, get out. 14 # Otherwise, check for hide-info at global and local repository level 15 if ! __git_prompt_git rev-parse --git-dir &> /dev/null \ 16 || [[ "$(__git_prompt_git config --get oh-my-zsh.hide-info 2>/dev/null)" == 1 ]]; then 17 return 0 18 fi 19 20 local ref 21 ref=$(__git_prompt_git symbolic-ref --short HEAD 2> /dev/null) \ 22 || ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) \ 23 || return 0 24 25 # Use global ZSH_THEME_GIT_SHOW_UPSTREAM=1 for including upstream remote info 26 local upstream 27 if (( ${+ZSH_THEME_GIT_SHOW_UPSTREAM} )); then 28 upstream=$(__git_prompt_git rev-parse --abbrev-ref --symbolic-full-name "@{upstream}" 2>/dev/null) \ 29 && upstream=" -> ${upstream}" 30 fi 31 32 echo "${ZSH_THEME_GIT_PROMPT_PREFIX}${ref:gs/%/%%}${upstream:gs/%/%%}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}" 33} 34 35# Checks if working tree is dirty 36function parse_git_dirty() { 37 local STATUS 38 local -a FLAGS 39 FLAGS=('--porcelain') 40 if [[ "$(__git_prompt_git config --get oh-my-zsh.hide-dirty)" != "1" ]]; then 41 if [[ "${DISABLE_UNTRACKED_FILES_DIRTY:-}" == "true" ]]; then 42 FLAGS+='--untracked-files=no' 43 fi 44 case "${GIT_STATUS_IGNORE_SUBMODULES:-}" in 45 git) 46 # let git decide (this respects per-repo config in .gitmodules) 47 ;; 48 *) 49 # if unset: ignore dirty submodules 50 # other values are passed to --ignore-submodules 51 FLAGS+="--ignore-submodules=${GIT_STATUS_IGNORE_SUBMODULES:-dirty}" 52 ;; 53 esac 54 STATUS=$(__git_prompt_git status ${FLAGS} 2> /dev/null | tail -n 1) 55 fi 56 if [[ -n $STATUS ]]; then 57 echo "$ZSH_THEME_GIT_PROMPT_DIRTY" 58 else 59 echo "$ZSH_THEME_GIT_PROMPT_CLEAN" 60 fi 61} 62 63# Gets the difference between the local and remote branches 64function git_remote_status() { 65 local remote ahead behind git_remote_status git_remote_status_detailed 66 remote=${$(__git_prompt_git rev-parse --verify ${hook_com[branch]}@{upstream} --symbolic-full-name 2>/dev/null)/refs\/remotes\/} 67 if [[ -n ${remote} ]]; then 68 ahead=$(__git_prompt_git rev-list ${hook_com[branch]}@{upstream}..HEAD 2>/dev/null | wc -l) 69 behind=$(__git_prompt_git rev-list HEAD..${hook_com[branch]}@{upstream} 2>/dev/null | wc -l) 70 71 if [[ $ahead -eq 0 ]] && [[ $behind -eq 0 ]]; then 72 git_remote_status="$ZSH_THEME_GIT_PROMPT_EQUAL_REMOTE" 73 elif [[ $ahead -gt 0 ]] && [[ $behind -eq 0 ]]; then 74 git_remote_status="$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE" 75 git_remote_status_detailed="$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE$((ahead))%{$reset_color%}" 76 elif [[ $behind -gt 0 ]] && [[ $ahead -eq 0 ]]; then 77 git_remote_status="$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE" 78 git_remote_status_detailed="$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE$((behind))%{$reset_color%}" 79 elif [[ $ahead -gt 0 ]] && [[ $behind -gt 0 ]]; then 80 git_remote_status="$ZSH_THEME_GIT_PROMPT_DIVERGED_REMOTE" 81 git_remote_status_detailed="$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE$((ahead))%{$reset_color%}$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE$((behind))%{$reset_color%}" 82 fi 83 84 if [[ -n $ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_DETAILED ]]; then 85 git_remote_status="$ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_PREFIX$remote$git_remote_status_detailed$ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_SUFFIX" 86 fi 87 88 echo $git_remote_status 89 fi 90} 91 92# Outputs the name of the current branch 93# Usage example: git pull origin $(git_current_branch) 94# Using '--quiet' with 'symbolic-ref' will not cause a fatal error (128) if 95# it's not a symbolic ref, but in a Git repo. 96function git_current_branch() { 97 local ref 98 ref=$(__git_prompt_git symbolic-ref --quiet HEAD 2> /dev/null) 99 local ret=$? 100 if [[ $ret != 0 ]]; then 101 [[ $ret == 128 ]] && return # no git repo. 102 ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) || return 103 fi 104 echo ${ref#refs/heads/} 105} 106 107 108# Gets the number of commits ahead from remote 109function git_commits_ahead() { 110 if __git_prompt_git rev-parse --git-dir &>/dev/null; then 111 local commits="$(__git_prompt_git rev-list --count @{upstream}..HEAD 2>/dev/null)" 112 if [[ -n "$commits" && "$commits" != 0 ]]; then 113 echo "$ZSH_THEME_GIT_COMMITS_AHEAD_PREFIX$commits$ZSH_THEME_GIT_COMMITS_AHEAD_SUFFIX" 114 fi 115 fi 116} 117 118# Gets the number of commits behind remote 119function git_commits_behind() { 120 if __git_prompt_git rev-parse --git-dir &>/dev/null; then 121 local commits="$(__git_prompt_git rev-list --count HEAD..@{upstream} 2>/dev/null)" 122 if [[ -n "$commits" && "$commits" != 0 ]]; then 123 echo "$ZSH_THEME_GIT_COMMITS_BEHIND_PREFIX$commits$ZSH_THEME_GIT_COMMITS_BEHIND_SUFFIX" 124 fi 125 fi 126} 127 128# Outputs if current branch is ahead of remote 129function git_prompt_ahead() { 130 if [[ -n "$(__git_prompt_git rev-list origin/$(git_current_branch)..HEAD 2> /dev/null)" ]]; then 131 echo "$ZSH_THEME_GIT_PROMPT_AHEAD" 132 fi 133} 134 135# Outputs if current branch is behind remote 136function git_prompt_behind() { 137 if [[ -n "$(__git_prompt_git rev-list HEAD..origin/$(git_current_branch) 2> /dev/null)" ]]; then 138 echo "$ZSH_THEME_GIT_PROMPT_BEHIND" 139 fi 140} 141 142# Outputs if current branch exists on remote or not 143function git_prompt_remote() { 144 if [[ -n "$(__git_prompt_git show-ref origin/$(git_current_branch) 2> /dev/null)" ]]; then 145 echo "$ZSH_THEME_GIT_PROMPT_REMOTE_EXISTS" 146 else 147 echo "$ZSH_THEME_GIT_PROMPT_REMOTE_MISSING" 148 fi 149} 150 151# Formats prompt string for current git commit short SHA 152function git_prompt_short_sha() { 153 local SHA 154 SHA=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER" 155} 156 157# Formats prompt string for current git commit long SHA 158function git_prompt_long_sha() { 159 local SHA 160 SHA=$(__git_prompt_git rev-parse HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER" 161} 162 163function git_prompt_status() { 164 [[ "$(__git_prompt_git config --get oh-my-zsh.hide-status 2>/dev/null)" = 1 ]] && return 165 166 # Maps a git status prefix to an internal constant 167 # This cannot use the prompt constants, as they may be empty 168 local -A prefix_constant_map 169 prefix_constant_map=( 170 '\?\? ' 'UNTRACKED' 171 'A ' 'ADDED' 172 'M ' 'ADDED' 173 'MM ' 'MODIFIED' 174 ' M ' 'MODIFIED' 175 'AM ' 'MODIFIED' 176 ' T ' 'MODIFIED' 177 'R ' 'RENAMED' 178 ' D ' 'DELETED' 179 'D ' 'DELETED' 180 'UU ' 'UNMERGED' 181 'ahead' 'AHEAD' 182 'behind' 'BEHIND' 183 'diverged' 'DIVERGED' 184 'stashed' 'STASHED' 185 ) 186 187 # Maps the internal constant to the prompt theme 188 local -A constant_prompt_map 189 constant_prompt_map=( 190 'UNTRACKED' "$ZSH_THEME_GIT_PROMPT_UNTRACKED" 191 'ADDED' "$ZSH_THEME_GIT_PROMPT_ADDED" 192 'MODIFIED' "$ZSH_THEME_GIT_PROMPT_MODIFIED" 193 'RENAMED' "$ZSH_THEME_GIT_PROMPT_RENAMED" 194 'DELETED' "$ZSH_THEME_GIT_PROMPT_DELETED" 195 'UNMERGED' "$ZSH_THEME_GIT_PROMPT_UNMERGED" 196 'AHEAD' "$ZSH_THEME_GIT_PROMPT_AHEAD" 197 'BEHIND' "$ZSH_THEME_GIT_PROMPT_BEHIND" 198 'DIVERGED' "$ZSH_THEME_GIT_PROMPT_DIVERGED" 199 'STASHED' "$ZSH_THEME_GIT_PROMPT_STASHED" 200 ) 201 202 # The order that the prompt displays should be added to the prompt 203 local status_constants 204 status_constants=( 205 UNTRACKED ADDED MODIFIED RENAMED DELETED 206 STASHED UNMERGED AHEAD BEHIND DIVERGED 207 ) 208 209 local status_text 210 status_text="$(__git_prompt_git status --porcelain -b 2> /dev/null)" 211 212 # Don't continue on a catastrophic failure 213 if [[ $? -eq 128 ]]; then 214 return 1 215 fi 216 217 # A lookup table of each git status encountered 218 local -A statuses_seen 219 220 if __git_prompt_git rev-parse --verify refs/stash &>/dev/null; then 221 statuses_seen[STASHED]=1 222 fi 223 224 local status_lines 225 status_lines=("${(@f)${status_text}}") 226 227 # If the tracking line exists, get and parse it 228 if [[ "$status_lines[1]" =~ "^## [^ ]+ \[(.*)\]" ]]; then 229 local branch_statuses 230 branch_statuses=("${(@s/,/)match}") 231 for branch_status in $branch_statuses; do 232 if [[ ! $branch_status =~ "(behind|diverged|ahead) ([0-9]+)?" ]]; then 233 continue 234 fi 235 local last_parsed_status=$prefix_constant_map[$match[1]] 236 statuses_seen[$last_parsed_status]=$match[2] 237 done 238 fi 239 240 # For each status prefix, do a regex comparison 241 for status_prefix in ${(k)prefix_constant_map}; do 242 local status_constant="${prefix_constant_map[$status_prefix]}" 243 local status_regex=$'(^|\n)'"$status_prefix" 244 245 if [[ "$status_text" =~ $status_regex ]]; then 246 statuses_seen[$status_constant]=1 247 fi 248 done 249 250 # Display the seen statuses in the order specified 251 local status_prompt 252 for status_constant in $status_constants; do 253 if (( ${+statuses_seen[$status_constant]} )); then 254 local next_display=$constant_prompt_map[$status_constant] 255 status_prompt="$next_display$status_prompt" 256 fi 257 done 258 259 echo $status_prompt 260} 261 262# Outputs the name of the current user 263# Usage example: $(git_current_user_name) 264function git_current_user_name() { 265 __git_prompt_git config user.name 2>/dev/null 266} 267 268# Outputs the email of the current user 269# Usage example: $(git_current_user_email) 270function git_current_user_email() { 271 __git_prompt_git config user.email 2>/dev/null 272} 273 274# Output the name of the root directory of the git repository 275# Usage example: $(git_repo_name) 276function git_repo_name() { 277 local repo_path 278 if repo_path="$(__git_prompt_git rev-parse --show-toplevel 2>/dev/null)" && [[ -n "$repo_path" ]]; then 279 echo ${repo_path:t} 280 fi 281} 282