git.zsh 9.76 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
# The git prompt's git commands are read-only and should not interfere with
# other processes. This environment variable is equivalent to running with `git
# --no-optional-locks`, but falls back gracefully for older versions of git.
# See git(1) for and git-status(1) for a description of that flag.
#
# We wrap in a local function instead of exporting the variable directly in
# order to avoid interfering with manually-run git commands by the user.
function __git_prompt_git() {
  GIT_OPTIONAL_LOCKS=0 command git "$@"
}

12
# Outputs current branch info in prompt format
13
function git_prompt_info() {
14
  local ref
15
16
17
  if [[ "$(__git_prompt_git config --get oh-my-zsh.hide-status 2>/dev/null)" != "1" ]]; then
    ref=$(__git_prompt_git symbolic-ref HEAD 2> /dev/null) || \
    ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) || return 0
18
19
    echo "$ZSH_THEME_GIT_PROMPT_PREFIX${ref#refs/heads/}$(parse_git_dirty)$ZSH_THEME_GIT_PROMPT_SUFFIX"
  fi
20
}
21

22
# Checks if working tree is dirty
23
function parse_git_dirty() {
24
  local STATUS
25
  local -a FLAGS
26
  FLAGS=('--porcelain')
27
  if [[ "$(__git_prompt_git config --get oh-my-zsh.hide-dirty)" != "1" ]]; then
28
    if [[ "${DISABLE_UNTRACKED_FILES_DIRTY:-}" == "true" ]]; then
29
      FLAGS+='--untracked-files=no'
30
    fi
31
    case "${GIT_STATUS_IGNORE_SUBMODULES:-}" in
32
      git)
33
34
35
        # let git decide (this respects per-repo config in .gitmodules)
        ;;
      *)
36
        # if unset: ignore dirty submodules
37
        # other values are passed to --ignore-submodules
38
        FLAGS+="--ignore-submodules=${GIT_STATUS_IGNORE_SUBMODULES:-dirty}"
39
40
        ;;
    esac
41
    STATUS=$(__git_prompt_git status ${FLAGS} 2> /dev/null | tail -n1)
42
43
44
  fi
  if [[ -n $STATUS ]]; then
    echo "$ZSH_THEME_GIT_PROMPT_DIRTY"
Felipe Contreras's avatar
Felipe Contreras committed
45
46
  else
    echo "$ZSH_THEME_GIT_PROMPT_CLEAN"
47
  fi
48
}
49

50
51
52
# Gets the difference between the local and remote branches
function git_remote_status() {
    local remote ahead behind git_remote_status git_remote_status_detailed
53
    remote=${$(__git_prompt_git rev-parse --verify ${hook_com[branch]}@{upstream} --symbolic-full-name 2>/dev/null)/refs\/remotes\/}
54
    if [[ -n ${remote} ]]; then
55
56
        ahead=$(__git_prompt_git rev-list ${hook_com[branch]}@{upstream}..HEAD 2>/dev/null | wc -l)
        behind=$(__git_prompt_git rev-list HEAD..${hook_com[branch]}@{upstream} 2>/dev/null | wc -l)
57

58
59
60
        if [[ $ahead -eq 0 ]] && [[ $behind -eq 0 ]]; then
            git_remote_status="$ZSH_THEME_GIT_PROMPT_EQUAL_REMOTE"
        elif [[ $ahead -gt 0 ]] && [[ $behind -eq 0 ]]; then
61
62
            git_remote_status="$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE"
            git_remote_status_detailed="$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE$((ahead))%{$reset_color%}"
63
        elif [[ $behind -gt 0 ]] && [[ $ahead -eq 0 ]]; then
64
65
            git_remote_status="$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE"
            git_remote_status_detailed="$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE$((behind))%{$reset_color%}"
66
        elif [[ $ahead -gt 0 ]] && [[ $behind -gt 0 ]]; then
67
68
            git_remote_status="$ZSH_THEME_GIT_PROMPT_DIVERGED_REMOTE"
            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%}"
69
        fi
70

71
        if [[ -n $ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_DETAILED ]]; then
