mirror of
https://github.com/ohmyzsh/ohmyzsh.git
synced 2024-11-22 15:53:22 +01:00
ec1afe9dd6
BREAKING CHANGE: the new async prompt feature will render prompt information asyncronously and degrade nicely when the calls take too long to finish, as the prompt will already be first drawn and interactive. This is enabled by default for the git prompt and themes that use it (`git_prompt_info`). If you find that it's not working for you, please open an issue if one is not already opened, and see https://github.com/ohmyzsh/ohmyzsh#disable-async-git-prompt for how to turn it off.
319 lines
11 KiB
Bash
319 lines
11 KiB
Bash
# 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 "$@"
|
|
}
|
|
|
|
function _omz_git_prompt_status() {
|
|
# If we are on a folder not tracked by git, get out.
|
|
# Otherwise, check for hide-info at global and local repository level
|
|
if ! __git_prompt_git rev-parse --git-dir &> /dev/null \
|
|
|| [[ "$(__git_prompt_git config --get oh-my-zsh.hide-info 2>/dev/null)" == 1 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Get either:
|
|
# - the current branch name
|
|
# - the tag name if we are on a tag
|
|
# - the short SHA of the current commit
|
|
local ref
|
|
ref=$(__git_prompt_git symbolic-ref --short HEAD 2> /dev/null) \
|
|
|| ref=$(__git_prompt_git describe --tags --exact-match HEAD 2> /dev/null) \
|
|
|| ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) \
|
|
|| return 0
|
|
|
|
# Use global ZSH_THEME_GIT_SHOW_UPSTREAM=1 for including upstream remote info
|
|
local upstream
|
|
if (( ${+ZSH_THEME_GIT_SHOW_UPSTREAM} )); then
|
|
upstream=$(__git_prompt_git rev-parse --abbrev-ref --symbolic-full-name "@{upstream}" 2>/dev/null) \
|
|
&& upstream=" -> ${upstream}"
|
|
fi
|
|
|
|
echo "${ZSH_THEME_GIT_PROMPT_PREFIX}${ref:gs/%/%%}${upstream:gs/%/%%}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}"
|
|
}
|
|
|
|
# Enable async prompt by default unless the setting is at false / no
|
|
if zstyle -T ':omz:alpha:lib:git' async-prompt; then
|
|
function git_prompt_info() {
|
|
if [[ -n "$_OMZ_ASYNC_OUTPUT[_omz_git_prompt_status]" ]]; then
|
|
echo -n "$_OMZ_ASYNC_OUTPUT[_omz_git_prompt_status]"
|
|
fi
|
|
}
|
|
|
|
# Conditionally register the async handler, only if it's needed in $PROMPT
|
|
# or any of the other prompt variables
|
|
function _defer_async_git_register() {
|
|
# Check if git_prompt_info is used in a prompt variable
|
|
case "${PS1}:${PS2}:${PS3}:${PS4}:${RPS1}:${RPS2}:${RPS3}:${RPS4}" in
|
|
*(\$\(git_prompt_info\)|\`git_prompt_info\`)*)
|
|
_omz_register_handler _omz_git_prompt_status
|
|
return
|
|
;;
|
|
esac
|
|
|
|
add-zsh-hook -d precmd _defer_async_git_register
|
|
unset -f _defer_async_git_register
|
|
}
|
|
|
|
# Register the async handler first. This needs to be done before
|
|
# the async request prompt is run
|
|
precmd_functions=(_defer_async_git_register $precmd_functions)
|
|
else
|
|
function git_prompt_info() {
|
|
_omz_git_prompt_status
|
|
}
|
|
fi
|
|
|
|
# Checks if working tree is dirty
|
|
function parse_git_dirty() {
|
|
local STATUS
|
|
local -a FLAGS
|
|
FLAGS=('--porcelain')
|
|
if [[ "$(__git_prompt_git config --get oh-my-zsh.hide-dirty)" != "1" ]]; then
|
|
if [[ "${DISABLE_UNTRACKED_FILES_DIRTY:-}" == "true" ]]; then
|
|
FLAGS+='--untracked-files=no'
|
|
fi
|
|
case "${GIT_STATUS_IGNORE_SUBMODULES:-}" in
|
|
git)
|
|
# let git decide (this respects per-repo config in .gitmodules)
|
|
;;
|
|
*)
|
|
# if unset: ignore dirty submodules
|
|
# other values are passed to --ignore-submodules
|
|
FLAGS+="--ignore-submodules=${GIT_STATUS_IGNORE_SUBMODULES:-dirty}"
|
|
;;
|
|
esac
|
|
STATUS=$(__git_prompt_git status ${FLAGS} 2> /dev/null | tail -n 1)
|
|
fi
|
|
if [[ -n $STATUS ]]; then
|
|
echo "$ZSH_THEME_GIT_PROMPT_DIRTY"
|
|
else
|
|
echo "$ZSH_THEME_GIT_PROMPT_CLEAN"
|
|
fi
|
|
}
|
|
|
|
# Gets the difference between the local and remote branches
|
|
function git_remote_status() {
|
|
local remote ahead behind git_remote_status git_remote_status_detailed
|
|
remote=${$(__git_prompt_git rev-parse --verify ${hook_com[branch]}@{upstream} --symbolic-full-name 2>/dev/null)/refs\/remotes\/}
|
|
if [[ -n ${remote} ]]; then
|
|
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)
|
|
|
|
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
|
|
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%}"
|
|
elif [[ $behind -gt 0 ]] && [[ $ahead -eq 0 ]]; then
|
|
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%}"
|
|
elif [[ $ahead -gt 0 ]] && [[ $behind -gt 0 ]]; then
|
|
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%}"
|
|
fi
|
|
|
|
if [[ -n $ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_DETAILED ]]; then
|
|
git_remote_status="$ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_PREFIX${remote:gs/%/%%}$git_remote_status_detailed$ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_SUFFIX"
|
|
fi
|
|
|
|
echo $git_remote_status
|
|
fi
|
|
}
|
|
|
|
# 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
|
|
ref=$(__git_prompt_git symbolic-ref --quiet HEAD 2> /dev/null)
|
|
local ret=$?
|
|
if [[ $ret != 0 ]]; then
|
|
[[ $ret == 128 ]] && return # no git repo.
|
|
ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) || return
|
|
fi
|
|
echo ${ref#refs/heads/}
|
|
}
|
|
|
|
|
|
# Gets the number of commits ahead from remote
|
|
function git_commits_ahead() {
|
|
if __git_prompt_git rev-parse --git-dir &>/dev/null; then
|
|
local commits="$(__git_prompt_git rev-list --count @{upstream}..HEAD 2>/dev/null)"
|
|
if [[ -n "$commits" && "$commits" != 0 ]]; then
|
|
echo "$ZSH_THEME_GIT_COMMITS_AHEAD_PREFIX$commits$ZSH_THEME_GIT_COMMITS_AHEAD_SUFFIX"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Gets the number of commits behind remote
|
|
function git_commits_behind() {
|
|
if __git_prompt_git rev-parse --git-dir &>/dev/null; then
|
|
local commits="$(__git_prompt_git rev-list --count HEAD..@{upstream} 2>/dev/null)"
|
|
if [[ -n "$commits" && "$commits" != 0 ]]; then
|
|
echo "$ZSH_THEME_GIT_COMMITS_BEHIND_PREFIX$commits$ZSH_THEME_GIT_COMMITS_BEHIND_SUFFIX"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Outputs if current branch is ahead of remote
|
|
function git_prompt_ahead() {
|
|
if [[ -n "$(__git_prompt_git rev-list origin/$(git_current_branch)..HEAD 2> /dev/null)" ]]; then
|
|
echo "$ZSH_THEME_GIT_PROMPT_AHEAD"
|
|
fi
|
|
}
|
|
|
|
# Outputs if current branch is behind remote
|
|
function git_prompt_behind() {
|
|
if [[ -n "$(__git_prompt_git rev-list HEAD..origin/$(git_current_branch) 2> /dev/null)" ]]; then
|
|
echo "$ZSH_THEME_GIT_PROMPT_BEHIND"
|
|
fi
|
|
}
|
|
|
|
# Outputs if current branch exists on remote or not
|
|
function git_prompt_remote() {
|
|
if [[ -n "$(__git_prompt_git show-ref origin/$(git_current_branch) 2> /dev/null)" ]]; then
|
|
echo "$ZSH_THEME_GIT_PROMPT_REMOTE_EXISTS"
|
|
else
|
|
echo "$ZSH_THEME_GIT_PROMPT_REMOTE_MISSING"
|
|
fi
|
|
}
|
|
|
|
# Formats prompt string for current git commit short SHA
|
|
function git_prompt_short_sha() {
|
|
local SHA
|
|
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"
|
|
}
|
|
|
|
# Formats prompt string for current git commit long SHA
|
|
function git_prompt_long_sha() {
|
|
local SHA
|
|
SHA=$(__git_prompt_git rev-parse HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER"
|
|
}
|
|
|
|
function git_prompt_status() {
|
|
[[ "$(__git_prompt_git config --get oh-my-zsh.hide-status 2>/dev/null)" = 1 ]] && return
|
|
|
|
# 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
|
|
prefix_constant_map=(
|
|
'\?\? ' 'UNTRACKED'
|
|
'A ' 'ADDED'
|
|
'M ' 'ADDED'
|
|
'MM ' 'MODIFIED'
|
|
' 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
|
|
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
|
|
status_constants=(
|
|
UNTRACKED ADDED MODIFIED RENAMED DELETED
|
|
STASHED UNMERGED AHEAD BEHIND DIVERGED
|
|
)
|
|
|
|
local status_text
|
|
status_text="$(__git_prompt_git status --porcelain -b 2> /dev/null)"
|
|
|
|
# Don't continue on a catastrophic failure
|
|
if [[ $? -eq 128 ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# A lookup table of each git status encountered
|
|
local -A statuses_seen
|
|
|
|
if __git_prompt_git rev-parse --verify refs/stash &>/dev/null; then
|
|
statuses_seen[STASHED]=1
|
|
fi
|
|
|
|
local status_lines
|
|
status_lines=("${(@f)${status_text}}")
|
|
|
|
# If the tracking line exists, get and parse it
|
|
if [[ "$status_lines[1]" =~ "^## [^ ]+ \[(.*)\]" ]]; then
|
|
local branch_statuses
|
|
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
|
|
fi
|
|
|
|
# For each status prefix, do a regex comparison
|
|
for status_prefix in ${(k)prefix_constant_map}; do
|
|
local status_constant="${prefix_constant_map[$status_prefix]}"
|
|
local status_regex=$'(^|\n)'"$status_prefix"
|
|
|
|
if [[ "$status_text" =~ $status_regex ]]; then
|
|
statuses_seen[$status_constant]=1
|
|
fi
|
|
done
|
|
|
|
# Display the seen statuses in the order specified
|
|
local status_prompt
|
|
for status_constant in $status_constants; do
|
|
if (( ${+statuses_seen[$status_constant]} )); then
|
|
local next_display=$constant_prompt_map[$status_constant]
|
|
status_prompt="$next_display$status_prompt"
|
|
fi
|
|
done
|
|
|
|
echo $status_prompt
|
|
}
|
|
|
|
# Outputs the name of the current user
|
|
# Usage example: $(git_current_user_name)
|
|
function git_current_user_name() {
|
|
__git_prompt_git config user.name 2>/dev/null
|
|
}
|
|
|
|
# Outputs the email of the current user
|
|
# Usage example: $(git_current_user_email)
|
|
function git_current_user_email() {
|
|
__git_prompt_git config user.email 2>/dev/null
|
|
}
|
|
|
|
# Output the name of the root directory of the git repository
|
|
# Usage example: $(git_repo_name)
|
|
function git_repo_name() {
|
|
local repo_path
|
|
if repo_path="$(__git_prompt_git rev-parse --show-toplevel 2>/dev/null)" && [[ -n "$repo_path" ]]; then
|
|
echo ${repo_path:t}
|
|
fi
|
|
}
|