mirror of
https://github.com/ohmyzsh/ohmyzsh.git
synced 2024-11-19 21:41:07 +01:00
0b8a9d1de2
This patch adds the git library function for building a short string that represents the output of git diff --numstat. This gives theme designers the ability to add the Number of files changed, number of lines added and number of lines removed in their prompt for a git repository, while the user's current working directory is in the git repo. The new function supports prefix and suffix for each of the options, files changed, lines added and lines removed. To colorize the output, you can do something like this in your theme file ZSH_THEME_GIT_FILES_CHANGED_PREFIX="%{$fg[yellow]%}" ZSH_THEME_GIT_FILES_CHANGED_SUFFIX="%{$reset_color%}" ZSH_THEME_GIT_LINES_ADDED_PREFIX="%{$fg[green]%}" ZSH_THEME_GIT_LINES_ADDED_SUFFIX="%{$reset_color%}" ZSH_THEME_GIT_LINES_REMOVED_PREFIX="%{$fg[red]%}" ZSH_THEME_GIT_LINES_REMOVED_SUFFIX="%{$fg[blue]%}" For example, if there are 3 files changed, 10 lines added and no lines removed you will get 3f:10+ 1 files changed, 20 lines added and 6 lines removed: 1f:20+:6-
311 lines
11 KiB
Bash
311 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 git_prompt_info() {
|
|
# 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
|
|
|
|
local ref
|
|
ref=$(__git_prompt_git symbolic-ref --short 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}${upstream}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}"
|
|
}
|
|
|
|
# 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 -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$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="$(__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
|
|
}
|
|
|
|
# Outputs 3 stats for git repo
|
|
# 1) number of files changed,
|
|
# 2) the total number of lines added
|
|
# 3) total number of lines removed
|
|
#
|
|
# Example
|
|
# 3f:69+:6-
|
|
function git_files_changed() {
|
|
local -i files=0
|
|
local -i insertions=0
|
|
local -i deletions=0
|
|
local raw=$(command git diff --numstat 2>/dev/null) || return 0
|
|
if [[ -n $raw ]]; then
|
|
echo $raw | while IFS= read -r line; do
|
|
local -i d=$line[(w)2]
|
|
local -i i=$line[(w)1]
|
|
insertions+=i
|
|
deletions+=d
|
|
files+=1
|
|
done
|
|
local output="$ZSH_THEME_GIT_FILES_CHANGED_PREFIX${files}f$ZSH_THEME_GIT_FILES_CHANGED_SUFFIX"
|
|
if (( $insertions > 0 )); then
|
|
output="$output:$ZSH_THEME_GIT_LINES_ADDED_PREFIX${insertions}+$ZSH_THEME_GIT_LINES_ADDED_SUFFIX"
|
|
fi
|
|
if (( $deletions > 0 )); then
|
|
output="$output:$ZSH_THEME_GIT_LINES_REMOVED_PREFIX${deletions}-$ZSH_THEME_GIT_LINES_REMOVED_SUFFIX"
|
|
fi
|
|
echo $output
|
|
fi
|
|
}
|