tools/git-prompt.sh
changeset 0 7215ca490221
equal deleted inserted replaced
-1:000000000000 0:7215ca490221
       
     1 # bash/zsh git prompt support
       
     2 #
       
     3 # Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
       
     4 # Distributed under the GNU General Public License, version 2.0.
       
     5 #
       
     6 # This script allows you to see the current branch in your prompt.
       
     7 #
       
     8 # To enable:
       
     9 #
       
    10 #    1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
       
    11 #    2) Add the following line to your .bashrc/.zshrc:
       
    12 #        source ~/.git-prompt.sh
       
    13 #    3) Change your PS1 to also show the current branch:
       
    14 #         Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
       
    15 #         ZSH:  PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
       
    16 #
       
    17 # The argument to __git_ps1 will be displayed only if you are currently
       
    18 # in a git repository.  The %s token will be the name of the current
       
    19 # branch.
       
    20 #
       
    21 # In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
       
    22 # unstaged (*) and staged (+) changes will be shown next to the branch
       
    23 # name.  You can configure this per-repository with the
       
    24 # bash.showDirtyState variable, which defaults to true once
       
    25 # GIT_PS1_SHOWDIRTYSTATE is enabled.
       
    26 #
       
    27 # You can also see if currently something is stashed, by setting
       
    28 # GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
       
    29 # then a '$' will be shown next to the branch name.
       
    30 #
       
    31 # If you would like to see if there're untracked files, then you can set
       
    32 # GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
       
    33 # files, then a '%' will be shown next to the branch name.
       
    34 #
       
    35 # If you would like to see the difference between HEAD and its upstream,
       
    36 # set GIT_PS1_SHOWUPSTREAM="auto".  A "<" indicates you are behind, ">"
       
    37 # indicates you are ahead, and "<>" indicates you have diverged.  You
       
    38 # can further control behaviour by setting GIT_PS1_SHOWUPSTREAM to a
       
    39 # space-separated list of values:
       
    40 #
       
    41 #     verbose       show number of commits ahead/behind (+/-) upstream
       
    42 #     legacy        don't use the '--count' option available in recent
       
    43 #                   versions of git-rev-list
       
    44 #     git           always compare HEAD to @{upstream}
       
    45 #     svn           always compare HEAD to your SVN upstream
       
    46 #
       
    47 # By default, __git_ps1 will compare HEAD to your SVN upstream if it can
       
    48 # find one, or @{upstream} otherwise.  Once you have set
       
    49 # GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
       
    50 # setting the bash.showUpstream config variable.
       
    51 
       
    52 # __gitdir accepts 0 or 1 arguments (i.e., location)
       
    53 # returns location of .git repo
       
    54 __gitdir ()
       
    55 {
       
    56 	# Note: this function is duplicated in git-completion.bash
       
    57 	# When updating it, make sure you update the other one to match.
       
    58 	if [ -z "${1-}" ]; then
       
    59 		if [ -n "${__git_dir-}" ]; then
       
    60 			echo "$__git_dir"
       
    61 		elif [ -n "${GIT_DIR-}" ]; then
       
    62 			test -d "${GIT_DIR-}" || return 1
       
    63 			echo "$GIT_DIR"
       
    64 		elif [ -d .git ]; then
       
    65 			echo .git
       
    66 		else
       
    67 			git rev-parse --git-dir 2>/dev/null
       
    68 		fi
       
    69 	elif [ -d "$1/.git" ]; then
       
    70 		echo "$1/.git"
       
    71 	else
       
    72 		echo "$1"
       
    73 	fi
       
    74 }
       
    75 
       
    76 # stores the divergence from upstream in $p
       
    77 # used by GIT_PS1_SHOWUPSTREAM
       
    78 __git_ps1_show_upstream ()
       
    79 {
       
    80 	local key value
       
    81 	local svn_remote svn_url_pattern count n
       
    82 	local upstream=git legacy="" verbose=""
       
    83 
       
    84 	svn_remote=()
       
    85 	# get some config options from git-config
       
    86 	local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
       
    87 	while read -r key value; do
       
    88 		case "$key" in
       
    89 		bash.showupstream)
       
    90 			GIT_PS1_SHOWUPSTREAM="$value"
       
    91 			if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
       
    92 				p=""
       
    93 				return
       
    94 			fi
       
    95 			;;
       
    96 		svn-remote.*.url)
       
    97 			svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
       
    98 			svn_url_pattern+="\\|$value"
       
    99 			upstream=svn+git # default upstream is SVN if available, else git
       
   100 			;;
       
   101 		esac
       
   102 	done <<< "$output"
       
   103 
       
   104 	# parse configuration values
       
   105 	for option in ${GIT_PS1_SHOWUPSTREAM}; do
       
   106 		case "$option" in
       
   107 		git|svn) upstream="$option" ;;
       
   108 		verbose) verbose=1 ;;
       
   109 		legacy)  legacy=1  ;;
       
   110 		esac
       
   111 	done
       
   112 
       
   113 	# Find our upstream
       
   114 	case "$upstream" in
       
   115 	git)    upstream="@{upstream}" ;;
       
   116 	svn*)
       
   117 		# get the upstream from the "git-svn-id: ..." in a commit message
       
   118 		# (git-svn uses essentially the same procedure internally)
       
   119 		local svn_upstream=($(git log --first-parent -1 \
       
   120 					--grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
       
   121 		if [[ 0 -ne ${#svn_upstream[@]} ]]; then
       
   122 			svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
       
   123 			svn_upstream=${svn_upstream%@*}
       
   124 			local n_stop="${#svn_remote[@]}"
       
   125 			for ((n=1; n <= n_stop; n++)); do
       
   126 				svn_upstream=${svn_upstream#${svn_remote[$n]}}
       
   127 			done
       
   128 
       
   129 			if [[ -z "$svn_upstream" ]]; then
       
   130 				# default branch name for checkouts with no layout:
       
   131 				upstream=${GIT_SVN_ID:-git-svn}
       
   132 			else
       
   133 				upstream=${svn_upstream#/}
       
   134 			fi
       
   135 		elif [[ "svn+git" = "$upstream" ]]; then
       
   136 			upstream="@{upstream}"
       
   137 		fi
       
   138 		;;
       
   139 	esac
       
   140 
       
   141 	# Find how many commits we are ahead/behind our upstream
       
   142 	if [[ -z "$legacy" ]]; then
       
   143 		count="$(git rev-list --count --left-right \
       
   144 				"$upstream"...HEAD 2>/dev/null)"
       
   145 	else
       
   146 		# produce equivalent output to --count for older versions of git
       
   147 		local commits
       
   148 		if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
       
   149 		then
       
   150 			local commit behind=0 ahead=0
       
   151 			for commit in $commits
       
   152 			do
       
   153 				case "$commit" in
       
   154 				"<"*) ((behind++)) ;;
       
   155 				*)    ((ahead++))  ;;
       
   156 				esac
       
   157 			done
       
   158 			count="$behind	$ahead"
       
   159 		else
       
   160 			count=""
       
   161 		fi
       
   162 	fi
       
   163 
       
   164 	# calculate the result
       
   165 	if [[ -z "$verbose" ]]; then
       
   166 		case "$count" in
       
   167 		"") # no upstream
       
   168 			p="" ;;
       
   169 		"0	0") # equal to upstream
       
   170 			p="=" ;;
       
   171 		"0	"*) # ahead of upstream
       
   172 			p=">" ;;
       
   173 		*"	0") # behind upstream
       
   174 			p="<" ;;
       
   175 		*)	    # diverged from upstream
       
   176 			p="<>" ;;
       
   177 		esac
       
   178 	else
       
   179 		case "$count" in
       
   180 		"") # no upstream
       
   181 			p="" ;;
       
   182 		"0	0") # equal to upstream
       
   183 			p=" u=" ;;
       
   184 		"0	"*) # ahead of upstream
       
   185 			p=" u+${count#0	}" ;;
       
   186 		*"	0") # behind upstream
       
   187 			p=" u-${count%	0}" ;;
       
   188 		*)	    # diverged from upstream
       
   189 			p=" u+${count#*	}-${count%	*}" ;;
       
   190 		esac
       
   191 	fi
       
   192 
       
   193 }
       
   194 
       
   195 
       
   196 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
       
   197 # returns text to add to bash PS1 prompt (includes branch name)
       
   198 __git_ps1 ()
       
   199 {
       
   200 	local g="$(__gitdir)"
       
   201 	if [ -n "$g" ]; then
       
   202 		local r=""
       
   203 		local b=""
       
   204 		if [ -f "$g/rebase-merge/interactive" ]; then
       
   205 			r="|REBASE-i"
       
   206 			b="$(cat "$g/rebase-merge/head-name")"
       
   207 		elif [ -d "$g/rebase-merge" ]; then
       
   208 			r="|REBASE-m"
       
   209 			b="$(cat "$g/rebase-merge/head-name")"
       
   210 		else
       
   211 			if [ -d "$g/rebase-apply" ]; then
       
   212 				if [ -f "$g/rebase-apply/rebasing" ]; then
       
   213 					r="|REBASE"
       
   214 				elif [ -f "$g/rebase-apply/applying" ]; then
       
   215 					r="|AM"
       
   216 				else
       
   217 					r="|AM/REBASE"
       
   218 				fi
       
   219 			elif [ -f "$g/MERGE_HEAD" ]; then
       
   220 				r="|MERGING"
       
   221 			elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
       
   222 				r="|CHERRY-PICKING"
       
   223 			elif [ -f "$g/BISECT_LOG" ]; then
       
   224 				r="|BISECTING"
       
   225 			fi
       
   226 
       
   227 			b="$(git symbolic-ref HEAD 2>/dev/null)" || {
       
   228 
       
   229 				b="$(
       
   230 				case "${GIT_PS1_DESCRIBE_STYLE-}" in
       
   231 				(contains)
       
   232 					git describe --contains HEAD ;;
       
   233 				(branch)
       
   234 					git describe --contains --all HEAD ;;
       
   235 				(describe)
       
   236 					git describe HEAD ;;
       
   237 				(* | default)
       
   238 					git describe --tags --exact-match HEAD ;;
       
   239 				esac 2>/dev/null)" ||
       
   240 
       
   241 				b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
       
   242 				b="unknown"
       
   243 				b="($b)"
       
   244 			}
       
   245 		fi
       
   246 
       
   247 		local w=""
       
   248 		local i=""
       
   249 		local s=""
       
   250 		local u=""
       
   251 		local c=""
       
   252 		local p=""
       
   253 
       
   254 		if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
       
   255 			if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
       
   256 				c="BARE:"
       
   257 			else
       
   258 				b="GIT_DIR!"
       
   259 			fi
       
   260 		elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
       
   261 			if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
       
   262 				if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
       
   263 					git diff --no-ext-diff --quiet --exit-code || w="*"
       
   264 					if git rev-parse --quiet --verify HEAD >/dev/null; then
       
   265 						git diff-index --cached --quiet HEAD -- || i="+"
       
   266 					else
       
   267 						i="#"
       
   268 					fi
       
   269 				fi
       
   270 			fi
       
   271 			if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
       
   272 				git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
       
   273 			fi
       
   274 
       
   275 			if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
       
   276 				if [ -n "$(git ls-files --others --exclude-standard)" ]; then
       
   277 					u="%"
       
   278 				fi
       
   279 			fi
       
   280 
       
   281 			if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
       
   282 				__git_ps1_show_upstream
       
   283 			fi
       
   284 		fi
       
   285 
       
   286 		local f="$w$i$s$u"
       
   287 		printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
       
   288 	fi
       
   289 }