Browse Source

bash git-prompt

master
Robin 9 years ago
parent
commit
d2aceb31ec
2 changed files with 533 additions and 1 deletions
  1. 532
    0
      bash/git-prompt.sh
  2. 1
    1
      bash/install

+ 532
- 0
bash/git-prompt.sh View File

@@ -0,0 +1,532 @@
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 repository status 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
+#    3a) Change your PS1 to call __git_ps1 as
14
+#        command-substitution:
15
+#        Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
16
+#        ZSH:  setopt PROMPT_SUBST ; PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
17
+#        the optional argument will be used as format string.
18
+#    3b) Alternatively, for a slightly faster prompt, __git_ps1 can
19
+#        be used for PROMPT_COMMAND in Bash or for precmd() in Zsh
20
+#        with two parameters, <pre> and <post>, which are strings
21
+#        you would put in $PS1 before and after the status string
22
+#        generated by the git-prompt machinery.  e.g.
23
+#        Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
24
+#          will show username, at-sign, host, colon, cwd, then
25
+#          various status string, followed by dollar and SP, as
26
+#          your prompt.
27
+#        ZSH:  precmd () { __git_ps1 "%n" ":%~$ " "|%s" }
28
+#          will show username, pipe, then various status string,
29
+#          followed by colon, cwd, dollar and SP, as your prompt.
30
+#        Optionally, you can supply a third argument with a printf
31
+#        format string to finetune the output of the branch status
32
+#
33
+# The repository status will be displayed only if you are currently in a
34
+# git repository. The %s token is the placeholder for the shown status.
35
+#
36
+# The prompt status always includes the current branch name.
37
+#
38
+# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
39
+# unstaged (*) and staged (+) changes will be shown next to the branch
40
+# name.  You can configure this per-repository with the
41
+# bash.showDirtyState variable, which defaults to true once
42
+# GIT_PS1_SHOWDIRTYSTATE is enabled.
43
+#
44
+# You can also see if currently something is stashed, by setting
45
+# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
46
+# then a '$' will be shown next to the branch name.
47
+#
48
+# If you would like to see if there're untracked files, then you can set
49
+# GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
50
+# files, then a '%' will be shown next to the branch name.  You can
51
+# configure this per-repository with the bash.showUntrackedFiles
52
+# variable, which defaults to true once GIT_PS1_SHOWUNTRACKEDFILES is
53
+# enabled.
54
+#
55
+# If you would like to see the difference between HEAD and its upstream,
56
+# set GIT_PS1_SHOWUPSTREAM="auto".  A "<" indicates you are behind, ">"
57
+# indicates you are ahead, "<>" indicates you have diverged and "="
58
+# indicates that there is no difference. You can further control
59
+# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated list
60
+# of values:
61
+#
62
+#     verbose       show number of commits ahead/behind (+/-) upstream
63
+#     name          if verbose, then also show the upstream abbrev name
64
+#     legacy        don't use the '--count' option available in recent
65
+#                   versions of git-rev-list
66
+#     git           always compare HEAD to @{upstream}
67
+#     svn           always compare HEAD to your SVN upstream
68
+#
69
+# You can change the separator between the branch name and the above
70
+# state symbols by setting GIT_PS1_STATESEPARATOR. The default separator
71
+# is SP.
72
+#
73
+# By default, __git_ps1 will compare HEAD to your SVN upstream if it can
74
+# find one, or @{upstream} otherwise.  Once you have set
75
+# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
76
+# setting the bash.showUpstream config variable.
77
+#
78
+# If you would like to see more information about the identity of
79
+# commits checked out as a detached HEAD, set GIT_PS1_DESCRIBE_STYLE
80
+# to one of these values:
81
+#
82
+#     contains      relative to newer annotated tag (v1.6.3.2~35)
83
+#     branch        relative to newer tag or branch (master~4)
84
+#     describe      relative to older annotated tag (v1.6.3.1-13-gdd42c2f)
85
+#     default       exactly matching tag
86
+#
87
+# If you would like a colored hint about the current dirty state, set
88
+# GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on
89
+# the colored output of "git status -sb" and are available only when
90
+# using __git_ps1 for PROMPT_COMMAND or precmd.
91
+#
92
+# If you would like __git_ps1 to do nothing in the case when the current
93
+# directory is set up to be ignored by git, then set
94
+# GIT_PS1_HIDE_IF_PWD_IGNORED to a nonempty value. Override this on the
95
+# repository level by setting bash.hideIfPwdIgnored to "false".
96
+
97
+# check whether printf supports -v
98
+__git_printf_supports_v=
99
+printf -v __git_printf_supports_v -- '%s' yes >/dev/null 2>&1
100
+
101
+# stores the divergence from upstream in $p
102
+# used by GIT_PS1_SHOWUPSTREAM
103
+__git_ps1_show_upstream ()
104
+{
105
+	local key value
106
+	local svn_remote svn_url_pattern count n
107
+	local upstream=git legacy="" verbose="" name=""
108
+
109
+	svn_remote=()
110
+	# get some config options from git-config
111
+	local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
112
+	while read -r key value; do
113
+		case "$key" in
114
+		bash.showupstream)
115
+			GIT_PS1_SHOWUPSTREAM="$value"
116
+			if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
117
+				p=""
118
+				return
119
+			fi
120
+			;;
121
+		svn-remote.*.url)
122
+			svn_remote[$((${#svn_remote[@]} + 1))]="$value"
123
+			svn_url_pattern="$svn_url_pattern\\|$value"
124
+			upstream=svn+git # default upstream is SVN if available, else git
125
+			;;
126
+		esac
127
+	done <<< "$output"
128
+
129
+	# parse configuration values
130
+	for option in ${GIT_PS1_SHOWUPSTREAM}; do
131
+		case "$option" in
132
+		git|svn) upstream="$option" ;;
133
+		verbose) verbose=1 ;;
134
+		legacy)  legacy=1  ;;
135
+		name)    name=1 ;;
136
+		esac
137
+	done
138
+
139
+	# Find our upstream
140
+	case "$upstream" in
141
+	git)    upstream="@{upstream}" ;;
142
+	svn*)
143
+		# get the upstream from the "git-svn-id: ..." in a commit message
144
+		# (git-svn uses essentially the same procedure internally)
145
+		local -a svn_upstream
146
+		svn_upstream=($(git log --first-parent -1 \
147
+					--grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
148
+		if [[ 0 -ne ${#svn_upstream[@]} ]]; then
149
+			svn_upstream=${svn_upstream[${#svn_upstream[@]} - 2]}
150
+			svn_upstream=${svn_upstream%@*}
151
+			local n_stop="${#svn_remote[@]}"
152
+			for ((n=1; n <= n_stop; n++)); do
153
+				svn_upstream=${svn_upstream#${svn_remote[$n]}}
154
+			done
155
+
156
+			if [[ -z "$svn_upstream" ]]; then
157
+				# default branch name for checkouts with no layout:
158
+				upstream=${GIT_SVN_ID:-git-svn}
159
+			else
160
+				upstream=${svn_upstream#/}
161
+			fi
162
+		elif [[ "svn+git" = "$upstream" ]]; then
163
+			upstream="@{upstream}"
164
+		fi
165
+		;;
166
+	esac
167
+
168
+	# Find how many commits we are ahead/behind our upstream
169
+	if [[ -z "$legacy" ]]; then
170
+		count="$(git rev-list --count --left-right \
171
+				"$upstream"...HEAD 2>/dev/null)"
172
+	else
173
+		# produce equivalent output to --count for older versions of git
174
+		local commits
175
+		if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
176
+		then
177
+			local commit behind=0 ahead=0
178
+			for commit in $commits
179
+			do
180
+				case "$commit" in
181
+				"<"*) ((behind++)) ;;
182
+				*)    ((ahead++))  ;;
183
+				esac
184
+			done
185
+			count="$behind	$ahead"
186
+		else
187
+			count=""
188
+		fi
189
+	fi
190
+
191
+	# calculate the result
192
+	if [[ -z "$verbose" ]]; then
193
+		case "$count" in
194
+		"") # no upstream
195
+			p="" ;;
196
+		"0	0") # equal to upstream
197
+			p="=" ;;
198
+		"0	"*) # ahead of upstream
199
+			p=">" ;;
200
+		*"	0") # behind upstream
201
+			p="<" ;;
202
+		*)	    # diverged from upstream
203
+			p="<>" ;;
204
+		esac
205
+	else
206
+		case "$count" in
207
+		"") # no upstream
208
+			p="" ;;
209
+		"0	0") # equal to upstream
210
+			p=" u=" ;;
211
+		"0	"*) # ahead of upstream
212
+			p=" u+${count#0	}" ;;
213
+		*"	0") # behind upstream
214
+			p=" u-${count%	0}" ;;
215
+		*)	    # diverged from upstream
216
+			p=" u+${count#*	}-${count%	*}" ;;
217
+		esac
218
+		if [[ -n "$count" && -n "$name" ]]; then
219
+			__git_ps1_upstream_name=$(git rev-parse \
220
+				--abbrev-ref "$upstream" 2>/dev/null)
221
+			if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
222
+				p="$p \${__git_ps1_upstream_name}"
223
+			else
224
+				p="$p ${__git_ps1_upstream_name}"
225
+				# not needed anymore; keep user's
226
+				# environment clean
227
+				unset __git_ps1_upstream_name
228
+			fi
229
+		fi
230
+	fi
231
+
232
+}
233
+
234
+# Helper function that is meant to be called from __git_ps1.  It
235
+# injects color codes into the appropriate gitstring variables used
236
+# to build a gitstring.
237
+__git_ps1_colorize_gitstring ()
238
+{
239
+	if [[ -n ${ZSH_VERSION-} ]]; then
240
+		local c_red='%F{red}'
241
+		local c_green='%F{green}'
242
+		local c_lblue='%F{blue}'
243
+		local c_clear='%f'
244
+	else
245
+		# Using \[ and \] around colors is necessary to prevent
246
+		# issues with command line editing/browsing/completion!
247
+		local c_red='\[\e[31m\]'
248
+		local c_green='\[\e[32m\]'
249
+		local c_lblue='\[\e[1;34m\]'
250
+		local c_clear='\[\e[0m\]'
251
+	fi
252
+	local bad_color=$c_red
253
+	local ok_color=$c_green
254
+	local flags_color="$c_lblue"
255
+
256
+	local branch_color=""
257
+	if [ $detached = no ]; then
258
+		branch_color="$ok_color"
259
+	else
260
+		branch_color="$bad_color"
261
+	fi
262
+	c="$branch_color$c"
263
+
264
+	z="$c_clear$z"
265
+	if [ "$w" = "*" ]; then
266
+		w="$bad_color$w"
267
+	fi
268
+	if [ -n "$i" ]; then
269
+		i="$ok_color$i"
270
+	fi
271
+	if [ -n "$s" ]; then
272
+		s="$flags_color$s"
273
+	fi
274
+	if [ -n "$u" ]; then
275
+		u="$bad_color$u"
276
+	fi
277
+	r="$c_clear$r"
278
+}
279
+
280
+__git_eread ()
281
+{
282
+	local f="$1"
283
+	shift
284
+	test -r "$f" && read "$@" <"$f"
285
+}
286
+
287
+# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
288
+# when called from PS1 using command substitution
289
+# in this mode it prints text to add to bash PS1 prompt (includes branch name)
290
+#
291
+# __git_ps1 requires 2 or 3 arguments when called from PROMPT_COMMAND (pc)
292
+# in that case it _sets_ PS1. The arguments are parts of a PS1 string.
293
+# when two arguments are given, the first is prepended and the second appended
294
+# to the state string when assigned to PS1.
295
+# The optional third parameter will be used as printf format string to further
296
+# customize the output of the git-status string.
297
+# In this mode you can request colored hints using GIT_PS1_SHOWCOLORHINTS=true
298
+__git_ps1 ()
299
+{
300
+	# preserve exit status
301
+	local exit=$?
302
+	local pcmode=no
303
+	local detached=no
304
+	local ps1pc_start='\u@\h:\w '
305
+	local ps1pc_end='\$ '
306
+	local printf_format=' (%s)'
307
+
308
+	case "$#" in
309
+		2|3)	pcmode=yes
310
+			ps1pc_start="$1"
311
+			ps1pc_end="$2"
312
+			printf_format="${3:-$printf_format}"
313
+			# set PS1 to a plain prompt so that we can
314
+			# simply return early if the prompt should not
315
+			# be decorated
316
+			PS1="$ps1pc_start$ps1pc_end"
317
+		;;
318
+		0|1)	printf_format="${1:-$printf_format}"
319
+		;;
320
+		*)	return $exit
321
+		;;
322
+	esac
323
+
324
+	# ps1_expanded:  This variable is set to 'yes' if the shell
325
+	# subjects the value of PS1 to parameter expansion:
326
+	#
327
+	#   * bash does unless the promptvars option is disabled
328
+	#   * zsh does not unless the PROMPT_SUBST option is set
329
+	#   * POSIX shells always do
330
+	#
331
+	# If the shell would expand the contents of PS1 when drawing
332
+	# the prompt, a raw ref name must not be included in PS1.
333
+	# This protects the user from arbitrary code execution via
334
+	# specially crafted ref names.  For example, a ref named
335
+	# 'refs/heads/$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' might cause the
336
+	# shell to execute 'sudo rm -rf /' when the prompt is drawn.
337
+	#
338
+	# Instead, the ref name should be placed in a separate global
339
+	# variable (in the __git_ps1_* namespace to avoid colliding
340
+	# with the user's environment) and that variable should be
341
+	# referenced from PS1.  For example:
342
+	#
343
+	#     __git_ps1_foo=$(do_something_to_get_ref_name)
344
+	#     PS1="...stuff...\${__git_ps1_foo}...stuff..."
345
+	#
346
+	# If the shell does not expand the contents of PS1, the raw
347
+	# ref name must be included in PS1.
348
+	#
349
+	# The value of this variable is only relevant when in pcmode.
350
+	#
351
+	# Assume that the shell follows the POSIX specification and
352
+	# expands PS1 unless determined otherwise.  (This is more
353
+	# likely to be correct if the user has a non-bash, non-zsh
354
+	# shell and safer than the alternative if the assumption is
355
+	# incorrect.)
356
+	#
357
+	local ps1_expanded=yes
358
+	[ -z "$ZSH_VERSION" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
359
+	[ -z "$BASH_VERSION" ] || shopt -q promptvars || ps1_expanded=no
360
+
361
+	local repo_info rev_parse_exit_code
362
+	repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
363
+		--is-bare-repository --is-inside-work-tree \
364
+		--short HEAD 2>/dev/null)"
365
+	rev_parse_exit_code="$?"
366
+
367
+	if [ -z "$repo_info" ]; then
368
+		return $exit
369
+	fi
370
+
371
+	local short_sha
372
+	if [ "$rev_parse_exit_code" = "0" ]; then
373
+		short_sha="${repo_info##*$'\n'}"
374
+		repo_info="${repo_info%$'\n'*}"
375
+	fi
376
+	local inside_worktree="${repo_info##*$'\n'}"
377
+	repo_info="${repo_info%$'\n'*}"
378
+	local bare_repo="${repo_info##*$'\n'}"
379
+	repo_info="${repo_info%$'\n'*}"
380
+	local inside_gitdir="${repo_info##*$'\n'}"
381
+	local g="${repo_info%$'\n'*}"
382
+
383
+	if [ "true" = "$inside_worktree" ] &&
384
+	   [ -n "${GIT_PS1_HIDE_IF_PWD_IGNORED-}" ] &&
385
+	   [ "$(git config --bool bash.hideIfPwdIgnored)" != "false" ] &&
386
+	   git check-ignore -q .
387
+	then
388
+		return $exit
389
+	fi
390
+
391
+	local r=""
392
+	local b=""
393
+	local step=""
394
+	local total=""
395
+	if [ -d "$g/rebase-merge" ]; then
396
+		__git_eread "$g/rebase-merge/head-name" b
397
+		__git_eread "$g/rebase-merge/msgnum" step
398
+		__git_eread "$g/rebase-merge/end" total
399
+		if [ -f "$g/rebase-merge/interactive" ]; then
400
+			r="|REBASE-i"
401
+		else
402
+			r="|REBASE-m"
403
+		fi
404
+	else
405
+		if [ -d "$g/rebase-apply" ]; then
406
+			__git_eread "$g/rebase-apply/next" step
407
+			__git_eread "$g/rebase-apply/last" total
408
+			if [ -f "$g/rebase-apply/rebasing" ]; then
409
+				__git_eread "$g/rebase-apply/head-name" b
410
+				r="|REBASE"
411
+			elif [ -f "$g/rebase-apply/applying" ]; then
412
+				r="|AM"
413
+			else
414
+				r="|AM/REBASE"
415
+			fi
416
+		elif [ -f "$g/MERGE_HEAD" ]; then
417
+			r="|MERGING"
418
+		elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
419
+			r="|CHERRY-PICKING"
420
+		elif [ -f "$g/REVERT_HEAD" ]; then
421
+			r="|REVERTING"
422
+		elif [ -f "$g/BISECT_LOG" ]; then
423
+			r="|BISECTING"
424
+		fi
425
+
426
+		if [ -n "$b" ]; then
427
+			:
428
+		elif [ -h "$g/HEAD" ]; then
429
+			# symlink symbolic ref
430
+			b="$(git symbolic-ref HEAD 2>/dev/null)"
431
+		else
432
+			local head=""
433
+			if ! __git_eread "$g/HEAD" head; then
434
+				return $exit
435
+			fi
436
+			# is it a symbolic ref?
437
+			b="${head#ref: }"
438
+			if [ "$head" = "$b" ]; then
439
+				detached=yes
440
+				b="$(
441
+				case "${GIT_PS1_DESCRIBE_STYLE-}" in
442
+				(contains)
443
+					git describe --contains HEAD ;;
444
+				(branch)
445
+					git describe --contains --all HEAD ;;
446
+				(describe)
447
+					git describe HEAD ;;
448
+				(* | default)
449
+					git describe --tags --exact-match HEAD ;;
450
+				esac 2>/dev/null)" ||
451
+
452
+				b="$short_sha..."
453
+				b="($b)"
454
+			fi
455
+		fi
456
+	fi
457
+
458
+	if [ -n "$step" ] && [ -n "$total" ]; then
459
+		r="$r $step/$total"
460
+	fi
461
+
462
+	local w=""
463
+	local i=""
464
+	local s=""
465
+	local u=""
466
+	local c=""
467
+	local p=""
468
+
469
+	if [ "true" = "$inside_gitdir" ]; then
470
+		if [ "true" = "$bare_repo" ]; then
471
+			c="BARE:"
472
+		else
473
+			b="GIT_DIR!"
474
+		fi
475
+	elif [ "true" = "$inside_worktree" ]; then
476
+		if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
477
+		   [ "$(git config --bool bash.showDirtyState)" != "false" ]
478
+		then
479
+			git diff --no-ext-diff --quiet --exit-code || w="*"
480
+			if [ -n "$short_sha" ]; then
481
+				git diff-index --cached --quiet HEAD -- || i="+"
482
+			else
483
+				i="#"
484
+			fi
485
+		fi
486
+		if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] &&
487
+		   git rev-parse --verify --quiet refs/stash >/dev/null
488
+		then
489
+			s="$"
490
+		fi
491
+
492
+		if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
493
+		   [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
494
+		   git ls-files --others --exclude-standard --error-unmatch -- ':/*' >/dev/null 2>/dev/null
495
+		then
496
+			u="%${ZSH_VERSION+%}"
497
+		fi
498
+
499
+		if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
500
+			__git_ps1_show_upstream
501
+		fi
502
+	fi
503
+
504
+	local z="${GIT_PS1_STATESEPARATOR-" "}"
505
+
506
+	# NO color option unless in PROMPT_COMMAND mode
507
+	if [ $pcmode = yes ] && [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
508
+		__git_ps1_colorize_gitstring
509
+	fi
510
+
511
+	b=${b##refs/heads/}
512
+	if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
513
+		__git_ps1_branch_name=$b
514
+		b="\${__git_ps1_branch_name}"
515
+	fi
516
+
517
+	local f="$w$i$s$u"
518
+	local gitstring="$c$b${f:+$z$f}$r$p"
519
+
520
+	if [ $pcmode = yes ]; then
521
+		if [ "${__git_printf_supports_v-}" != yes ]; then
522
+			gitstring=$(printf -- "$printf_format" "$gitstring")
523
+		else
524
+			printf -v gitstring -- "$printf_format" "$gitstring"
525
+		fi
526
+		PS1="$ps1pc_start$gitstring$ps1pc_end"
527
+	else
528
+		printf -- "$printf_format" "$gitstring"
529
+	fi
530
+
531
+	return $exit
532
+}

+ 1
- 1
bash/install View File

@@ -1,6 +1,6 @@
1 1
 #! /usr/bin/env bash
2 2
 
3
-for file in bash_aliases bash_preexec bash_preexec_hooks bashrc
3
+for file in bash_aliases bash_preexec bash_preexec_hooks bashrc git-prompt.sh
4 4
 do
5 5
   ln -sf "$PWD"/$file ~/.$file
6 6
 done

Loading…
Cancel
Save