72
73
74
75
            git_remote_status="$ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_PREFIX$remote$git_remote_status_detailed$ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_SUFFIX"
        fi

        echo $git_remote_status
76
77
78
    fi
}

79
80
81
82
83
84
# Outputs the name of the current branch
# Usage example: git pull origin $(git_current_branch)
# Using '--quiet' with 'symbolic-ref' will not cause a fatal error (128) if
# it's not a symbolic ref, but in a Git repo.
function git_current_branch() {
  local ref
85
  ref=$(__git_prompt_git symbolic-ref --quiet HEAD 2> /dev/null)
86
87
88
  local ret=$?
  if [[ $ret != 0 ]]; then
    [[ $ret == 128 ]] && return  # no git repo.
89
    ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) || return
90
91
92
93
94
  fi
  echo ${ref#refs/heads/}
}


95
96
# Gets the number of commits ahead from remote
function git_commits_ahead() {
97
98
  if __git_prompt_git rev-parse --git-dir &>/dev/null; then
    local commits="$(__git_prompt_git rev-list --count @{upstream}..HEAD 2>/dev/null)"
99
    if [[ -n "$commits" && "$commits" != 0 ]]; then
100
101
      echo "$ZSH_THEME_GIT_COMMITS_AHEAD_PREFIX$commits$ZSH_THEME_GIT_COMMITS_AHEAD_SUFFIX"
    fi
102
103
104
  fi
}

105
106
# Gets the number of commits behind remote
function git_commits_behind() {
107
108
  if __git_prompt_git rev-parse --git-dir &>/dev/null; then
    local commits="$(__git_prompt_git rev-list --count HEAD..@{upstream} 2>/dev/null)"
109
    if [[ -n "$commits" && "$commits" != 0 ]]; then
110
111
      echo "$ZSH_THEME_GIT_COMMITS_BEHIND_PREFIX$commits$ZSH_THEME_GIT_COMMITS_BEHIND_SUFFIX"
    fi
112
113
114
  fi
}

115
116
# Outputs if current branch is ahead of remote
function git_prompt_ahead() {
117
  if [[ -n "$(__git_prompt_git rev-list origin/$(git_current_branch)..HEAD 2> /dev/null)" ]]; then
118
119
120
121
122
123
    echo "$ZSH_THEME_GIT_PROMPT_AHEAD"
  fi
}

# Outputs if current branch is behind remote
function git_prompt_behind() {
124
  if [[ -n "$(__git_prompt_git rev-list HEAD..origin/$(git_current_branch) 2> /dev/null)" ]]; then
125
126
127
128
129
130
    echo "$ZSH_THEME_GIT_PROMPT_BEHIND"
  fi
}

# Outputs if current branch exists on remote or not
function git_prompt_remote() {
131
  if [[ -n "$(__git_prompt_git show-ref origin/$(git_current_branch) 2> /dev/null)" ]]; then
132
133
134
135
136
137
    echo "$ZSH_THEME_GIT_PROMPT_REMOTE_EXISTS"
  else
    echo "$ZSH_THEME_GIT_PROMPT_REMOTE_MISSING"
  fi
}

138
139
# Formats prompt string for current git commit short SHA
function git_prompt_short_sha() {
140
  local SHA
141
  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"
142
143
144
145
}

