ohmyzsh/lib/git.zsh
Hemna 0b8a9d1de2 Added git_files_changed function
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-
2021-01-25 08:11:02 -05:00

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
}