blob: 6a4e8db6ef11622152d4608d0329b00d924b7c4f (
plain) (
blame)
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
250
251
252
253
254
255
|
[[ -o interactive ]] || return
zmodload zsh/datetime || return
# Retrives status of a git repo from a directory under its working tree.
#
# -d STR Directory to query. Defaults to $PWD. Must be absolute.
# -c STR Callback function to call once the results are available. Called only after
# gitstatus_query returns 0 with VCS_STATUS_RESULT=tout.
# -t FLOAT Timeout in seconds. Will block for at most this long. If no results are
# available by then: if -c isn't specified, will return 1; otherwise will set
# VCS_STATUS_RESULT=tout and return 0.
#
# On success sets VCS_STATUS_RESULT to one of the following values:
#
# tout Timed out waiting for data; will call the user-specified callback later.
# norepo-sync The directory isn't a git repo.
# ok-sync The directory is a git repo.
#
# When the callback is called VCS_STATUS_RESULT s set to one of the following values:
#
# norepo-async The directory isn't a git repo.
# ok-async The directory is a git repo.
#
# If VCS_STATUS_RESULT is ok-sync or ok-async, additional variables are set:
#
# VCS_STATUS_LOCAL_BRANCH Local branch name. Not empty.
# VCS_STATUS_REMOTE_BRANCH Upstream branch name. Can be empty.
# VCS_STATUS_REMOTE_URL Remote URL. Can be empty.
# VCS_STATUS_ACTION Repository state, A.K.A. action. Can be empty.
# VCS_STATUS_HAS_STAGED 1 if there are staged changes, 0 otherwise.
# VCS_STATUS_HAS_UNSTAGED 1 if there are unstaged changes, 0 if there aren't, -1 if unknown.
# VCS_STATUS_HAS_UNTRACKED 1 if there are untracked files, 0 if there aren't, -1 if unknown.
# VCS_STATUS_COMMITS_AHEAD Number of commits the current branch is ahead of upstream.
# Non-negative integer.
# VCS_STATUS_COMMITS_BEHIND Number of commits the current branch is behind upstream. Non-negative
# integer.
# VCS_STATUS_STASHES Number of stashes. Non-negative integer.
# VCS_STATUS_WORKDIR Git repo working directory. Not empty.
# VCS_STATUS_ALL All of the above in an array. The order of elements is unspecified.
# More elements can be added in the future.
#
# The point of reporting -1 as unstaged and untracked is to allow the command to skip scanning
# files in large repos. See -m flag of gitstatus_start.
#
# !!!!! WARNING: CONCURRENT CALLS WITH THE SAME NAME ARE NOT ALLOWED !!!!!
#
# It's illegal to call `gitstatus_query NAME` if `gitstatus_query NAME` is running concurrently in
# the same interactive shell or its child, or if the callback for the last timed out request hasn't
# fired yet. If you need to issue concurrent requests, use different NAME arguments.
function gitstatus_query() {
emulate -L zsh
setopt err_return no_unset
local opt
local dir=$PWD
local callback=''
local -F timeout=-1
while true; do
getopts "d:c:t:" opt || break
case $opt in
d) dir=$OPTARG;;
c) callback=$OPTARG;;
t) timeout=$OPTARG;;
?) return 1;;
done) break;;
esac
done
(( OPTIND == ARGC )) || { echo "usage: gitstatus_query [OPTION]... NAME" >&2; return 1 }
local name=${*[$OPTIND]}
[[ -v GITSTATUS_DAEMON_PID_${name} ]]
local -i req_fd
local req_fd_var=_GITSTATUS_REQ_FD_${name}
local client_pid_var=_GITSTATUS_CLIENT_PID_${name}
[[ ${(P)client_pid_var} == $$ ]] && {
req_fd=${(P)req_fd_var}
} || {
local req_fifo_var=_GITSTATUS_REQ_FIFO_${name}
local resp_fifo_var=_GITSTATUS_RESP_FIFO_${name}
local resp_fd_var=_GITSTATUS_RESP_FD_${name}
local -i resp_fd
exec {req_fd}<>${(P)req_fifo_var}
exec {resp_fd}<>${(P)resp_fifo_var}
typeset -g $client_pid_var=$$
typeset -g $req_fd_var=$req_fd
typeset -g $resp_fd_var=$resp_fd
typeset -giH $client_pid_var=$client_pid
typeset -gfH _gitstatus_process_response_${name}() {
_gitstatus_process_response ${0#_gitstatus_process_response_} 0 ''
}
zle -F $resp_fd _gitstatus_process_response_${name}
}
local -r req_id="$EPOCHREALTIME"
echo -nE "${req_id} ${callback}"$'\x1f'"${dir}"$'\x1e' >&$req_fd
while true; do
_gitstatus_process_response $name $timeout $req_id
[[ $VCS_STATUS_RESULT == *-async ]] || break
done
}
typeset -fH _gitstatus_process_response() {
emulate -L zsh
setopt err_return no_unset
local name=$1
local -F timeout=$2
local req_id=$3
local resp_fd_var=_GITSTATUS_RESP_FD_${name}
typeset -ga VCS_STATUS_ALL
typeset -g VCS_STATUS_RESULT
(( timeout >= 0 )) && local -a t=(-t $timeout) || local -a t=()
IFS=$'\x1f' read -rd $'\x1e' -u ${(P)resp_fd_var} $t -A VCS_STATUS_ALL || {
VCS_STATUS_RESULT=tout
unset VCS_STATUS_ALL
return
}
local -a header=("${(@Q)${(z)VCS_STATUS_ALL[1]}}")
[[ ${header[1]} == $req_id ]] && local -i ours=1 || local -i ours=0
shift header
[[ ${VCS_STATUS_ALL[2]} == 1 ]] && {
shift 2 VCS_STATUS_ALL
(( ours )) && VCS_STATUS_RESULT=ok-sync || VCS_STATUS_RESULT=ok-async
typeset -g VCS_STATUS_LOCAL_BRANCH="${VCS_STATUS_ALL[1]}"
typeset -g VCS_STATUS_REMOTE_BRANCH="${VCS_STATUS_ALL[2]}"
typeset -g VCS_STATUS_REMOTE_URL="${VCS_STATUS_ALL[3]}"
typeset -g VCS_STATUS_ACTION="${VCS_STATUS_ALL[4]}"
typeset -gi VCS_STATUS_HAS_STAGED="${VCS_STATUS_ALL[5]}"
typeset -gi VCS_STATUS_HAS_UNSTAGED="${VCS_STATUS_ALL[6]}"
typeset -gi VCS_STATUS_HAS_UNTRACKED="${VCS_STATUS_ALL[7]}"
typeset -gi VCS_STATUS_COMMITS_AHEAD="${VCS_STATUS_ALL[8]}"
typeset -gi VCS_STATUS_COMMITS_BEHIND="${VCS_STATUS_ALL[9]}"
typeset -gi VCS_STATUS_STASHES="${VCS_STATUS_ALL[10]}"
typeset -g VCS_STATUS_WORKDIR="${VCS_STATUS_ALL[11]}"
} || {
(( ours )) && VCS_STATUS_RESULT=norepo-sync || VCS_STATUS_RESULT=norepo-async
unset VCS_STATUS_ALL
unset VCS_STATUS_LOCAL_BRANCH
unset VCS_STATUS_REMOTE_BRANCH
unset VCS_STATUS_REMOTE_URL
unset VCS_STATUS_ACTION
unset VCS_STATUS_HAS_STAGED
unset VCS_STATUS_HAS_UNSTAGED
unset VCS_STATUS_HAS_UNTRACKED
unset VCS_STATUS_COMMITS_AHEAD
unset VCS_STATUS_COMMITS_BEHIND
unset VCS_STATUS_STASHES
unset VCS_STATUS_WORKDIR
}
(( ! ours )) && (( #header )) && emulate -L zsh && "${header[@]}" || true
}
# Usage: gitstatus_start [OPTION]... NAME
#
# -t FLOAT Fail the self-check on initialization if not getting a response from gitstatusd for
# this this many seconds. Defaults to 5.
# -m INT Report -1 unstaged and untracked if there are more than this many files in the index.
# Negative value means infinity. Defaults to -1.
function gitstatus_start() {
emulate -L zsh
setopt err_return no_unset
local opt
local -F timeout=5
local -i max_dirty=-1
while true; do
getopts "t:m:" opt || break
case $opt in
t) timeout=$OPTARG;;
m) max_dirty=$OPTARG;;
?) return 1;;
done) break;;
esac
done
(( timeout > 0 )) || { echo "invalid -t: $timeout" >&2; return 1 }
(( OPTIND == ARGC )) || { echo "usage: gitstatus_start [OPTION]... NAME" >&2; return 1 }
local name=${*[$OPTIND]}
[[ ! -v GITSTATUS_DAEMON_PID_${name} ]] || return 0
function gitstatus_arch() {
local kernel && kernel=$(uname -s) && [[ -n $kernel ]]
local arch && arch=$(uname -m) && [[ -n $arch ]]
echo -E "${kernel:l}-${arch:l}"
}
local daemon && daemon=${GITSTATUS_DAEMON:-${${(%):-%x}:A:h}/bin/gitstatusd-$(gitstatus_arch)}
[[ -f $daemon ]] || { echo "file not found: $daemon" >&2 && return 1 }
local req_fifo resp_fifo log
local -i req_fd resp_fd daemon_pid
function start() {
req_fifo=$(mktemp -u "${TMPDIR:-/tmp}"/gitstatus.$$.pipe.req.XXXXXXXXXX)
resp_fifo=$(mktemp -u "${TMPDIR:-/tmp}"/gitstatus.$$.pipe.resp.XXXXXXXXXX)
mkfifo $req_fifo
mkfifo $resp_fifo
exec {req_fd}<>$req_fifo
exec {resp_fd}<>$resp_fifo
typeset -gfH _gitstatus_process_response_${name}() {
_gitstatus_process_response ${0#_gitstatus_process_response_} 0 ''
}
zle -F $resp_fd _gitstatus_process_response_${name}
log=$(mktemp "${TMPDIR:-/tmp}"/gitstatus.$$.log.XXXXXXXXXX)
(
nice -n -20 $daemon --dirty-max-index-size=$max_dirty --parent-pid=$$ \
<&$req_fd >&$resp_fd 2>$log || true
echo -nE $'bye\x1f0\x1e' >&$resp_fd || true
rm -f $req_fifo $resp_fifo
) &!
daemon_pid=$!
local reply
echo -nE $'hello\x1f\x1e' >&$req_fd
IFS='' read -r -d $'\x1e' -u $resp_fd -t $timeout reply
[[ $reply == $'hello\x1f0' ]]
}
start && {
typeset -g GITSTATUS_DAEMON_LOG_${name}=$log
typeset -gi GITSTATUS_DAEMON_PID_${name}=$daemon_pid
typeset -gH _GITSTATUS_REQ_FIFO_${name}=$req_fifo
typeset -gH _GITSTATUS_RESP_FIFO_${name}=$resp_fifo
typeset -giH _GITSTATUS_REQ_FD_${name}=$req_fd
typeset -giH _GITSTATUS_RESP_FD_${name}=$resp_fd
typeset -giH _GITSTATUS_CLIENT_PID_${name}=$$
} || {
echo "gitstatus failed to initialize" >&2
[[ -n $daemon_pid ]] && kill $daemon_pid &>/dev/null || true
[[ -n $req_fd ]] && exec {req_fd}>&- || true
[[ -n $resp_fd ]] && { zle -F $resp_fd || true } && { exec {resp_fd}>&- || true}
rm -f $req_fifo $resp_fifo
return 1
}
}
# Usage: gitstatus_check NAME.
#
# Returns 0 if and only if `gitstatus_start NAME` has succeeded previously.
# If it returns non-zero, gitstatus_query NAME is guaranteed to return non-zero.
function gitstatus_check() {
local name=$1
[[ -v GITSTATUS_DAEMON_PID_${name} ]]
}
|