# Formats prompt string for current git commit long SHA
function git_prompt_long_sha() {
146
  local SHA
147
  SHA=$(__git_prompt_git rev-parse HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER"
148
149
}

150
function git_prompt_status() {
151
152
  [[ "$(__git_prompt_git config --get oh-my-zsh.hide-status 2>/dev/null)" = 1 ]] && return

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
  local status_prompt=""

  # A lookup table of each git status encountered
  local -A statuses_seen

  # Maps a git status prefix to an internal constant
  # This cannot use the prompt constants, as they may be empty
  local -A prefix_constant_map=(
    '?? '       'UNTRACKED'
    'A  '       'ADDED'
    'M  '       'ADDED'
    'MM '       'ADDED'
    ' M '       'MODIFIED'
    'AM '       'MODIFIED'
    ' T '       'MODIFIED'
    'R  '       'RENAMED'
    ' D '       'DELETED'
    'D  '       'DELETED'
    'UU '       'UNMERGED'
    'ahead'     'AHEAD'
    'behind'    'BEHIND'
    'diverged'  'DIVERGED'
    'stashed'   'STASHED'
  )

  # Maps the internal constant to the prompt theme
  local -A constant_prompt_map=(
    'UNTRACKED' "$ZSH_THEME_GIT_PROMPT_UNTRACKED"
    'ADDED'     "$ZSH_THEME_GIT_PROMPT_ADDED"
    'MODIFIED'  "$ZSH_THEME_GIT_PROMPT_MODIFIED"
    'RENAMED'   "$ZSH_THEME_GIT_PROMPT_RENAMED"
    'DELETED'   "$ZSH_THEME_GIT_PROMPT_DELETED"
    'UNMERGED'  "$ZSH_THEME_GIT_PROMPT_UNMERGED"
    'AHEAD'     "$ZSH_THEME_GIT_PROMPT_AHEAD"
    'BEHIND'    "$ZSH_THEME_GIT_PROMPT_BEHIND"
    'DIVERGED'  "$ZSH_THEME_GIT_PROMPT_DIVERGED"
    'STASHED'   "$ZSH_THEME_GIT_PROMPT_STASHED"
  )

  # The order that the prompt displays should be added to the prompt
  local status_constants=(UNTRACKED ADDED MODIFIED RENAMED DELETED STASHED
                          UNMERGED AHEAD BEHIND DIVERGED)

  local status_text=$(__git_prompt_git status --porcelain -b 2> /dev/null)

  # Don't continue on a catastrophic failure
  if [[ $? -eq 128 ]]; then
    return 1
201
  fi
202

203
  if $(__git_prompt_git rev-parse --verify refs/stash >/dev/null 2>&1); then
204
    statuses_seen['STASHED']=1
205
  fi
206
207
208
209
210
211
212
213
214
215
216
217
218
219

  local status_lines=("${(@f)${status_text}}");

  # If the tracking line exists, get and parse it
  if [[ $status_lines[1] =~ "^## [^ ]+ \[(.*)\]" ]]; then
    local branch_statuses=("${(@s/,/)match}")
    for branch_status in $branch_statuses; do
      if [[ ! $branch_status =~ "(behind|diverged|ahead) ([0-9]+)?" ]]; then
        continue
      fi
      local last_parsed_status=$prefix_constant_map[$match[1]]
      statuses_seen[$last_parsed_status]=$match[2]
    done
    shift status_lines
220
  fi
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247

  # This not only gives us a status lookup, but the count of each type
  for status_line in ${status_lines}; do
    local status_prefix=${status_line[1, 3]}
    local status_constant=${(v)prefix_constant_map[$status_prefix]}

    if [[ -z $status_constant ]]; then
      continue
    fi

    (( statuses_seen[$status_constant]++ ))
  done

  # At this point, the statuses_seen hash contains:
  # - Tracking      => The difference between tracked and current
  # - Modifications => The count of that type of modification
  # - Stash         => Whether or not a stash exists
  # Might be useful for someone?

  for status_constant in $status_constants; do
    if [[ ${+statuses_seen[$status_constant]} -eq 1 ]]; then
      local next_display=$constant_prompt_map[$status_constant]
      status_prompt="$next_display$status_prompt"
    fi
  done

  echo $status_prompt
248
}
249

250
251
252
# Outputs the name of the current user
# Usage example: $(git_current_user_name)
function git_current_user_name() {
253
  __git_prompt_git config user.name 2>/dev/null
254
255
256
257
258
}

# Outputs the email of the current user
# Usage example: $(git_current_user_email)
function git_current_user_email() {
259
  __git_prompt_git config user.email 2>/dev/null
260
}
261
262
263
264
265

# Output the name of the root directory of the git repository
# Usage example: $(git_repo_name)
function git_repo_name() {
  local repo_path
266
  if repo_path="$(__git_prompt_git rev-parse --show-toplevel 2>/dev/null)" && [[ -n "$repo_path" ]]; then
267
268
269
    echo ${repo_path:t}
  fi
}