/
scm-prompt.sh
249 lines (236 loc) · 7.81 KB
/
scm-prompt.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# Copyright (C) 2015 Facebook, Inc
# Maintained by Ryan McElroy <rm@fb.com>
#
# Inspiration and derivation from git-completion.bash by Shawn O. Pearce.
#
# Distributed under the GNU General Public License, version 2.0.
#
# ========================================================================
#
# Quickly determines the and emits some useful information about the state
# of your current mercurial or git repository. Useful for PS1 prompts.
#
# Design goals:
# * Useful for both git and mercurial
# * Portable to both zsh and bash
# * Portable to both Mac (BSD-based utils) and Linux (GNU-based utils)
# * As fast as possible given the above constraints (few command invocations)
# * Avoids invoking git or mercurial, which may be slow on large repositories
#
# To use from zsh:
#
# NOTE! the single quotes are important; if you use double quotes
# then the prompt won't change when you chdir or checkout different
# branches!
#
# setopt PROMPT_SUBST
# source /path/to/scm-prompt
# export PS1='$(_scm_prompt)$USER@%m:%~%% '
#
# To use from bash:
#
# source /path/to/scm-prompt
# export PS1="\$(_scm_prompt)\u@\h:\W\$ "
#
# NOTE! You *EITHER* need to single-quote the whole thing *OR* back-slash
# the $(...) (as above), but not both. Which one you use depends on if
# you need the rest of your PS1 to interpolate variables.
#
# You may additionally pass a format-string to the scm_info command. This
# allows you to control the format of the prompt string without interfering
# with the prompt outside of a mercurial or git repository. For example:
#
# $(_scm_prompt "%s")
#
# The default format string is " (%s)" (note the space)
#
# Options: you may want to set these environment variables
# * HOME_IS_NOT_A_REPO : Stop walking up the filesystem looking for a git or
# hg repo at (but not including) /home instead of /. This helps when /home
# is a remote or autofs mount point.
# * WANT_OLD_SCM_PROMPT : Use '%s' as the formatting for the prompt instead
# of ' (%s)'
#
# Notes to developers:
#
# * Aliases can screw up the default commands. To prevent this issue, use
# the 'builtin' prefix for built-in shell commands (eg, 'cd' and 'echo')
# and use the 'command' prefix for external commands that you do not want
# to invoke aliases for (eg, 'grep', 'cut').
#
# =========================================================================
#
_find_most_relevant() {
# We don't want to output all remote bookmarks because there can be many
# of them. This function finds the most relevant remote bookmark using this
# algorithm:
# 1. If 'master' or '@' bookmark is available then output it
# 2. Sort remote bookmarks and output the first in reverse sorted order (
# it's a heuristic that tries to find the newest bookmark. It will work well
# with bookmarks like 'release20160926' and 'release20161010').
relevantbook="$(command grep -m1 -E -o "^[^/]+/(master|@)$" <<< "$1")"
if [[ -n $relevantbook ]]; then
builtin echo $relevantbook
return 0
fi
builtin echo "$(command sort -r <<< "$1" | command head -n 1)"
}
_hg_prompt() {
local hg br extra
hg="$1"
if [[ -f "$hg/bisect.state" ]]; then
extra="|BISECT"
elif [[ -f "$hg/histedit-state" ]]; then
extra="|HISTEDIT"
elif [[ -f "$hg/graftstate" ]]; then
extra="|GRAFT"
elif [[ -f "$hg/unshelverebasestate" ]]; then
extra="|UNSHELVE"
elif [[ -f "$hg/rebasestate" ]]; then
extra="|REBASE"
elif [[ -d "$hg/merge" ]]; then
extra="|MERGE"
elif [[ -L "$hg/store/lock" ]]; then
extra="|STORE-LOCKED"
elif [[ -L "$hg/wlock" ]]; then
extra="|WDIR-LOCKED"
fi
local dirstate="$( \
( [[ -f "$hg/dirstate" ]] && \
command hexdump -vn 20 -e '1/1 "%02x"' "$hg/dirstate") || \
builtin echo "empty")"
local remote="$hg/remotenames"
local shared_hg="$hg"
if [[ -f "$hg/sharedpath" ]]; then
shared_hg="$(command cat $hg/sharedpath)"
remote="$shared_hg/remotenames"
fi
local active="$hg/bookmarks.current"
if [[ -f "$active" ]]; then
br="$(command cat "$active")"
# check to see if active bookmark needs update (eg, moved after pull)
local marks="$hg/bookmarks"
if [[ -f "$hg/sharedpath" && -f "$hg/shared" ]] &&
command grep -q '^bookmarks$' "$hg/shared"; then
marks="$shared_hg/bookmarks"
fi
if [[ -z "$extra" ]] && [[ -f "$marks" ]]; then
local markstate="$(command grep " $br$" "$marks" | \
command cut -f 1 -d ' ')"
if [[ $markstate != "$dirstate" ]]; then
extra="|UPDATE_NEEDED"
fi
fi
else
br="$(builtin echo "$dirstate" | command cut -c 1-7)"
fi
if [[ -f "$remote" ]]; then
local allremotemarks="$(command grep "^$dirstate bookmarks" "$remote" | \
command cut -f 3 -d ' ')"
if [[ -n "$allremotemarks" ]]; then
local remotemark="$(_find_most_relevant "$allremotemarks")"
if [[ -n "$remotemark" ]]; then
br="$br|$remotemark"
if [[ "$remotemark" != "$allremotemarks" ]]; then
# if there is more than one, let the user know with an elipsis
br="${br}..."
fi
fi
fi
fi
local branch
if [[ -f "$hg/branch" ]]; then
branch="$(command cat "$hg/branch")"
if [[ "$branch" != "default" ]]; then
br="$br|$branch"
fi
fi
br="$br$extra"
builtin printf "%s" "$br"
}
_git_prompt() {
local git br
git="$1"
if [[ -f "$git/HEAD" ]]; then
read br < "$git/HEAD"
case $br in
ref:\ refs/heads/*) br=${br#ref: refs/heads/} ;;
*) br="$(builtin echo "$br" | command cut -c 1-7)" ;;
esac
if [[ -f "$git/rebase-merge/interactive" ]]; then
b="$(command cat "$git/rebase-merge/head-name")"
b="${b#refs/heads/}"
br="$br|REBASE-i|$b"
elif [[ -d "$git/rebase-merge" ]]; then
b="$(command cat "$git/rebase-merge/head-name")"
b="${b#refs/heads/}"
br="$br|REBASE-m|$b"
else
if [[ -d "$git/rebase-apply" ]]; then
if [[ -f "$git/rebase-apply/rebasing" ]]; then
b="$(command cat "$git/rebase-apply/head-name")"
b="${b#refs/heads/}"
br="$br|REBASE|$b"
elif [[ -f "$git/rebase-apply/applying" ]]; then
br="$br|AM"
else
br="$br|AM/REBASE"
fi
elif [[ -f "$git/CHERRY_PICK_HEAD" ]]; then
br="$br|CHERRY-PICKING"
elif [[ -f "$git/REVERT_HEAD" ]]; then
br="$br|REVERTING"
elif [[ -f "$git/MERGE_HEAD" ]]; then
br="$br|MERGE"
elif [[ -f "$git/BISECT_LOG" ]]; then
br="$br|BISECT"
fi
fi
fi
builtin printf "%s" "$br"
}
_scm_prompt() {
local dir fmt br
# Default to be compatable with __git_ps1. In particular:
# - provide a space for the user so that they don't have to have
# random extra spaces in their prompt when not in a repo
# - provide parens so it's differentiated from other crap in their prompt
fmt="${1:- (%s)}"
# find out if we're in a git or hg repo by looking for the control dir
dir="$PWD"
while : ; do
[[ -n "$HOME_IS_NOT_A_REPO" ]] && [[ "$dir" = "/home" ]] && break
if [[ -d "$dir/.git" ]]; then
br="$(_git_prompt "$dir/.git")"
break
elif [[ -d "$dir/.hg" ]]; then
br="$(_hg_prompt "$dir/.hg")"
break
fi
[[ "$dir" = "/" ]] && break
# portable "realpath" equivalent
dir="$(builtin cd -P "$dir/.." && builtin echo "$PWD")"
done
if [[ -n "$br" ]]; then
builtin printf "$fmt" "$br"
fi
}
#
# Backwards-compatibility layer for odl scm-prompt script
#
# Older versions of this file at Facebook used a longer function name.
# These versions also included support for an environmental directive
# called $WANT_OLD_SCM_PROMPT. Support this to remain compatible.
#
_dotfiles_scm_info() {
local fmt
fmt=$1
if [[ -z "$fmt" ]]; then
if [[ -n "$WANT_OLD_SCM_PROMPT" ]]; then
fmt="%s"
else
fmt=' (%s)'
fi
fi
_scm_prompt "$fmt"
}