mirror of
https://github.com/git/git.git
synced 2024-12-30 12:15:08 +08:00
ac3eb1c384
The __gitdir function is duplicated between completion and prompt scripts, and these definitions should not diverge; otherwise one of them can be subtly broken depending on the order the user's shell dot-sources them. Leave a note to people who may want to touch one copy to make sure they update the other one in sync. Hopefully this line would also appear in the context of the patch to allow reviewers to notice a patch that attempts to update only one of them. Signed-off-by: Junio C Hamano <gitster@pobox.com>
290 lines
8.0 KiB
Bash
290 lines
8.0 KiB
Bash
# bash/zsh git prompt support
|
|
#
|
|
# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
|
|
# Distributed under the GNU General Public License, version 2.0.
|
|
#
|
|
# This script allows you to see the current branch in your prompt.
|
|
#
|
|
# To enable:
|
|
#
|
|
# 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
|
|
# 2) Add the following line to your .bashrc/.zshrc:
|
|
# source ~/.git-prompt.sh
|
|
# 3) Change your PS1 to also show the current branch:
|
|
# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
|
|
# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
|
|
#
|
|
# The argument to __git_ps1 will be displayed only if you are currently
|
|
# in a git repository. The %s token will be the name of the current
|
|
# branch.
|
|
#
|
|
# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
|
|
# unstaged (*) and staged (+) changes will be shown next to the branch
|
|
# name. You can configure this per-repository with the
|
|
# bash.showDirtyState variable, which defaults to true once
|
|
# GIT_PS1_SHOWDIRTYSTATE is enabled.
|
|
#
|
|
# You can also see if currently something is stashed, by setting
|
|
# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
|
|
# then a '$' will be shown next to the branch name.
|
|
#
|
|
# If you would like to see if there're untracked files, then you can set
|
|
# GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
|
|
# files, then a '%' will be shown next to the branch name.
|
|
#
|
|
# If you would like to see the difference between HEAD and its upstream,
|
|
# set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates you are behind, ">"
|
|
# indicates you are ahead, and "<>" indicates you have diverged. You
|
|
# can further control behaviour by setting GIT_PS1_SHOWUPSTREAM to a
|
|
# space-separated list of values:
|
|
#
|
|
# verbose show number of commits ahead/behind (+/-) upstream
|
|
# legacy don't use the '--count' option available in recent
|
|
# versions of git-rev-list
|
|
# git always compare HEAD to @{upstream}
|
|
# svn always compare HEAD to your SVN upstream
|
|
#
|
|
# By default, __git_ps1 will compare HEAD to your SVN upstream if it can
|
|
# find one, or @{upstream} otherwise. Once you have set
|
|
# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
|
|
# setting the bash.showUpstream config variable.
|
|
|
|
# __gitdir accepts 0 or 1 arguments (i.e., location)
|
|
# returns location of .git repo
|
|
__gitdir ()
|
|
{
|
|
# Note: this function is duplicated in git-completion.bash
|
|
# When updating it, make sure you update the other one to match.
|
|
if [ -z "${1-}" ]; then
|
|
if [ -n "${__git_dir-}" ]; then
|
|
echo "$__git_dir"
|
|
elif [ -n "${GIT_DIR-}" ]; then
|
|
test -d "${GIT_DIR-}" || return 1
|
|
echo "$GIT_DIR"
|
|
elif [ -d .git ]; then
|
|
echo .git
|
|
else
|
|
git rev-parse --git-dir 2>/dev/null
|
|
fi
|
|
elif [ -d "$1/.git" ]; then
|
|
echo "$1/.git"
|
|
else
|
|
echo "$1"
|
|
fi
|
|
}
|
|
|
|
# stores the divergence from upstream in $p
|
|
# used by GIT_PS1_SHOWUPSTREAM
|
|
__git_ps1_show_upstream ()
|
|
{
|
|
local key value
|
|
local svn_remote svn_url_pattern count n
|
|
local upstream=git legacy="" verbose=""
|
|
|
|
svn_remote=()
|
|
# get some config options from git-config
|
|
local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
|
|
while read -r key value; do
|
|
case "$key" in
|
|
bash.showupstream)
|
|
GIT_PS1_SHOWUPSTREAM="$value"
|
|
if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
|
|
p=""
|
|
return
|
|
fi
|
|
;;
|
|
svn-remote.*.url)
|
|
svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
|
|
svn_url_pattern+="\\|$value"
|
|
upstream=svn+git # default upstream is SVN if available, else git
|
|
;;
|
|
esac
|
|
done <<< "$output"
|
|
|
|
# parse configuration values
|
|
for option in ${GIT_PS1_SHOWUPSTREAM}; do
|
|
case "$option" in
|
|
git|svn) upstream="$option" ;;
|
|
verbose) verbose=1 ;;
|
|
legacy) legacy=1 ;;
|
|
esac
|
|
done
|
|
|
|
# Find our upstream
|
|
case "$upstream" in
|
|
git) upstream="@{upstream}" ;;
|
|
svn*)
|
|
# get the upstream from the "git-svn-id: ..." in a commit message
|
|
# (git-svn uses essentially the same procedure internally)
|
|
local svn_upstream=($(git log --first-parent -1 \
|
|
--grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
|
|
if [[ 0 -ne ${#svn_upstream[@]} ]]; then
|
|
svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
|
|
svn_upstream=${svn_upstream%@*}
|
|
local n_stop="${#svn_remote[@]}"
|
|
for ((n=1; n <= n_stop; n++)); do
|
|
svn_upstream=${svn_upstream#${svn_remote[$n]}}
|
|
done
|
|
|
|
if [[ -z "$svn_upstream" ]]; then
|
|
# default branch name for checkouts with no layout:
|
|
upstream=${GIT_SVN_ID:-git-svn}
|
|
else
|
|
upstream=${svn_upstream#/}
|
|
fi
|
|
elif [[ "svn+git" = "$upstream" ]]; then
|
|
upstream="@{upstream}"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# Find how many commits we are ahead/behind our upstream
|
|
if [[ -z "$legacy" ]]; then
|
|
count="$(git rev-list --count --left-right \
|
|
"$upstream"...HEAD 2>/dev/null)"
|
|
else
|
|
# produce equivalent output to --count for older versions of git
|
|
local commits
|
|
if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
|
|
then
|
|
local commit behind=0 ahead=0
|
|
for commit in $commits
|
|
do
|
|
case "$commit" in
|
|
"<"*) ((behind++)) ;;
|
|
*) ((ahead++)) ;;
|
|
esac
|
|
done
|
|
count="$behind $ahead"
|
|
else
|
|
count=""
|
|
fi
|
|
fi
|
|
|
|
# calculate the result
|
|
if [[ -z "$verbose" ]]; then
|
|
case "$count" in
|
|
"") # no upstream
|
|
p="" ;;
|
|
"0 0") # equal to upstream
|
|
p="=" ;;
|
|
"0 "*) # ahead of upstream
|
|
p=">" ;;
|
|
*" 0") # behind upstream
|
|
p="<" ;;
|
|
*) # diverged from upstream
|
|
p="<>" ;;
|
|
esac
|
|
else
|
|
case "$count" in
|
|
"") # no upstream
|
|
p="" ;;
|
|
"0 0") # equal to upstream
|
|
p=" u=" ;;
|
|
"0 "*) # ahead of upstream
|
|
p=" u+${count#0 }" ;;
|
|
*" 0") # behind upstream
|
|
p=" u-${count% 0}" ;;
|
|
*) # diverged from upstream
|
|
p=" u+${count#* }-${count% *}" ;;
|
|
esac
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
|
|
# returns text to add to bash PS1 prompt (includes branch name)
|
|
__git_ps1 ()
|
|
{
|
|
local g="$(__gitdir)"
|
|
if [ -n "$g" ]; then
|
|
local r=""
|
|
local b=""
|
|
if [ -f "$g/rebase-merge/interactive" ]; then
|
|
r="|REBASE-i"
|
|
b="$(cat "$g/rebase-merge/head-name")"
|
|
elif [ -d "$g/rebase-merge" ]; then
|
|
r="|REBASE-m"
|
|
b="$(cat "$g/rebase-merge/head-name")"
|
|
else
|
|
if [ -d "$g/rebase-apply" ]; then
|
|
if [ -f "$g/rebase-apply/rebasing" ]; then
|
|
r="|REBASE"
|
|
elif [ -f "$g/rebase-apply/applying" ]; then
|
|
r="|AM"
|
|
else
|
|
r="|AM/REBASE"
|
|
fi
|
|
elif [ -f "$g/MERGE_HEAD" ]; then
|
|
r="|MERGING"
|
|
elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
|
|
r="|CHERRY-PICKING"
|
|
elif [ -f "$g/BISECT_LOG" ]; then
|
|
r="|BISECTING"
|
|
fi
|
|
|
|
b="$(git symbolic-ref HEAD 2>/dev/null)" || {
|
|
|
|
b="$(
|
|
case "${GIT_PS1_DESCRIBE_STYLE-}" in
|
|
(contains)
|
|
git describe --contains HEAD ;;
|
|
(branch)
|
|
git describe --contains --all HEAD ;;
|
|
(describe)
|
|
git describe HEAD ;;
|
|
(* | default)
|
|
git describe --tags --exact-match HEAD ;;
|
|
esac 2>/dev/null)" ||
|
|
|
|
b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
|
|
b="unknown"
|
|
b="($b)"
|
|
}
|
|
fi
|
|
|
|
local w=""
|
|
local i=""
|
|
local s=""
|
|
local u=""
|
|
local c=""
|
|
local p=""
|
|
|
|
if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
|
|
if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
|
|
c="BARE:"
|
|
else
|
|
b="GIT_DIR!"
|
|
fi
|
|
elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
|
|
if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
|
|
if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
|
|
git diff --no-ext-diff --quiet --exit-code || w="*"
|
|
if git rev-parse --quiet --verify HEAD >/dev/null; then
|
|
git diff-index --cached --quiet HEAD -- || i="+"
|
|
else
|
|
i="#"
|
|
fi
|
|
fi
|
|
fi
|
|
if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
|
|
git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
|
|
fi
|
|
|
|
if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
|
|
if [ -n "$(git ls-files --others --exclude-standard)" ]; then
|
|
u="%"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
|
|
__git_ps1_show_upstream
|
|
fi
|
|
fi
|
|
|
|
local f="$w$i$s$u"
|
|
printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
|
|
fi
|
|
}
|