forked from Bash-it/bash-it
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelpers.bash
More file actions
1276 lines (1080 loc) · 37.3 KB
/
helpers.bash
File metadata and controls
1276 lines (1080 loc) · 37.3 KB
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
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# shellcheck shell=bash
# shellcheck disable=SC2016
#
# A collection of reusable functions.
: "${BASH_IT_LOAD_PRIORITY_ALIAS:=750}"
: "${BASH_IT_LOAD_PRIORITY_PLUGIN:=250}"
: "${BASH_IT_LOAD_PRIORITY_COMPLETION:=350}"
BASH_IT_LOAD_PRIORITY_SEPARATOR="---"
# Handle the different ways of running `sed` without generating a backup file based on provenance:
# - GNU sed (Linux) uses `-i''`
# - BSD sed (FreeBSD/macOS/Solaris/PlayStation) uses `-i ''`
# To use this in Bash-it for inline replacements with `sed`, use the following syntax:
# sed "${BASH_IT_SED_I_PARAMETERS[@]}" -e "..." file
# shellcheck disable=SC2034 # expected for this case
if sed --version > /dev/null 2>&1; then
# GNU sed accepts "long" options
BASH_IT_SED_I_PARAMETERS=('-i')
else
# BSD sed errors on invalid option `-`
BASH_IT_SED_I_PARAMETERS=('-i' '')
fi
function _bash_it_homebrew_check() {
if _binary_exists 'brew'; then
# Homebrew is installed
if [[ "${BASH_IT_HOMEBREW_PREFIX:-unset}" == 'unset' ]]; then
# variable isn't set
BASH_IT_HOMEBREW_PREFIX="$(brew --prefix)"
else
true # Variable is set already, don't invoke `brew`.
fi
else
# Homebrew is not installed: clear variable.
BASH_IT_HOMEBREW_PREFIX=
false # return failure if brew not installed.
fi
}
function _make_reload_alias() {
echo "source '${BASH_IT?}/scripts/reloader.bash' '${1?}' '${2?}'"
}
# Alias for reloading aliases
# shellcheck disable=SC2139
alias reload_aliases="$(_make_reload_alias alias aliases)"
# Alias for reloading auto-completion
# shellcheck disable=SC2139
alias reload_completion="$(_make_reload_alias completion completion)"
# Alias for reloading plugins
# shellcheck disable=SC2139
alias reload_plugins="$(_make_reload_alias plugin plugins)"
function bash-it() {
about 'Bash-it help and maintenance'
param '1: verb [one of: help | show | enable | disable | migrate | update | search | preview | version | reload | restart | doctor ] '
param '2: component type [one of: alias(es) | completion(s) | plugin(s) ] or search term(s)'
param '3: specific component [optional]'
example '$ bash-it show plugins'
example '$ bash-it help aliases'
example '$ bash-it enable plugin git [tmux]...'
example '$ bash-it disable alias hg [tmux]...'
example '$ bash-it migrate'
example '$ bash-it update'
example '$ bash-it search [-|@]term1 [-|@]term2 ... [ -e/--enable ] [ -d/--disable ] [ -r/--refresh ] [ -c/--no-color ]'
example '$ bash-it preview'
example '$ bash-it preview essential'
example '$ bash-it version'
example '$ bash-it reload'
example '$ bash-it restart'
example '$ bash-it profile list|save|load|rm [profile_name]'
example '$ bash-it doctor errors|warnings|all|summary'
local verb=${1:-}
shift
local component=${1:-}
shift
local func
case "$verb" in
show)
func="_bash-it-$component"
;;
enable)
func="_enable-$component"
;;
disable)
func="_disable-$component"
;;
help)
func="_help-$component"
;;
doctor)
func="_bash-it-doctor-$component"
;;
profile)
func=_bash-it-profile-$component
;;
search)
_bash-it-search "$component" "$@"
return
;;
preview)
_bash-it-preview "$component" "$@"
return
;;
update)
func="_bash-it-update-$component"
;;
migrate)
func="_bash-it-migrate"
;;
version)
func="_bash-it-version"
;;
restart)
func="_bash-it-restart"
;;
reload)
func="_bash-it-reload"
;;
*)
reference "bash-it"
return
;;
esac
# pluralize component if necessary
if ! _is_function "$func"; then
if _is_function "${func}s"; then
func="${func}s"
else
if _is_function "${func}es"; then
func="${func}es"
else
echo "oops! $component is not a valid option!"
reference bash-it
return
fi
fi
fi
if [[ "$verb" == "enable" || "$verb" == "disable" ]]; then
# Automatically run a migration if required
_bash-it-migrate
for arg in "$@"; do
"$func" "$arg"
done
if [[ -n "${BASH_IT_AUTOMATIC_RELOAD_AFTER_CONFIG_CHANGE:-}" ]]; then
_bash-it-reload
fi
else
"$func" "$@"
fi
}
function _bash-it-aliases() {
_about 'summarizes available bash_it aliases'
_group 'lib'
_bash-it-describe "aliases" "an" "alias" "Alias"
}
function _bash-it-completions() {
_about 'summarizes available bash_it completions'
_group 'lib'
_bash-it-describe "completion" "a" "completion" "Completion"
}
function _bash-it-plugins() {
_about 'summarizes available bash_it plugins'
_group 'lib'
_bash-it-describe "plugins" "a" "plugin" "Plugin"
}
function _bash-it-update-dev() {
_about 'updates Bash-it to the latest master'
_group 'lib'
_bash-it-update- dev "$@"
}
function _bash-it-update-stable() {
_about 'updates Bash-it to the latest tag'
_group 'lib'
_bash-it-update- stable "$@"
}
function _bash-it_update_migrate_and_restart() {
_about 'Checks out the wanted version, pops directory and restart. Does not return (because of the restart!)'
_param '1: Which branch to checkout to'
_param '2: Which type of version we are using'
if git checkout "${1?}" &> /dev/null; then
echo "Bash-it successfully updated."
echo ""
echo "Migrating your installation to the latest ${2:-} version now..."
_bash-it-migrate
echo ""
echo "All done, enjoy!"
# Don't forget to restore the original pwd (called from `_bash-it-update-`)!
popd > /dev/null || return
_bash-it-restart
else
echo "Error updating Bash-it, please, check if your Bash-it installation folder (${BASH_IT}) is clean."
fi
}
function _bash-it-update-() {
_about 'updates Bash-it'
_param '1: What kind of update to do (stable|dev)'
_group 'lib'
local silent word DIFF version TARGET revision status revert log_color RESP
for word in "$@"; do
if [[ "${word}" == "--silent" || "${word}" == "-s" ]]; then
silent=true
fi
done
pushd "${BASH_IT?}" > /dev/null || return
DIFF=$(git diff --name-status)
if [[ -n "$DIFF" ]]; then
echo -e "Local changes detected in bash-it directory. Clean '$BASH_IT' directory to proceed.\n$DIFF"
popd > /dev/null || return
return 1
fi
if [[ -z "$BASH_IT_REMOTE" ]]; then
BASH_IT_REMOTE=$(_get-git-default-remote-name)
fi
git fetch "$BASH_IT_REMOTE" --tags &> /dev/null
if [[ -z "$BASH_IT_DEVELOPMENT_BRANCH" ]]; then
BASH_IT_DEVELOPMENT_BRANCH="master"
fi
# Defaults to stable update
if [[ -z "${1:-}" || "$1" == "stable" ]]; then
version="stable"
TARGET=$(git describe --tags "$(git rev-list --tags --max-count=1)" 2> /dev/null)
if [[ -z "$TARGET" ]]; then
echo "Can not find tags, so can not update to latest stable version..."
popd > /dev/null || return
return
fi
else
version="dev"
TARGET="${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH}"
fi
revision="HEAD..${TARGET}"
status="$(git rev-list "${revision}" 2> /dev/null)"
if [[ -z "${status}" && "${version}" == "stable" ]]; then
revision="${TARGET}..HEAD"
status="$(git rev-list "${revision}" 2> /dev/null)"
revert=true
fi
if [[ -n "${status}" ]]; then
if [[ -n "${revert}" ]]; then
echo "Your version is a more recent development version ($(git log -1 --format=%h HEAD))"
echo "You can continue in order to revert and update to the latest stable version"
echo ""
log_color="%Cred"
fi
git log --no-merges --format="${log_color}%h: %s (%an)" "${revision}"
echo ""
if [[ -n "${silent}" ]]; then
echo "Updating to ${TARGET}($(git log -1 --format=%h "${TARGET}"))..."
_bash-it_update_migrate_and_restart "$TARGET" "$version"
else
read -r -e -n 1 -p "Would you like to update to ${TARGET}($(git log -1 --format=%h "${TARGET}"))? [Y/n] " RESP
case "$RESP" in
[yY] | "")
_bash-it_update_migrate_and_restart "$TARGET" "$version"
;;
[nN])
echo "Not updating…"
;;
*)
echo -e "${echo_orange?}Please choose y or n.${echo_reset_color?}"
;;
esac
fi
else
if [[ "${version}" == "stable" ]]; then
echo "You're on the latest stable version. If you want to check out the latest 'dev' version, please run \"bash-it update dev\""
else
echo "Bash-it is up to date, nothing to do!"
fi
fi
popd > /dev/null || return
}
function _bash-it-migrate() {
_about 'migrates Bash-it configuration from a previous format to the current one'
_group 'lib'
local migrated_something component_type component_name single_type file_type _bash_it_config_file disable_func enable_func
migrated_something=false
for file_type in "aliases" "plugins" "completion"; do
for _bash_it_config_file in "${BASH_IT}/$file_type/enabled"/*.bash; do
[[ -f "$_bash_it_config_file" ]] || continue
# Get the type of component from the extension
component_type="$(_bash-it-get-component-type-from-path "$_bash_it_config_file")"
# Cut off the optional "250---" prefix and the suffix
component_name="$(_bash-it-get-component-name-from-path "$_bash_it_config_file")"
migrated_something=true
single_type="${component_type/aliases/aliass}"
echo "Migrating ${single_type%s} $component_name."
disable_func="_disable-${single_type%s}"
enable_func="_enable-${single_type%s}"
"$disable_func" "$component_name"
"$enable_func" "$component_name"
done
done
if [[ -n "${BASH_IT_AUTOMATIC_RELOAD_AFTER_CONFIG_CHANGE:-}" ]]; then
_bash-it-reload
fi
if [[ "$migrated_something" == "true" ]]; then
echo ""
echo "If any migration errors were reported, please try the following: reload && bash-it migrate"
fi
}
function _bash-it-version() {
_about 'shows current Bash-it version'
_group 'lib'
local BASH_IT_GIT_REMOTE BASH_IT_GIT_URL current_tag BASH_IT_GIT_VERSION_INFO TARGET
pushd "${BASH_IT?}" > /dev/null || return
if [[ -z "${BASH_IT_REMOTE:-}" ]]; then
BASH_IT_REMOTE=$(_get-git-default-remote-name)
fi
BASH_IT_GIT_REMOTE="$(git remote get-url "$BASH_IT_REMOTE")"
BASH_IT_GIT_URL="${BASH_IT_GIT_REMOTE%.git}"
if [[ "$BASH_IT_GIT_URL" == *"git@"* ]]; then
# Fix URL in case it is ssh based URL
BASH_IT_GIT_URL="${BASH_IT_GIT_URL/://}"
BASH_IT_GIT_URL="${BASH_IT_GIT_URL/git@/https://}"
fi
current_tag="$(git describe --exact-match --tags 2> /dev/null)"
if [[ -z "$current_tag" ]]; then
BASH_IT_GIT_VERSION_INFO="$(git log --pretty=format:'%h on %aI' -n 1)"
TARGET="${BASH_IT_GIT_VERSION_INFO%% *}"
echo "Version type: dev"
echo "Current git SHA: $BASH_IT_GIT_VERSION_INFO"
echo "Commit info: $BASH_IT_GIT_URL/commit/$TARGET"
else
TARGET="$current_tag"
echo "Version type: stable"
echo "Current tag: $current_tag"
echo "Tag information: $BASH_IT_GIT_URL/releases/tag/$current_tag"
fi
echo "Compare to latest: $BASH_IT_GIT_URL/compare/$TARGET...master"
popd > /dev/null || return
}
function _bash-it-doctor() {
_about 'reloads a profile file with a BASH_IT_LOG_LEVEL set'
_param '1: BASH_IT_LOG_LEVEL argument: "errors" "warnings" "all"'
_group 'lib'
# shellcheck disable=SC2034 # expected for this case
local BASH_IT_LOG_LEVEL="${1?}"
_bash-it-reload
}
function _bash-it-doctor-all() {
_about 'reloads a profile file with error, warning and debug logs'
_group 'lib'
_bash-it-doctor "${BASH_IT_LOG_LEVEL_ALL?}"
}
function _bash-it-doctor-warnings() {
_about 'reloads a profile file with error and warning logs'
_group 'lib'
_bash-it-doctor "${BASH_IT_LOG_LEVEL_WARNING?}"
}
function _bash-it-doctor-errors() {
_about 'reloads a profile file with error logs'
_group 'lib'
_bash-it-doctor "${BASH_IT_LOG_LEVEL_ERROR?}"
}
function _bash-it-doctor-check-profile-sourcing-grep() {
_about 'checks if .bashrc is sourced from profile using grep'
_param '1: profile file path'
_group 'lib'
local profile_file="${1}"
[[ ! -f "$profile_file" ]] && return 1
# Look for common patterns that source .bashrc
command grep -qE '(source|\.)\s+(~|\$HOME|"\$HOME")?/\.bashrc|if.*BASH_VERSION.*bashrc' "$profile_file"
}
function _bash-it-doctor-check-profile-sourcing-test() {
_about 'checks if .bashrc is actually sourced using brute force test'
_group 'lib'
local bashrc="$HOME/.bashrc"
[[ ! -f "$bashrc" ]] && return 1
local backup_bashrc="/tmp/.bashrc_backup_$$"
# Move .bashrc aside
command mv "$bashrc" "$backup_bashrc" 2> /dev/null || return 1
# Create test .bashrc that just echoes
echo 'echo "__BASHRC_WAS_SOURCED__"' > "$bashrc"
# Test in login shell, capture output
local output
output=$(bash -l -c ':' 2>&1)
# Restore immediately
command mv "$backup_bashrc" "$bashrc"
# Check if our marker appeared
command grep -q "__BASHRC_WAS_SOURCED__" <<< "$output"
}
function _bash-it-doctor-check-profile-sourcing() {
_about 'checks if .bashrc is sourced from login shell profile files'
_group 'lib'
local profile_file
if [[ -f "$HOME/.bash_profile" ]]; then
profile_file="$HOME/.bash_profile"
elif [[ -f "$HOME/.profile" ]]; then
profile_file="$HOME/.profile"
else
echo "${YELLOW}No .bash_profile or .profile found${RESET}"
echo "Login shells may not load bash-it configuration"
return
fi
# Show if it's a symlink
if [[ -L "$profile_file" ]]; then
echo "${YELLOW}Note:${RESET} $profile_file is a symlink to $(readlink "$profile_file")"
fi
# Try grep detection first (fast and safe)
if _bash-it-doctor-check-profile-sourcing-grep "$profile_file"; then
echo "${GREEN}✓${RESET} .bashrc is sourced from $profile_file"
return 0
fi
# Grep didn't find it, try brute force test
echo "Grep detection unclear, testing if .bashrc actually loads..."
if _bash-it-doctor-check-profile-sourcing-test; then
echo "${GREEN}✓${RESET} .bashrc is sourced (confirmed via test)"
return 0
fi
# Not sourced
echo "${RED}✗${RESET} .bashrc is NOT sourced from $profile_file"
echo " ${YELLOW}Warning:${RESET} bash-it will not load in login shells (Terminal.app, SSH sessions)"
echo " ${YELLOW}Fix:${RESET} Add the following to $profile_file:"
echo ""
echo " if [ -n \"\$BASH_VERSION\" ]; then"
echo " if [ -f \"\$HOME/.bashrc\" ]; then"
echo " . \"\$HOME/.bashrc\""
echo " fi"
echo " fi"
echo ""
}
function _bash-it-doctor-summary() {
_about 'shows a comprehensive diagnostic summary for bug reports'
_group 'lib'
local component_type enabled_count enabled_list f component_name
# Color definitions
local BOLD CYAN GREEN YELLOW RESET
BOLD=$(tput bold 2> /dev/null || echo "")
CYAN=$(tput setaf 6 2> /dev/null || echo "")
GREEN=$(tput setaf 2 2> /dev/null || echo "")
YELLOW=$(tput setaf 3 2> /dev/null || echo "")
RESET=$(tput sgr0 2> /dev/null || echo "")
echo "${BOLD}${CYAN}Bash-it Doctor Summary${RESET}"
echo "${CYAN}======================${RESET}"
echo ""
# Environment Information
echo "${BOLD}## Environment${RESET}"
echo "${GREEN}OS:${RESET} $(uname -s) $(uname -r)"
echo "${GREEN}Bash Version:${RESET} ${BASH_VERSION}"
echo "${GREEN}Bash-it Location:${RESET} ${BASH_IT}"
# Check which config file is used
local config_file
if [[ -n "${BASH_IT_BASHRC:-}" ]]; then
config_file="${BASH_IT_BASHRC}"
elif [[ -f "${HOME}/.bashrc" ]]; then
config_file="${HOME}/.bashrc"
elif [[ -f "${HOME}/.bash_profile" ]]; then
config_file="${HOME}/.bash_profile"
else
config_file="unknown"
fi
echo "${GREEN}Config File:${RESET} ${config_file}"
echo ""
# Bash-it Version Information
echo "${BOLD}## Bash-it Version${RESET}"
pushd "${BASH_IT}" > /dev/null 2>&1 || {
echo "Error: Cannot access Bash-it directory"
return 1
}
local current_commit current_tag commits_behind latest_tag commits_since_tag
current_commit="$(git rev-parse --short HEAD 2> /dev/null || echo 'unknown')"
current_tag="$(git describe --exact-match --tags 2> /dev/null || echo 'none')"
if [[ -z "${BASH_IT_REMOTE:-}" ]]; then
BASH_IT_REMOTE="origin"
fi
# Get version info relative to tags
latest_tag="$(git describe --tags --abbrev=0 2> /dev/null || echo 'none')"
commits_since_tag="$(git rev-list --count "${latest_tag}..HEAD" 2> /dev/null || echo '0')"
if [[ "${current_tag}" != "none" ]]; then
echo "${GREEN}Current Version:${RESET} ${current_tag} (${current_commit})"
elif [[ "${latest_tag}" != "none" && "${commits_since_tag}" != "0" ]]; then
echo "${GREEN}Current Version:${RESET} ${latest_tag} +${commits_since_tag} (${current_commit})"
else
echo "${GREEN}Current Commit:${RESET} ${current_commit}"
fi
# Check how far behind we are
git fetch "${BASH_IT_REMOTE}" --quiet 2> /dev/null
if [[ -z "${BASH_IT_DEVELOPMENT_BRANCH:-}" ]]; then
BASH_IT_DEVELOPMENT_BRANCH="master"
fi
commits_behind="$(git rev-list --count HEAD.."${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH}" 2> /dev/null || echo 'unknown')"
if [[ "${commits_behind}" == "0" ]]; then
echo "${GREEN}Status:${RESET} Up to date with ${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH} ✓"
elif [[ "${commits_behind}" != "unknown" ]]; then
echo "${YELLOW}Status:${RESET} ${commits_behind} commits behind ${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH}"
# Offer to update if behind and it's safe to do so
local git_status untracked_files merge_base can_ff
git_status="$(git status --porcelain 2> /dev/null)"
untracked_files="$(echo "$git_status" | command grep -c '^??' || true)"
# Check if we can fast-forward
merge_base="$(git merge-base HEAD "${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH}" 2> /dev/null)"
can_ff=false
if [[ "$(git rev-parse HEAD 2> /dev/null)" == "$merge_base" ]]; then
can_ff=true
fi
# Only offer merge if:
# 1. No modified/staged files (untracked are OK)
# 2. Can fast-forward OR no untracked files that would conflict
if ! echo "$git_status" | command grep -v '^??' -q; then
if [[ "$can_ff" == "true" ]] || [[ "$untracked_files" == "0" ]]; then
echo ""
echo "Would you like to update now? This will merge ${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH} into your current branch."
read -r -p "Update? [y/N] " response
case "$response" in
[yY] | [yY][eE][sS])
echo "Updating bash-it..."
if git merge "${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH}" --ff-only 2> /dev/null; then
echo "✓ Successfully updated to latest version!"
echo ""
echo "Please restart your shell or run: source ~/.bashrc"
else
echo "✗ Fast-forward merge failed. Please run 'bash-it update' for a guided update."
fi
;;
*)
echo "Skipping update. You can update later with: bash-it update"
;;
esac
else
echo ""
echo "Note: Cannot safely auto-update (untracked files may conflict). Use: bash-it update"
fi
else
echo ""
echo "Note: Cannot auto-update (uncommitted changes present). Use: bash-it update"
fi
fi
popd > /dev/null 2>&1 || true
echo ""
# Bash-it Loading Configuration
echo "${BOLD}## Bash-it Loading${RESET}"
local config_files_to_check=()
local config_file_path
# Check all common config files
for config_file_path in "${HOME}/.bashrc" "${HOME}/.bash_profile" "${HOME}/.profile"; do
[[ -f "$config_file_path" ]] && config_files_to_check+=("$config_file_path")
done
if [[ ${#config_files_to_check[@]} -gt 0 ]]; then
for config_file_path in "${config_files_to_check[@]}"; do
if command grep -i "bash.it\|bash_it" "$config_file_path" > /dev/null 2>&1; then
echo "From ${config_file_path}:"
command grep -n -i "bash.it\|bash_it" -B2 -A2 "$config_file_path" 2> /dev/null
echo ""
fi
done
else
echo "No config files found (.bashrc, .bash_profile, .profile)"
fi
# Profile Sourcing Check (macOS/Solaris/BSD)
echo "${BOLD}## Profile Configuration${RESET}"
case "$(uname -s)" in
Darwin | SunOS | Illumos | *BSD)
_bash-it-doctor-check-profile-sourcing
;;
*)
echo "Not applicable (Linux uses .bashrc for non-login shells by default)"
;;
esac
echo ""
# Enabled Components Summary
echo "${BOLD}## Enabled Components${RESET}"
# Process each component type
for component_type in aliases plugins completion; do
enabled_count=0
enabled_list=()
# Get singular form for display
local display_type="${component_type}"
if [[ "$component_type" == "aliases" ]]; then
display_type="Aliases"
elif [[ "$component_type" == "plugins" ]]; then
display_type="Plugins"
else
display_type="Completions"
fi
# Count and collect enabled components
for f in "${BASH_IT?}/$component_type/available"/*.*.bash; do
[[ -f "$f" ]] || continue
component_name="$(_bash-it-get-component-name-from-path "$f")"
if _bash-it-component-item-is-enabled "$f"; then
enabled_list+=("$component_name")
((enabled_count++))
fi
done
# Display the summary with colors
if [[ $enabled_count -eq 0 ]]; then
printf '%s%s%s (%s): %s\n' "$CYAN" "$display_type" "$RESET" "$enabled_count" "${YELLOW}none${RESET}"
else
printf '%s%s%s (%s): %s\n' "$CYAN" "$display_type" "$RESET" "$enabled_count" "${enabled_list[*]}"
fi
done
echo ""
echo "${YELLOW}Tip:${RESET} To copy this report: ${CYAN}bash-it doctor${RESET} | pbcopy (macOS) or xclip (Linux)"
}
function _bash-it-doctor-() {
_about 'default bash-it doctor behavior, shows component summary'
_group 'lib'
_bash-it-doctor-summary
}
function _bash-it-profile-save() {
_about 'saves the current configuration to the "profile" directory'
_group 'lib'
local name="${1:-}"
while [[ -z "$name" ]]; do
read -r -e -p "Please enter the name of the profile to save: " name
case "$name" in
"")
echo -e "${echo_orange?}Please choose a name.${echo_reset_color?}"
;;
*)
break
;;
esac
done
local profile_path="${BASH_IT}/profiles/${name}.bash_it" RESP
if [[ -s "$profile_path" ]]; then
echo -e "${echo_yellow?}Profile '$name' already exists.${echo_reset_color?}"
while true; do
read -r -e -n 1 -p "Would you like to overwrite existing profile? [y/N] " RESP
case "$RESP" in
[yY])
echo -e "${echo_green?}Overwriting profile '$name'...${echo_reset_color?}"
break
;;
[nN] | "")
echo -e "${echo_orange?}Aborting profile save...${echo_reset_color?}"
return 1
;;
*)
echo -e "${echo_orange?}Please choose y or n.${echo_reset_color?}"
;;
esac
done
fi
local something_exists subdirectory component_exists f enabled_file
echo "# This file is auto generated by Bash-it. Do not edit manually!" > "$profile_path"
for subdirectory in "plugins" "completion" "aliases"; do
echo "Saving $subdirectory configuration..."
for f in "${BASH_IT}/$subdirectory/available"/*.bash; do
if _bash-it-component-item-is-enabled "$f"; then
if [[ -z "${component_exists:-}" ]]; then
# This is the first component of this type, print the header
component_exists="yes"
something_exists="yes"
echo "" >> "$profile_path"
echo "# $subdirectory" >> "$profile_path"
fi
enabled_file="$(_bash-it-get-component-name-from-path "$f")"
echo "$subdirectory $enabled_file" >> "$profile_path"
fi
done
done
if [[ -z "${something_exists:-}" ]]; then
echo "It seems like no configuration was enabled.."
echo "Make sure to double check that this is the wanted behavior."
fi
echo "All done!"
echo ""
echo "Profile location: $profile_path"
echo "Load the profile by invoking \"bash-it profile load $name\""
}
_bash-it-profile-load-parse-profile() {
_about 'Internal function used to parse the profile file'
_param '1: path to the profile file'
_param '2: dry run- only check integrity of the profile file'
_example '$ _bash-it-profile-load-parse-profile "profile.bash_it" "dry"'
local -i num=0
local line enable_func subdirectory component to_enable bad
while read -r -a line; do
((++num))
# Ignore comments and empty lines
[[ -z "${line[*]}" || "${line[*]}" =~ ^#.* ]] && continue
enable_func="_enable-${line[0]}"
subdirectory=${line[0]}
component=${line[1]}
to_enable=("${BASH_IT}/$subdirectory/available/$component.${subdirectory%s}"*.bash)
# Ignore botched lines
if [[ ! -e "${to_enable[0]}" ]]; then
echo -e "${echo_orange?}Bad line(#$num) in profile, aborting load...${line[*]}${echo_reset_color?}"
bad="bad line"
break
fi
# Do not actually modify config on dry run
[[ -z "${2:-}" ]] || continue
# Actually enable the component
$enable_func "$component"
done < "${1?}"
# Make sure to propagate the error
[[ -z ${bad:-} ]]
}
_bash-it-profile-list() {
about 'lists all profiles from the "profiles" directory'
_group 'lib'
local profile
echo "Available profiles:"
for profile in "${BASH_IT}/profiles"/*.bash_it; do
profile="${profile##*/}"
echo "${profile/.bash_it/}"
done
}
_bash-it-profile-rm() {
about 'Removes a profile from the "profiles" directory'
_group 'lib'
local name="${1:-}"
if [[ -z $name ]]; then
echo -e "${echo_orange?}Please specify profile name to remove...${echo_reset_color?}"
return 1
fi
# Users should not be allowed to delete the default profile
if [[ $name == "default" ]]; then
echo -e "${echo_orange?}Can not remove the default profile...${echo_reset_color?}"
return 1
fi
local profile_path="${BASH_IT}/profiles/$name.bash_it"
if [[ ! -f "$profile_path" ]]; then
echo -e "${echo_orange?}Could not find profile '$name'...${echo_reset_color?}"
return 1
fi
command rm "$profile_path"
echo "Removed profile '$name' successfully!"
}
_bash-it-profile-load() {
_about 'loads a configuration from the "profiles" directory'
_group 'lib'
local name="${1:-}"
if [[ -z $name ]]; then
echo -e "${echo_orange?}Please specify profile name to load, not changing configuration...${echo_reset_color?}"
return 1
fi
local profile_path="${BASH_IT}/profiles/$name.bash_it"
if [[ ! -f "$profile_path" ]]; then
echo -e "${echo_orange?}Could not find profile '$name', not changing configuration...${echo_reset_color?}"
return 1
fi
echo "Trying to parse profile '$name'..."
if _bash-it-profile-load-parse-profile "$profile_path" "dry"; then
echo "Profile '$name' parsed successfully!"
echo "Disabling current configuration..."
_disable-all
echo ""
echo "Enabling configuration based on profile..."
_bash-it-profile-load-parse-profile "$profile_path"
echo ""
echo "Profile '$name' enabled!"
else
false # failure
fi
}
function _bash-it-restart() {
_about 'restarts the shell in order to fully reload it'
_group 'lib'
exec "${0#-}" --rcfile "${BASH_IT_BASHRC:-${HOME?}/.bashrc}"
}
function _bash-it-reload() {
_about 'reloads the shell initialization file'
_group 'lib'
# shellcheck disable=SC1090
source "${BASH_IT_BASHRC:-${HOME?}/.bashrc}"
}
function _bash-it-describe() {
_about 'summarizes available bash_it components'
_param '1: subdirectory'
_param '2: preposition'
_param '3: file_type'
_param '4: column_header'
_example '$ _bash-it-describe "plugins" "a" "plugin" "Plugin"'
local subdirectory preposition file_type column_header f enabled enabled_file
subdirectory="$1"
preposition="$2"
file_type="$3"
column_header="$4"
printf "%-20s %-10s %s\n" "$column_header" 'Enabled?' 'Description'
for f in "${BASH_IT?}/$subdirectory/available"/*.*.bash; do
enabled=''
enabled_file="${f##*/}"
enabled_file="${enabled_file%."${file_type}"*.bash}"
_bash-it-component-item-is-enabled "${file_type}" "${enabled_file}" && enabled='x'
printf "%-20s %-10s %s\n" "$enabled_file" "[${enabled:- }]" "$(metafor "about-$file_type" < "$f")"
done
printf '\n%s\n' "to enable $preposition $file_type, do:"
printf '%s\n' "$ bash-it enable $file_type <$file_type name> [$file_type name]... -or- $ bash-it enable $file_type all"
printf '\n%s\n' "to disable $preposition $file_type, do:"
printf '%s\n' "$ bash-it disable $file_type <$file_type name> [$file_type name]... -or- $ bash-it disable $file_type all"
}
function _on-disable-callback() {
_about 'Calls the disabled plugin destructor, if present'
_param '1: plugin name'
_example '$ _on-disable-callback gitstatus'
_group 'lib'
local callback="${1}_on_disable"
if _command_exists "$callback"; then
"$callback"
fi
}
function _disable-all() {
_about 'disables all bash_it components'
_example '$ _disable-all'
_group 'lib'
_disable-plugin "all"
_disable-alias "all"
_disable-completion "all"
}
function _disable-plugin() {
_about 'disables bash_it plugin'
_param '1: plugin name'
_example '$ disable-plugin rvm'
_group 'lib'
_disable-thing "plugins" "plugin" "${1?}"
_on-disable-callback "${1?}"
}
function _disable-alias() {
_about 'disables bash_it alias'
_param '1: alias name'
_example '$ disable-alias git'
_group 'lib'
_disable-thing "aliases" "alias" "${1?}"
}
function _disable-completion() {
_about 'disables bash_it completion'
_param '1: completion name'
_example '$ disable-completion git'
_group 'lib'
_disable-thing "completion" "completion" "${1?}"
}
function _disable-thing() {
_about 'disables a bash_it component'
_param '1: subdirectory'
_param '2: file_type'
_param '3: file_entity'
_example '$ _disable-thing "plugins" "plugin" "ssh"'
local subdirectory="${1?}"
local file_type="${2?}"
local file_entity="${3:-}"
if [[ -z "$file_entity" ]]; then
reference "disable-$file_type"
return
fi
local f suffix _bash_it_config_file plugin
suffix="${subdirectory/plugins/plugin}"
if [[ "$file_entity" == "all" ]]; then
# Disable everything that's using the old structure and everything in the global "enabled" directory.
for _bash_it_config_file in "${BASH_IT}/$subdirectory/enabled"/*."${suffix}.bash" "${BASH_IT}/enabled"/*".${suffix}.bash"; do
rm -f "$_bash_it_config_file"
done
else
# Use a glob to search for both possible patterns
# 250---node.plugin.bash
# node.plugin.bash
# Either one will be matched by this glob
for plugin in "${BASH_IT}/enabled"/[[:digit:]][[:digit:]][[:digit:]]"${BASH_IT_LOAD_PRIORITY_SEPARATOR}${file_entity}.${suffix}.bash" "${BASH_IT}/$subdirectory/enabled/"{[[:digit:]][[:digit:]][[:digit:]]"${BASH_IT_LOAD_PRIORITY_SEPARATOR}${file_entity}.${suffix}.bash","${file_entity}.${suffix}.bash"}; do
if [[ -e "${plugin}" ]]; then
rm -f "${plugin}"
plugin=