forked from rycus86/githooks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
uninstall.sh
executable file
·641 lines (542 loc) · 19.2 KB
/
uninstall.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
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
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
#!/bin/sh
#
# Uninstalls the base Git hook templates from https://github.com/rycus86/githooks
# See the documentation in the project README for more information.
############################################################
# Try to find the directory where the Git
# hook templates are currently.
#
# Sets ${TARGET_TEMPLATE_DIR} if found.
#
# Returns:
# None
############################################################
find_git_hook_templates() {
# 1. from environment variables
mark_directory_as_target "$GIT_TEMPLATE_DIR" "hooks"
if [ "$TARGET_TEMPLATE_DIR" != "" ]; then return; fi
# 2. from git config for templateDir
mark_directory_as_target "$(git config --get init.templateDir)" "hooks"
if [ "$TARGET_TEMPLATE_DIR" != "" ]; then return; fi
# 3. from git config for hooksPath
mark_directory_as_target "$(git config --get core.hooksPath)"
if [ "$TARGET_TEMPLATE_DIR" != "" ]; then return; fi
# 4. from the default location
mark_directory_as_target "/usr/share/git-core/templates/hooks"
if [ "$TARGET_TEMPLATE_DIR" != "" ]; then return; fi
}
############################################################
# Sets the ${TARGET_TEMPLATE_DIR} variable if the
# first parameter is a writable directory.
#
# Returns:
# None
############################################################
mark_directory_as_target() {
TARGET="$1"
if [ "$TARGET" = "" ]; then
return
fi
if [ "$2" != "" ]; then
TARGET="${TARGET}/$2"
fi
if [ -w "$TARGET" ]; then
TARGET_TEMPLATE_DIR="$TARGET"
return
fi
# Try to see if the path is given with a tilde
TILDE_REPLACED=$(echo "$TARGET" | awk 'gsub("~", "'"$HOME"'", $0)')
if [ -n "$TILDE_REPLACED" ] && [ -w "$TILDE_REPLACED" ]; then
TARGET_TEMPLATE_DIR="$TILDE_REPLACED"
return
fi
}
############################################################
# Uninstall the existing Git hook templates from the
# Git template directory.
#
# Returns:
# None
############################################################
remove_existing_hook_templates() {
for TEMPLATE_FILE in "$1"/*; do
grep 'https://github.com/rycus86/githooks' "${TEMPLATE_FILE}" >/dev/null 2>&1
# shellcheck disable=SC2181
if [ $? -eq 0 ]; then
rm -f "$TEMPLATE_FILE"
echo "Removed hook template at $TEMPLATE_FILE"
# Restore the previously moved hook if there was any
if [ -f "${TEMPLATE_FILE}.replaced.githook" ]; then
mv "${TEMPLATE_FILE}.replaced.githook" "$TEMPLATE_FILE"
fi
fi
done
}
############################################################
# Find existing repositories from a start directory `$1`.
# Sets the variable `$EXISTING_REPOSITORY_LIST`
#
# Returns:
# 0 on success, 1 on failure
############################################################
find_existing_git_dirs() {
REPOSITORY_LIST=$(
find "$1" \( -type d -and -name .git \) -or \
\( -type f -and -name HEAD -and -not -path "*/.git/*" \) 2>/dev/null
)
# List if existing Git repositories
EXISTING_REPOSITORY_LIST=""
IFS="$IFS_NEWLINE"
for EXISTING in $REPOSITORY_LIST; do
unset IFS
if [ -f "$EXISTING" ]; then
# Strip HEAD file
EXISTING=$(dirname "$EXISTING")
fi
# Try to go to the root git dir (works in bare and non-bare repositories)
# to neglect false positives from the find above
# e.g. spourious HEAD file or .git dir which does not mark a repository
REPO_GIT_DIR=$(cd "$EXISTING" && cd "$(git rev-parse --git-common-dir 2>/dev/null)" && pwd)
if is_git_repo "$REPO_GIT_DIR" && ! echo "$EXISTING_REPOSITORY_LIST" | grep -F -q "$REPO_GIT_DIR"; then
EXISTING_REPOSITORY_LIST="$REPO_GIT_DIR
$EXISTING_REPOSITORY_LIST"
fi
done
# Sort the list if we can
if sort --help >/dev/null 2>&1; then
EXISTING_REPOSITORY_LIST=$(echo "$EXISTING_REPOSITORY_LIST" | sort)
fi
}
############################################################
# Uninstall the existing Git hook templates from the
# existing local repositories.
#
# Returns:
# 0 on success, 1 on failure
############################################################
uninstall_from_existing_repositories() {
# Don't offer to remove from repo's if we were using the hooksPath implementation
if using_hooks_path; then
return 0
fi
printf 'Do you want to uninstall the hooks from existing repositories? [yN] '
read -r DO_UNINSTALL
if [ "$DO_UNINSTALL" != "y" ] && [ "$DO_UNINSTALL" != "Y" ]; then return 0; fi
PRE_START_DIR=$(git config --global --get githooks.previousSearchDir)
# shellcheck disable=SC2181
if [ $? -eq 0 ] && [ -n "$PRE_START_DIR" ]; then
START_DIR="$PRE_START_DIR"
else
START_DIR=~
fi
printf 'Where do you want to start the search? [%s] ' "$START_DIR"
read -r START_DIR
if [ "$START_DIR" = "" ]; then
START_DIR="$HOME"
else
TILDE_REPLACED=$(echo "$START_DIR" | awk 'gsub("~", "'"$HOME"'", $0)')
if [ -n "$TILDE_REPLACED" ]; then
START_DIR="$TILDE_REPLACED"
fi
fi
if [ ! -d "$START_DIR" ]; then
echo "'$START_DIR' is not a directory" >&2
return 1
fi
find_existing_git_dirs "$START_DIR"
# Loop over all existing git dirs
IFS="$IFS_NEWLINE"
for EXISTING in $EXISTING_REPOSITORY_LIST; do
unset IFS
uninstall_hooks_from_repo "$EXISTING"
IFS="$IFS_NEWLINE"
done
unset IFS
return 0
}
#####################################################
# Uninstall from all registered repositories
#
# Returns: 0
#####################################################
uninstall_from_registered_repositories() {
LIST="$INSTALL_DIR/registered"
if [ -f "$LIST" ]; then
# Filter list according to
# - non-existing repos
# - non-git repos
# Uninstall list
UNINSTALL_LIST=$(mktemp)
IFS="$IFS_NEWLINE"
while read -r INSTALLED_REPO; do
unset IFS
if ! is_git_repo "$INSTALLED_REPO"; then
# Not existing or no git dir -> skip.
true
else
# Found existing registed repository -> uninstall
echo "$INSTALLED_REPO" >>"$UNINSTALL_LIST"
fi
IFS="$IFS_NEWLINE"
done <"$LIST"
if [ -s "$UNINSTALL_LIST" ]; then
echo "The following registered repositories in \`$LIST\`"
echo "contain a Githooks installation:"
sed -E "s/^/ - /" <"$UNINSTALL_LIST"
printf 'Do you want to uninstall from all of them? [Yn] '
read -r DO_UNINSTALL
if [ "$DO_UNINSTALL" = "n" ] || [ "$DO_UNINSTALL" = "N" ]; then
rm -f "$UNINSTALL_LIST" >/dev/null 2>&1
# Do not change registered list.
return 0
fi
# Loop over all existing git dirs
IFS="$IFS_NEWLINE"
while read -r INSTALLED_REPO; do
unset IFS
uninstall_hooks_from_repo "$INSTALLED_REPO"
IFS="$IFS_NEWLINE"
done <"$UNINSTALL_LIST"
rm -f "$UNINSTALL_LIST" >/dev/null 2>&1
fi
# Remove the registered list since we
# uninstalled from all registered repos.
rm -f "$LIST" >/dev/null 2>&1
# Remove legacy list
if [ -d "$INSTALL_DIR/autoupdate" ]; then
rm -rf "$INSTALL_DIR/autoupdate"
fi
fi
return 0
}
############################################################
# Checks whether the given directory
# is a Git repository (bare included) or not.
#
# Returns:
# 1 if failed, 0 otherwise
############################################################
is_git_repo() {
git -C "$1" rev-parse >/dev/null 2>&1 || return 1
}
############################################################
# Removes the repository from the global list
# `githooks.registered`
#
# Returns: None
############################################################
unregister_repo() {
CURRENT_REPO="$(cd "$1" && pwd)"
LIST="$INSTALL_DIR/registered"
# Remove
if [ -f "$LIST" ]; then
TEMP_FILE=$(mktemp)
CURRENT_ESCAPED=$(echo "$CURRENT_REPO" | sed "s@/@\\\\\/@g")
sed "/$CURRENT_ESCAPED/d" "$LIST" >"$TEMP_FILE"
mv -f "$TEMP_FILE" "$LIST"
fi
}
############################################################
# Uninstall the existing Git hook templates from the
# current repository.
#
# Returns:
# 0 on success, 1 on failure
############################################################
uninstall_from_current_repository() {
if ! is_git_repo "$(pwd)"; then
echo "! The current directory ($(pwd)) does not seem to be inside a Git repository!" >&2
exit 1
fi
REPO_GIT_DIR=$(cd "$(git rev-parse --git-common-dir 2>/dev/null)" && pwd)
uninstall_hooks_from_repo "$REPO_GIT_DIR"
}
############################################################
# Uninstall the existing Git hook templates from an existing
# local repository, given by the first parameter.
#
# Returns:
# 0 if sucessful, 1 otherwise
############################################################
uninstall_hooks_from_repo() {
TARGET="$1"
if [ ! -w "${TARGET}/hooks" ]; then
echo "! Could not uninstall from \`$TARGET\` because there is no write access."
return 1
fi
UNINSTALLED="false"
for TARGET_HOOK in "$TARGET/hooks/"*; do
if [ -f "$TARGET_HOOK" ]; then
grep 'https://github.com/rycus86/githooks' "${TARGET_HOOK}" >/dev/null 2>&1
# shellcheck disable=SC2181
if [ $? -eq 0 ]; then
rm -f "$TARGET_HOOK"
UNINSTALLED="true"
# Restore the previously moved hook if there was any
if [ -f "${TARGET_HOOK}.replaced.githook" ]; then
mv "${TARGET_HOOK}.replaced.githook" "$TARGET_HOOK"
fi
fi
fi
done
if [ -f "${TARGET}/.githooks.checksum" ]; then
rm -f "${TARGET}/.githooks.checksum"
UNINSTALLED="true"
fi
# Remove all install relevant local githooks configs
(
cd "${TARGET}" &&
git config --local --unset githooks.single.install >/dev/null && # legacy settings (deprecated)
git config --local --unset githooks.autoupdate.registered >/dev/null 2>&1 && # legacy settings (deprecated)
git config --local --unset githooks.install.registered >/dev/null 2>&1 &&
git config --local --unset-all githooks.shared >/dev/null 2>&1 &&
git config --local --unset-all githooks.sharedHooksUpdateTriggers >/dev/null 2>&1
)
if [ "$UNINSTALLED" = "true" ]; then
echo "Hooks are uninstalled from $TARGET"
fi
# Always remove this repo from the registered list
unregister_repo "${TARGET}"
# If Git LFS is available, try installing the LFS hooks again
if [ "$GIT_LFS_AVAILABLE" = "true" ]; then
OUTPUT=$(git -C "$TARGET" lfs install 2>&1)
# shellcheck disable=SC2181
if [ $? -ne 0 ]; then
echo "! Reinstalling Git LFS in \`$TARGET\` failed! Output:" >&2
echo "$OUTPUT" >&2
fi
fi
return 0
}
############################################################
# Checks if we're using the hooksPath
# or templateDir implementation.
#
# Returns:
# 0 on true, 1 on false
############################################################
using_hooks_path() {
USE_HOOKS_PATH=$(git config --global githooks.useCoreHooksPath)
if [ "$USE_HOOKS_PATH" = "true" ] ||
[ "$USE_HOOKS_PATH" = "yes" ]; then # Legacy
return 0
else
return 1
fi
}
# Uninstall shared hooks.
#
# Returns:
# None
############################################################
uninstall_shared_hooks() {
if [ -d "$INSTALL_DIR/shared" ]; then
if ! rm -rf "${INSTALL_DIR}/shared" >/dev/null 2>&1; then
echo "! Failed to delete shared hook repository folders" >&2
exit 1
fi
fi
}
############################################################
# Uninstall the cli tool.
#
# Returns:
# None
############################################################
uninstall_cli() {
CLI_DIR="${INSTALL_DIR}/bin"
if [ -d "$CLI_DIR" ]; then
if ! rm -rf "$CLI_DIR" >/dev/null 2>&1; then
echo "! Failed to delete the githooks command-line tool" >&2
exit 1
fi
fi
}
############################################################
# Uninstall the release repository.
#
# Returns:
# None
############################################################
uninstall_release_repo() {
RELEASE_DIR="${INSTALL_DIR}/release"
if [ -d "$RELEASE_DIR" ]; then
if ! rm -rf "$RELEASE_DIR" >/dev/null 2>&1; then
echo "! Failed to delete the githooks release repository" >&2
exit 1
fi
fi
}
#####################################################
# Sets the ${INSTALL_DIR} variable
#
# Returns:
# 0 if success, 1 otherwise
#####################################################
load_install_dir() {
INSTALL_DIR=$(git config --global --get githooks.installDir)
if [ -z "$INSTALL_DIR" ]; then
# install dir not defined, use default
INSTALL_DIR=~/".githooks"
elif [ ! -d "$INSTALL_DIR" ]; then
echo "! Githooks installation is corrupt! " >&2
echo " Install directory at ${INSTALL_DIR} is missing." >&2
INSTALL_DIR=~/".githooks"
echo " Using default install directory at $INSTALL_DIR" >&2
fi
# Final check since we are going to delete folders
if ! echo "$INSTALL_DIR" | grep -F -q ".githooks"; then
echo "! Uninstall path at $INSTALL_DIR needs to contain \`.githooks\`" >&2
return 1
fi
return 0
}
#####################################################
# Check the install is local or global
#
# Returns: 0 if uninstall is local, 1 otherwise
#####################################################
is_single_repo_uninstall() {
[ "$SINGLE_REPO_UNINSTALL" = "true" ] || return 1
}
#####################################################
# Check we are running inside the release clone
#
# Returns: 0 if `true`, 1 otherwise
#####################################################
is_running_internal_uninstall() {
[ "$INTERNAL_UNINSTALL" = "true" ] || return 1
}
#####################################################
# Parse command line args.
#
# Returns: 0 if successful, 1 otherhwise
#####################################################
parse_command_line_args() {
SINGLE_REPO_UNINSTALL="false"
INTERNAL_UNINSTALL="false"
for p in "$@"; do
if [ "$p" = "--internal-uninstall" ]; then
INTERNAL_UNINSTALL="true"
elif [ "$p" = "--single" ] ||
[ "$p" = "--local" ]; then # legacy flag from old uninstall.sh
SINGLE_REPO_UNINSTALL="true"
else
echo "! Unknown argument \`$p\`" >&2
return 1
fi
done
return 0
}
#####################################################
# Set up the main variables that
# we will throughout the hook.
#
# Sets the ${INSTALL_DIR} variable
# Sets the ${GIT_LFS_AVAILABLE} variable
#
# Returns: None
#####################################################
set_main_variables() {
# Global IFS for loops
IFS_NEWLINE="
"
# Ensure user-customized CDPATHs do not change behavior
unset -v CDPATH
load_install_dir || return 1
# do we have Git LFS installed
GIT_LFS_AVAILABLE="false"
command -v git-lfs >/dev/null 2>&1 && GIT_LFS_AVAILABLE="true"
return 0
}
#####################################################
# Try removing core.hooksPath if we are using it.
# Returns: 0 if success, 1 otherwise
#####################################################
remove_core_hooks_path() {
if using_hooks_path; then
GITHOOKS_CORE_HOOKSPATH=$(git config --global githooks.pathForUseCoreHooksPath)
GIT_CORE_HOOKSPATH=$(git config --global core.hooksPath)
if [ "$GITHOOKS_CORE_HOOKSPATH" = "$GIT_CORE_HOOKSPATH" ]; then
git config --global --unset core.hooksPath
fi
fi
}
#####################################################
# Main uninstall routine.
# Returns: 0 if success, 1 otherwise
#####################################################
uninstall() {
set_main_variables || return 1
parse_command_line_args "$@"
if ! is_running_internal_uninstall &&
[ -f "$INSTALL_DIR/release/uninstall.sh" ]; then
# Dispatch to install script inside the clone
sh "$INSTALL_DIR/release/uninstall.sh" --internal-uninstall "$@" || return 1
return 0
fi
if is_single_repo_uninstall; then
# Uninstall the hooks from the current repository
if uninstall_from_current_repository; then
return 0
else
echo "! Failed to uninstall from current repository" >&2
return 1
fi
fi
# Uninstall the hooks from existing local repositories
if ! uninstall_from_existing_repositories; then
echo "! Failed to uninstall from existing repositories" >&2
return 1
fi
if ! uninstall_from_registered_repositories; then
echo "! Failed to uninstall from registered repositories" >&2
return 1
fi
# Find the current Git hook templates directory
TARGET_TEMPLATE_DIR=""
find_git_hook_templates
if [ ! -d "$TARGET_TEMPLATE_DIR" ]; then
echo "Git hook templates directory not found" >&2
return 1
fi
# Delete the hook templates
remove_existing_hook_templates "$TARGET_TEMPLATE_DIR"
# Uninstall all shared hooks
uninstall_shared_hooks
# Uninstall all cli
uninstall_cli
# uninstall release repo
uninstall_release_repo
# remove core hooks path if we are using it
remove_core_hooks_path
# Unset global Githooks variables
git config --global --unset githooks.runner
git config --global --unset-all githooks.shared
git config --global --unset githooks.failOnNonExistingSharedHooks
git config --global --unset githooks.maintainOnlyServerHooks
git config --global --unset githooks.autoupdate.enabled
git config --global --unset githooks.autoupdate.lastrun
git config --global --unset githooks.noReadme
git config --global --unset githooks.cloneUrl
git config --global --unset githooks.cloneBranch
git config --global --unset githooks.previousSearchDir
git config --global --unset githooks.disable
git config --global --unset githooks.installDir
git config --global --unset githooks.deleteDetectedLFSHooks
git config --global --unset githooks.pathForUseCoreHooksPath
git config --global --unset githooks.useCoreHooksPath
git config --global --unset-all githooks.sharedHooksUpdateTriggers
git config --global --unset alias.hooks
# Legacy
git config --global --unset githooks.autoupdate.updateCloneUrl
git config --global --unset githooks.autoupdate.updateCloneBranch
git config --global --unset githooks.previous.searchdir
# Finished
echo "All done!"
echo
echo "If you ever want to reinstall the hooks, just follow"
echo "the install instructions at https://github.com/rycus86/githooks"
return 0
}
uninstall "$@" || exit 1