-
Notifications
You must be signed in to change notification settings - Fork 611
/
git-update-git-for-windows
executable file
·360 lines (327 loc) · 10.1 KB
/
git-update-git-for-windows
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
#!/bin/sh
# Compares the currently installed Git for Windows against latest available
# release. If versions differ, the bit matched installer is downloaded and run
# when confirmation to do so is given.
# Compare version strings
# Prints -1, 0 or 1 to stdout
version_compare () {
a="$1"
b="$2"
while true
do
test -n "$b" || { echo 1; return; }
test -n "$a" || { echo -1; return; }
# Get the first numbers (if any)
a1="$(expr "$a" : '^\([0-9]*\)')"; a="${a#$a1}"
b1="$(expr "$b" : '^\([0-9]*\)')"; b="${b#$b1}"
if test -z "$b1"
then
test -z "$a1" || { echo 1; return; }
a1=0
b1=0
fi
test -n "$a1" || { echo -1; return; }
test $a1 -le $b1 || { echo 1; return; }
test $b1 -le $a1 || { echo -1; return; }
# Skip non-numeric prefixes
a1="$(expr "$a" : '^\([^0-9]\+\)')"; a="${a#$a1}"
b1="$(expr "$b" : '^\([^0-9]\+\)')"; b="${b#$b1}"
case "$a1,$b1" in
[.-]rc,[.-]rc) ;; # both are -rc versions
[.-]rc,*) echo -1; return;;
*,[.-]rc) echo 1; return;;
esac
done
}
test "--test-version-compare" != "$*" || {
test_version_compare () {
result="$(version_compare "$1" "$2")"
test "$3" = "$result" || {
echo "version_compare $1 $2 returned $result instead of $3" >&2
exit 1
}
result2="$(version_compare "$2" "$1")"
test "$result2" = "$((-$3))" || {
echo "version_compare $2 $1 returned $result2 instead of $((-$3))" >&2
exit 1
}
}
test_version_compare 2.32.0.windows.1 2.32.1.windows.1 -1
test_version_compare 2.32.1.windows.1 2.32.0.windows.1 1
test_version_compare 2.32.1.vfs.0.0 2.32.0.windows.1 1
test_version_compare 2.32.1.vfs.0.0 2.32.0.vfs.0.0 1
test_version_compare 2.32.0.vfs.0.1 2.32.0.vfs.0.2 -1
test_version_compare 2.32.0-rc0.windows.1 2.31.1.windows.1 1
test_version_compare 2.32.0-rc2.windows.1 2.32.0.windows.1 -1
test_version_compare 2.34.0.rc1.windows.1 2.33.1.windows.1 1
test_version_compare 2.34.0.rc2.windows.1 2.34.0.windows.1 -1
exit 0
}
# Counts how many Bash instances are running, apart from the current one (if
# any: `git update-git-for-windows` might have been called from a CMD window,
# in which case no Git Bash might be running at all).
#
# This is a little tricky, as the /usr/bin/sh process (as which `ps` reports the
# process running this script) is an MSYS2 one, but the calling `git.exe`
# process is a pure Win32 one. As a consequence, the former process' PPID will
# be reported as 1 (!!!) and its PGID will refer to the latter, while the
# latter's PGID will be identical to its PID and its PPID refers to the calling
# Bash (or is 1, if `git.exe` was not called by an MSYS2 program).
#
# So we have to employ a little sed fu to parse `ps` output of the form:
#
# PID PPID PGID WINPID TTY UID STIME COMMAND
# 19864 15640 19864 27996 pty0 4853009 15:58:05 /usr/bin/bash
# 15640 1 15640 15640 ? 4853009 15:58:05 /usr/bin/mintty
# 28128 13048 21176 28716 pty0 4853009 16:01:08 /usr/bin/ps
# 13048 1 21176 13048 pty0 4853009 16:01:08 /usr/bin/sh
# 21176 19864 21176 11996 pty0 4853009 16:01:08 /mingw64/bin/git
#
# Essentially, we are looking for the /usr/bin/sh line (in the example, PID
# 13048), follow its PGID to the /mingw64/bin/git line (in the example, PID
# 21176), and record the PPID of the latter as the pid of the current Bash, if
# any. As we do not know in which order the `sh` and the `git` line appear, we
# have to handle both orders.
#
# Then, we filter the `ps` output first by dropping the line with the current
# Bash, then finally counting the remaining lines referring to a bash process.
count_other_bashes () {
mypid=$$ && nl='\n *' && s=' *' && p='[1-9][0-9]*' &&
mypid="$(ps | sed -n ":1;N;
s/.*$nl$mypid$s$p$s\\($p\\) .*$nl\\1$s\\($p\\) .*/\\2/p;
s/.*$nl\\($p\\)$s\\($p\\) .*$nl$mypid$s$p$s\\1 .*/\\2/p;
b1")"
ps |
if test -z "$mypid"; then cat; else grep -v "^ *$mypid "; fi |
grep ' /usr/bin/bash$' |
wc -l
}
# Write HTTP GET response to stdout and return error code. This is equivalent
# to curl --fail, except that we output the response body to stderr in case of
# an HTTP error status.
http_get () {
url=$1
output=$(mktemp -t gfw-httpget-XXXXXXXX.txt)
code=$(curl \
--silent \
--show-error \
--output "$output" \
--write-out '%{http_code}' \
"$url") || return $?
fdout=1
ret=0
if test "$code" -ge 400
then
fdout=2
ret=22
fi
cat "$output" >&"$fdout"
rm -f "$output"
return "$ret"
}
get_recently_seen() {
if [ -f "$HOME"/.git-for-windows-updater ]; then
git config -f "$HOME"/.git-for-windows-updater winUpdater.recentlySeenVersion
else
git config --global winUpdater.recentlySeenVersion
fi
}
set_recently_seen() {
git config -f "$HOME"/.git-for-windows-updater winUpdater.recentlySeenVersion "$1"
}
# The main function of this script
update_git_for_windows () {
proxy=$(git config --get http.proxy)
if test -n "$proxy"
then
export https_proxy="$proxy"
echo "Using proxy server $https_proxy detected from git http.proxy" >&2
fi
yn=
use_gui=
quiet=
testing=
while test $# -gt 0
do
case "$1" in
-\?|--?\?|-h|--help) ;;
-y|--yes) yn=y; shift; continue;;
-g|--gui) use_gui=t; shift; continue;;
--quiet) quiet=t; shift; continue;;
--testing) testing=t; shift; continue;;
*) echo "Unknown option: $1" >&2;;
esac
printf >&2 '%s\n%s\n\t%s\n\t%s\n' \
"Usage: git update-git-for-windows [options]" \
"Options:" \
"-g, --gui Use GUI instead of terminal to prompt" \
"-y, --yes Automatic yes to download and install prompt" \
"Return code:" \
" 0: no update available" \
" 1: update available and user selected not to update" \
" 2: update available and it was started"
return 1
done
case "$(uname -m)" in
x86_64) bit=64;;
*) bit=32;;
esac
try_toast=
test -z "$use_gui" ||
case "$(uname -s)" in
*-6.[23]|*-6.[23]-[1-9]*|*-10.0|*-10.0-[1-9]*)
# Only try to show a Toast notification on Windows 8 & 10,
# and only if we have a working wintoast.exe
! type wintoast.exe >/dev/null 2>&1 ||
try_toast=t
;;
esac
test -f "$0.config" &&
fork="$(git config -f "$0.config" update.fromFork)" &&
test -n "$fork" ||
fork=
if test -n "$fork"
then
git_label="$(git config -f "$0.config" update.gitLabel)"
test -n "$git_label" || git_label=Git
releases_url=https://api.github.com/repos/$fork/releases
latest_tag_url=$releases_url/latest
latest_eval='latest=${latest_tag#*\"tag_name\": \"v}; latest=${latest%%\"*}'
else
git_label="Git for Windows"
releases_url=https://api.github.com/repos/git-for-windows/git/releases
latest_tag_url=https://gitforwindows.org/latest-tag.txt
latest_eval='latest=${latest_tag#v}'
fi
latest_tag=$(http_get $latest_tag_url) ||
case $?,"$proxy" in
7,)
proxy="$(proxy-lookup.exe https://gitforwindows.org)" &&
test -n "$proxy" &&
export https_proxy="$proxy" &&
echo "Using proxy $https_proxy as per lookup" >&2 &&
latest_tag=$(http_get $latest_tag_url) ||
return
;;
*)
return
;;
esac
eval "$latest_eval"
# Be extra careful to remove a leading 'v' from the tag.
latest=${latest#v}
# Did we ask about this version already?
if test -z "$use_recently_seen"
then
recently_seen="$(get_recently_seen)"
test -n "$quiet" && test "x$recently_seen" = "x$latest" && return
fi
version=$(git --version | sed "s/git version //")
if test -d /clangarm64/bin
then
arch_bit=arm64
else
arch_bit=${bit}-bit
fi
echo "$git_label $version ($arch_bit)" >&2
if test -z "$testing" && test "$latest" = "$version"
then
echo "Up to date" >&2
set_recently_seen "$latest"
return
fi
if ! test -n "$testing"
then
# We are not testing and we don't have exact equality,
# so do a careful comparison and look to see if the
# latest release is strictly newer than ours.
if test 0 -lt "$(version_compare "$version" "$latest")"
then
return
fi
fi
echo "Update $latest is available" >&2
releases=$(http_get $releases_url/latest) || return
download=$(echo "$releases" |
grep '"browser_download_url": "' |
grep "$arch_bit\.exe" |
sed -E 's/.*": "([^"]*).*/\1/')
filename=$(echo "$download" | sed -E 's/.*\/([^\/]*)$/\1/')
name="$(echo "$releases" | sed -n 's/^ "name": "\(.*\)",$/\1/p')"
installer=$(mktemp -t gfw-install-XXXXXXXX.exe)
if test -z "$yn"
then
other_bashes=$(count_other_bashes)
if test $other_bashes -le 0
then
warn=
elif test $other_bashes -eq 1
then
warn=" (killing one Git Bash)"
else
warn=" (killing $other_bashes Git Bash instances)"
fi
if test -n "$try_toast"
then
wintoast.exe --appname "$git_label" \
--appid GitForWindows.Updater \
--image /mingw$bit/share/git/git-for-windows.ico \
--text "Download and install $name$warn?" \
--action Yes --action No --expirems 15000
case $? in
0|16)
# clicked toast, or clicked Yes: download
;;
1|17)
# dismiseed, or clicked No: ignore this release
set_recently_seen "$latest"
return 1
;;
4|5|6|9|10)
# toast not activated, failed, toasts
# unsupported, WinToast init failed or toast
# not launched: fall back to using GUI
git askyesno \
--title "Git Update Available" \
"Download and install $name$warn?" || {
set_recently_seen "$latest"
return 1
}
;;
*)
# toast timed out, or hidden, or unknown
# failure: ignore
return 1
;;
esac
elif test -n "$use_gui"
then
git askyesno --title "Git Update Available" \
"Download and install $name$warn?" || {
set_recently_seen "$latest"
return 1
}
else
read -p "Download and install $name$warn [N/y]? " \
yn >&2
case "$yn" in
[Yy]*) ;;
*)
set_recently_seen "$latest"
return 1;;
esac
fi
else
echo "Downloading $filename" >&2
fi
curl -# -L -o $installer $download || return
start "" "$installer" //SILENT //NORESTART
# Kill all Bash processes (which will let MinTTY quit, too)"
#
# `ps` without `-W` will automatically only catch MSYS2 processes
# that link to *this* MSYS2 runtime, i.e. no processes from other Git
# installations (e.g. Git for Windows' SDK) will be killed.
ps | grep ' /usr/bin/bash$' | awk '{print "kill -9 " $1 ";" }' | sh
return 2
}
update_git_for_windows "$@"