-
Notifications
You must be signed in to change notification settings - Fork 124
/
vcsh.in
executable file
·768 lines (694 loc) · 23.7 KB
/
vcsh.in
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
#!@SHELL@
# Copyright (C) 2011-2015 Richard "RichiH" Hartmann <richih@debian.org>, see CONTRIBUTORS for details.
# SPDX-License-Identifier: GPL-2.0-or-later
#
# While the following is not legally binding, the author would like to explain
# the choice of GPLv2+ over GPLv3+. The author prefers GPLv3+ over GPLv2+ but
# feels it's better to maintain full compatibility's with Git. In case Git ever
# changes its licensing terms, which is admittedly extremely unlikely to the
# point of being impossible, this software will most likely follow suit.
# This should always be the first line of code to facilitate debugging
[ -n "$VCSH_DEBUG" ] && set -vx
# If '.r<N>-g<SHA>' is appended to the version, you are seeing an unreleased
# version built from the main branch HEAD. If "-standalone" is appended you are
# using a version built explicitly for portability as a standalone script
# rather than being installed to a specific system.
VCSH_VERSION='@VERSION@@DEPLOYMENT@'; export VCSH_VERSION
VCSH_SELF='@TRANSFORMED_PACKAGE_NAME@'; export VCSH_SELF
# Ensure all files created are accessible only to the current user.
umask 0077
fatal() {
echo "$VCSH_SELF: fatal: $1" >&2
[ -z "$2" ] && exit 1
exit "$2"
}
# We need to run getops as soon as possible so we catch -d and other
# options that will modify our behaviour.
# Commands are handled at the end of this script.
# shellcheck disable=SC2220
while getopts c:dv flag; do
case "$flag" in
d)
set -vx
VCSH_DEBUG=1
export VCSH_DEBUG
echo 'debug mode on'
echo "$VCSH_SELF $VCSH_VERSION"
;;
v)
VCSH_VERBOSE=1
export VCSH_VERBOSE
echo 'verbose mode on'
echo "$VCSH_SELF $VCSH_VERSION"
;;
c)
VCSH_OPTION_CONFIG="$OPTARG"
export VCSH_OPTION_CONFIG
;;
esac
done
shift $((OPTIND-1))
source_all() {
# Source file even if it's in $PWD and does not have any slashes in it
# shellcheck source=/dev/null
case "$1" in
*/*) . "$1";;
*) . "$PWD/$1";;
esac;
}
# Read configuration and set defaults if anything's not set
[ -n "$VCSH_DEBUG" ] && set -vx
: "${XDG_CONFIG_HOME:="$HOME/.config"}"
# Read configuration files if there are any
# shellcheck source=/dev/null
[ -r "/etc/vcsh/config" ] && . "/etc/vcsh/config"
# shellcheck source=/dev/null
[ -r "$XDG_CONFIG_HOME/vcsh/config" ] && . "$XDG_CONFIG_HOME/vcsh/config"
if [ -n "$VCSH_OPTION_CONFIG" ]; then
# Source $VCSH_OPTION_CONFIG if it can be read and is in $PWD of $PATH
if [ -r "$VCSH_OPTION_CONFIG" ]; then
source_all "$VCSH_OPTION_CONFIG"
else
fatal "Can not read configuration file '$VCSH_OPTION_CONFIG'" 1
fi
fi
[ -n "$VCSH_DEBUG" ] && set -vx
# Read defaults
: "${VCSH_REPO_D:="$XDG_CONFIG_HOME/vcsh/repo.d"}"; export VCSH_REPO_D
: "${VCSH_HOOK_D:="$XDG_CONFIG_HOME/vcsh/hooks-enabled"}"; export VCSH_HOOK_D
: "${VCSH_OVERLAY_D:="$XDG_CONFIG_HOME/vcsh/overlays-enabled"}"; export VCSH_OVERLAY_D
: "${VCSH_BASE:="$HOME"}"; export VCSH_BASE
: "${VCSH_GITIGNORE:=exact}"; export VCSH_GITIGNORE
: "${VCSH_GITATTRIBUTES:=none}"; export VCSH_GITATTRIBUTES
: "${VCSH_WORKTREE:=absolute}"; export VCSH_WORKTREE
if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
fi
if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ] && [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
fatal "'\$VCSH_WORKTREE' must equal 'absolute', or 'relative'" 1
fi
# editorconfig-checker-disable
help() {
echo "usage: $VCSH_SELF <options> <command>
options:
-c <file> Source file
-d Enable debug mode
-v Enable verbose mode
commands:
clone [-b <branch>] \\
<remote> \\
[<repo>] Clone from an existing repository
commit Commit in all repositories
delete <repo> Delete an existing repository
enter <repo> Enter repository; spawn new instance of \$SHELL
with \$GIT_DIR set.
foreach \\
[<-g>] [<-p>] \\
<git command> Execute a command for every repository
help Display this help text
init <repo> Initialize a new repository
list List all repositories
list-tracked \\
[<repo>] List all files tracked all or one repositories
list-untracked \\
[<-a>] [<-r>]
[<repo>] List all files not tracked by all or one repositories
pull Pull from all vcsh remotes
push Push to vcsh remotes
rename <repo> \\
<newname> Rename repository
run <repo> \\
<command> Use this repository
status \\
[--terse] [<repo>] Show statuses of all/one vcsh repositories
upgrade <repo> Upgrade repository to currently recommended settings
version Print version information
which <substring> Find substring in name of any tracked file
write-gitignore \\
[<repo>] Write .gitignore.d/<repo> via git ls-files
<repo> <git command> Shortcut to run git commands directly
<repo> Shortcut to enter repository" >&2
}
# editorconfig-checker-enable
debug() {
if [ -n "$VCSH_DEBUG" ]; then echo "$VCSH_SELF: debug: $*"; fi
}
verbose() {
if [ -n "$VCSH_DEBUG" ] || [ -n "$VCSH_VERBOSE" ]; then
echo "$VCSH_SELF: verbose: $*"
fi
}
error() {
echo "$VCSH_SELF: error: $1" >&2
}
info() {
echo "$VCSH_SELF: info: $1"
}
clone() {
hook pre-clone
# Check if remote is reachable. Abort early if there's a typo, TLS certificate problem, etc
@GIT@ ls-remote "$GIT_REMOTE" 2> /dev/null || fatal "Can not reach '$GIT_REMOTE'"
init
# Test which, if any, given or detected branches can be pulled from.
# In a future version, if we need the logic, we could do the following:
# git ls-remote ORIGIN
# get HEAD commit ID
# see what refs/heads/* match that commit ID
# set VCSH_BRANCH if only one match
# offer a list of all matching refs for the user to choose
for VCSH_BRANCH_TEST in "$VCSH_BRANCH" master trunk development; do
if [ $(@GIT@ ls-remote "$GIT_REMOTE" "$VCSH_BRANCH_TEST" 2> /dev/null | @WC@ -l ) -lt 1 ]; then
info "remote branch '$VCSH_BRANCH_TEST' empty"
else
info "remote branch '$VCSH_BRANCH_TEST' found"
VCSH_BRANCH_REMOTE=$VCSH_BRANCH_TEST
break
fi
done
if [ -z "$VCSH_BRANCH_REMOTE" ]; then
info "No non-empty remote branches found, aborting. Please run `$0 delete $VCSH_REPO_NAME`, determine the correct branch, and try again."
exit
fi
VCSH_BRANCH=$VCSH_BRANCH_REMOTE
# Set up remote
@GIT@ remote add origin "$GIT_REMOTE"
@GIT@ checkout -b "$VCSH_BRANCH" || return $?
@GIT@ config branch."$VCSH_BRANCH".remote origin
@GIT@ config branch."$VCSH_BRANCH".merge refs/heads/"$VCSH_BRANCH"
GIT_VERSION_MAJOR=$(@GIT@ --version | @SED@ -E -n 's/.* ([0-9]+)\..*/\1/p' )
if [ 1 -lt "$GIT_VERSION_MAJOR" ];then
@GIT@ fetch origin "$VCSH_BRANCH"
else
@GIT@ fetch origin
fi
hook pre-merge
@GIT@ read-tree -n -mu origin/"$VCSH_BRANCH" \
|| fatal "will stop after fetching and not try to merge!
Once this situation has been resolved, run 'vcsh $VCSH_REPO_NAME pull' to finish cloning." 17 # editorconfig-checker-disable-line
@GIT@ -c merge.ff=true merge origin/"$VCSH_BRANCH"
hook post-merge
hook post-clone
retire
hook post-clone-retired
}
commit() {
hook_global pre-commit
shift # remove the "commit" command.
for VCSH_REPO_NAME in $(list); do
export VCSH_REPO_NAME
echo "$VCSH_REPO_NAME: "
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
use
hook_repo pre-commit
@GIT@ commit --untracked-files=no --quiet "$@"
hook_repo post-commit
VCSH_COMMAND_RETURN_CODE=$?
echo
done
hook_global post-commit
}
delete() {
cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
use
info "This operation WILL DESTROY DATA!"
files=$(@GIT@ ls-files)
echo "These files will be deleted:
$files
AGAIN, THIS WILL DELETE YOUR DATA!
To continue, type 'Yes, do as I say'"
read -r answer
[ "x$answer" = 'xYes, do as I say' ] || exit 16
for file in $files; do
rm -f "$file" || info "could not delete '$file', continuing with deletion"
done
rm -rf "$GIT_DIR" || error "could not delete '$GIT_DIR'"
}
enter() {
hook pre-enter
use
$SHELL
hook post-enter
}
foreach() {
hook_global pre-foreach
# We default to prefixing `git` to all commands passed to foreach, but
# allow running in general context with -g
command_prefix=@GIT@
# shellcheck disable=SC2220
while getopts gp flag; do
case "$flag" in
g) unset command_prefix ;;
p) VCSH_PRINT_REPO_PREFIX=1 ;;
esac
done
shift $((OPTIND-1))
export VCSH_PRINT_REPO_PREFIX
for VCSH_REPO_NAME in $(list); do
export VCSH_REPO_NAME
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
use
hook_repo pre-foreach
if [ -n "${VCSH_PRINT_REPO_PREFIX+x}" ]; then
$command_prefix "$@" | @SED@ "s/^/$VCSH_REPO_NAME: /"
else
echo "$VCSH_REPO_NAME:"
$command_prefix "$@"
fi
hook_repo post-foreach
done
hook_global post-foreach
}
git_dir_exists() {
[ -d "$GIT_DIR" ] || fatal "no repository found for '$VCSH_REPO_NAME'" 12
}
hook_global() {
for hook in "$VCSH_HOOK_D/$1"*; do
[ -x "$hook" ] || continue
verbose "executing '$hook'"
"$hook"
done
}
hook_repo() {
for hook in "$VCSH_HOOK_D/$VCSH_REPO_NAME.$1"*; do
[ -x "$hook" ] || continue
verbose "executing '$hook'"
"$hook" || fatal "hook [$hook] failed" 1
done
}
hook() {
hook_global "$1"
hook_repo "$1"
}
init() {
hook pre-init
[ ! -e "$GIT_DIR" ] || fatal "'$GIT_DIR' exists" 10
mkdir -p "$VCSH_BASE" || fatal "could not create '$VCSH_BASE'" 50
cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
@GIT@ init --shared=false
upgrade
hook post-init
}
list() {
for repo in "$VCSH_REPO_D"/*.git; do
[ -d "$repo" ] && [ -r "$repo" ] && basename "$repo" .git
done
}
list_has_remote() {
for VCSH_REPO_NAME in $(list); do
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
# This command returns the tracking branch of the currently-checked-out local
# branch, if any. See https://stackoverflow.com/a/9753364
[ -n "$(@GIT@ for-each-ref "$(@GIT@ symbolic-ref -q HEAD)")" ] && echo "$VCSH_REPO_NAME"
done
}
get_files() {
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
@GIT@ ls-files --full-name
}
list_tracked() {
VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
if [ -n "$VCSH_REPO_NAME" ]; then
get_files | list_tracked_helper
else
for VCSH_REPO_NAME in $(list); do
get_files
done | list_tracked_helper
fi
}
list_tracked_helper() {
@SED@ "s,^,$(printf '%s\n' "$VCSH_BASE/" | @SED@ 's/[,\&]/\\&/g')," | sort -u
}
list_tracked_by() {
list_tracked '' "$2"
}
list_untracked() {
temp_file_others=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
temp_file_untracked=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
temp_file_untracked_copy=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
# Hack in support for `vcsh list-untracked -r`...
exclude_standard_opt='--exclude-standard'
directory_opt='--directory'
shift
while getopts ar flag; do
case "$flag" in
a) unset exclude_standard_opt ;;
r) unset directory_opt ;;
esac
done
shift $((OPTIND-1))
# ...and parse for a potential parameter afterwards. As we shifted things out of $* in during getops, we need to look at $1
VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
if [ -n "$VCSH_REPO_NAME" ]; then
list_untracked_helper "$VCSH_REPO_NAME"
else
for VCSH_REPO_NAME in $(list); do
list_untracked_helper "$VCSH_REPO_NAME"
done
fi
cat "$temp_file_untracked"
unset directory_opt directory_component
rm -f "$temp_file_others" "$temp_file_untracked" "$temp_file_untracked_copy" || fatal 'Could not delete temp files'
}
list_untracked_helper() {
export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
@GIT@ ls-files --others $exclude_standard_opt $directory_opt | (
while read -r line; do
echo "$line"
directory_component=${line%%/*}
[ -d "$directory_component" ] && printf '%s/\n' "$directory_component"
done
) | sort -u > "$temp_file_others"
if [ -z "$ran_once" ]; then
ran_once=1
cp "$temp_file_others" "$temp_file_untracked" || fatal 'Could not copy temp file'
fi
cp "$temp_file_untracked" "$temp_file_untracked_copy" || fatal 'Could not copy temp file'
@COMM@ -12 "$temp_file_others" "$temp_file_untracked_copy" > "$temp_file_untracked"
}
pull() {
hook_global pre-pull
for VCSH_REPO_NAME in $(list_has_remote); do
export VCSH_REPO_NAME
printf '%s: ' "$VCSH_REPO_NAME"
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
use
hook_repo pre-pull
@GIT@ pull
hook_repo post-pull
VCSH_COMMAND_RETURN_CODE=$?
echo
done
hook_global post-pull
}
push() {
hook_global pre-push
for VCSH_REPO_NAME in $(list_has_remote); do
export VCSH_REPO_NAME
printf '%s: ' "$VCSH_REPO_NAME"
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
use
hook_repo pre-push
@GIT@ push
hook_repo post-push
VCSH_COMMAND_RETURN_CODE=$?
echo
done
hook_global post-push
}
retire() {
unset VCSH_DIRECTORY
}
rename() {
git_dir_exists
[ -d "$GIT_DIR_NEW" ] && fatal "'$GIT_DIR_NEW' exists" 54
mv -f "$GIT_DIR" "$GIT_DIR_NEW" || fatal "Could not mv '$GIT_DIR' '$GIT_DIR_NEW'" 52
# Now that the repository has been renamed, we need to fix up its configuration
# Overwrite old name..
GIT_DIR=$GIT_DIR_NEW
VCSH_REPO_NAME=$VCSH_REPO_NAME_NEW
# ..and clobber all old configuration
upgrade
}
run() {
hook pre-run
use
"$@"
VCSH_COMMAND_RETURN_CODE=$?
hook post-run
}
status() {
if [ -t 1 ]; then
COLORING="-c color.status=always"
fi
if [ -n "$VCSH_REPO_NAME" ]; then
status_helper "$VCSH_REPO_NAME"
else
for VCSH_REPO_NAME in $(list); do
STATUS="$(status_helper "$VCSH_REPO_NAME" "$COLORING")"
[ -n "$STATUS" ] || [ -z "$VCSH_STATUS_TERSE" ] && echo "$VCSH_REPO_NAME:"
[ -n "$STATUS" ] && echo "$STATUS"
[ -z "$VCSH_STATUS_TERSE" ] && echo
done
fi
}
# Warning: disabled quoting check here due to all of them relating to
# wc -l output and a deliberate lack of quoting for git args.
# shellcheck disable=SC2086
status_helper() {
GIT_DIR=$VCSH_REPO_D/$1.git; export GIT_DIR
VCSH_GIT_OPTIONS="-c status.relativePaths=false $2"
use
# Shellcheck isn't understanding a complex block.
# shellcheck disable=SC1083
remote_tracking_branch=$(@GIT@ rev-parse --abbrev-ref --symbolic-full-name @{u} 2> /dev/null) && {
commits_behind=$(@GIT@ log ..${remote_tracking_branch} --oneline | @WC@ -l)
commits_ahead=$(@GIT@ log ${remote_tracking_branch}.. --oneline | @WC@ -l)
[ ${commits_behind} -ne 0 ] && echo "Behind $remote_tracking_branch by $commits_behind commits"
[ ${commits_ahead} -ne 0 ] && echo "Ahead of $remote_tracking_branch by $commits_ahead commits"
}
@GIT@ ${VCSH_GIT_OPTIONS} status --short --untracked-files='no' | @SED@ -E 's@([^ ] +)@\1~/@'
VCSH_COMMAND_RETURN_CODE=$?
}
upgrade() {
hook pre-upgrade
# fake-bare repositories are not bare, actually. Set this to false
# because otherwise Git complains "fatal: core.bare and core.worktree
# do not make sense"
@GIT@ config core.bare false
# core.worktree may be absolute or relative to $GIT_DIR, depending on
# user preference
if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ]; then
@GIT@ config core.worktree "$(cd "$GIT_DIR" && GIT_WORK_TREE=$VCSH_BASE @GIT@ rev-parse --show-cdup)"
elif [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
@GIT@ config core.worktree "$VCSH_BASE"
fi
[ ! "x$VCSH_GITIGNORE" = 'xnone' ] && @GIT@ config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
[ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && @GIT@ config core.attributesfile ".gitattributes.d/$VCSH_REPO_NAME"
@GIT@ config vcsh.vcsh 'true'
use
[ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && @GIT@ add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
[ -e "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME" ] && @GIT@ add -f "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME"
hook post-upgrade
}
use() {
git_dir_exists
VCSH_DIRECTORY=$VCSH_REPO_NAME; export VCSH_DIRECTORY
}
which() {
# It's ok to modify VCSH_REPO_NAME in a subshell.
# shellcheck disable=SC2030
output=$(for VCSH_REPO_NAME in $(list); do
get_files | @GREP@ -- "$VCSH_COMMAND_PARAMETER" | @SED@ "s/^/$VCSH_REPO_NAME: /"
done | sort -u)
if [ -z "$output" ]; then
fatal "'$VCSH_COMMAND_PARAMETER' does not exist" 1
else
echo "$output"
fi
}
# Ignore warnings about VCSH_REPO_NAME being changed in a subshell.
# shellcheck disable=SC2031
write_gitignore() {
# Don't do anything if the user does not want to write gitignore
if [ "x$VCSH_GITIGNORE" = 'xnone' ]; then
info "Not writing gitignore as '\$VCSH_GITIGNORE' is set to 'none'"
exit
fi
use
cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
# Works in all shells we care about.
# shellcheck disable=SC2039,SC3043
local GIT_VERSION GIT_VERSION_MAJOR GIT_VERSION_MINOR
GIT_VERSION="$(@GIT@ --version)"
GIT_VERSION_MAJOR="$(echo "$GIT_VERSION" | @SED@ -E -n 's/.* ([0-9]+)\..*/\1/p')"
GIT_VERSION_MINOR="$(echo "$GIT_VERSION" | @SED@ -E -n 's/.* ([0-9]+)\.([0-9]+)\..*/\2/p')"
OLDIFS=$IFS
IFS=$(printf '\n\t')
gitignores=$(for file in $(@GIT@ ls-files); do
while true; do
echo "$file"; new=${file%/*}
[ x"$file" = x"$new" ] && break
file=$new
done;
done | sort -u)
# Contrary to GNU mktemp, mktemp on BSD/OSX requires a template for temp files
# Using a template makes GNU mktemp default to $PWD and not #TMPDIR for tempfile location
# To make every OS happy, set full path explicitly
local tempfile
tempfile=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") ||
fatal "could not create tempfile: '${tempfile}'" 51
echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
for gitignore in $gitignores; do
echo "$gitignore" | @SED@ 's@^@!/@' >> "$tempfile" ||
fatal "could not write to '$tempfile'" 57
if [ "x$VCSH_GITIGNORE" = 'xrecursive' ] && [ -d "$gitignore" ]; then
{ echo "$gitignore/*" | @SED@ 's@^@!/@' >> "$tempfile" ||
fatal "could not write to '$tempfile'" 57; }
fi
done
IFS=$OLDIFS
local GIT_IGNORE_PATH
GIT_IGNORE_PATH="$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
if diff -N "$tempfile" "$GIT_IGNORE_PATH" > /dev/null; then
info "'$GIT_IGNORE_PATH' already up to date"
rm -f "$tempfile" || fatal "could not delete '$tempfile'" 59
return
fi
if [ -e "$GIT_IGNORE_PATH" ]; then
info "'$GIT_IGNORE_PATH' differs from new data, moving it to '$GIT_IGNORE_PATH.bak'"
mv -f "$GIT_IGNORE_PATH"{,.bak} ||
fatal "could not move '$GIT_IGNORE_PATH' to '$GIT_IGNORE_PATH.bak'" 53
fi
mv -f "$tempfile" "$GIT_IGNORE_PATH" ||
fatal "could not move '$tempfile' to '$GIT_IGNORE_PATH'" 53
}
debug "$(@GIT@ version)"
if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
fi
VCSH_COMMAND=$1; export VCSH_COMMAND
case $VCSH_COMMAND in
clon|clo|cl) VCSH_COMMAND=clone;;
commi|comm|com|co|ci) VCSH_COMMAND=commit;;
delet|dele|del|de) VCSH_COMMAND=delete;;
ente|ent|en) VCSH_COMMAND=enter;;
hel|he) VCSH_COMMAND=help;;
ini|in) VCSH_COMMAND=init;;
pul) VCSH_COMMAND=pull;;
pus) VCSH_COMMAND=push;;
renam|rena|ren|re) VCSH_COMMAND=rename;;
ru) VCSH_COMMAND=run;;
statu|stat|sta|st) VCSH_COMMAND=status;;
upgrad|upgra|upgr|up) VCSH_COMMAND=upgrade;;
versio|versi|vers|ver|ve) VCSH_COMMAND=version;;
which|whi|wh) VCSH_COMMAND=which;;
write|writ|wri|wr) VCSH_COMMAND=write-gitignore;;
esac
if [ x"$VCSH_COMMAND" = x'clone' ]; then
VCSH_BRANCH=
if [ "$2" = -b ]; then
VCSH_BRANCH=$3
shift
shift
fi
[ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a remote" 1
GIT_REMOTE="$2"
[ -n "$VCSH_BRANCH" ] || if [ "$3" = -b ]; then
VCSH_BRANCH=$4
shift
shift
fi
if [ -n "$3" ]; then
VCSH_REPO_NAME=$3
[ -z "$VCSH_BRANCH" ] && [ "$4" = -b ] && VCSH_BRANCH=$5
else
VCSH_REPO_NAME=$(basename "${GIT_REMOTE#*:}" .git)
fi
[ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: could not determine repository name" 1
export VCSH_REPO_NAME
[ -n "$VCSH_BRANCH" ] || VCSH_BRANCH=main
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
elif [ "$VCSH_COMMAND" = 'help' ]; then
help && exit
elif [ "$VCSH_COMMAND" = 'version' ]; then
echo "$VCSH_SELF $VCSH_VERSION"
@GIT@ version
exit
elif [ x"$VCSH_COMMAND" = x'which' ]; then
[ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a filename" 1
[ -n "$3" ] && fatal "$VCSH_COMMAND: too many parameters" 1
VCSH_COMMAND_PARAMETER=$2; export VCSH_COMMAND_PARAMETER
elif [ x"$VCSH_COMMAND" = x'delete' ] ||
[ x"$VCSH_COMMAND" = x'enter' ] ||
[ x"$VCSH_COMMAND" = x'init' ] ||
[ x"$VCSH_COMMAND" = x'list-tracked-by' ] ||
[ x"$VCSH_COMMAND" = x'rename' ] ||
[ x"$VCSH_COMMAND" = x'run' ] ||
[ x"$VCSH_COMMAND" = x'upgrade' ] ||
[ x"$VCSH_COMMAND" = x'write-gitignore' ]; then
if [ -z "$2" ]; then
[ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: please specify repository to work on" 1
else
VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
fi
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
[ x"$VCSH_COMMAND" = x'rename' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1
[ x"$VCSH_COMMAND" = x'run' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1
[ x"$VCSH_COMMAND" = x'rename' ] && { VCSH_REPO_NAME_NEW=$3; export VCSH_REPO_NAME_NEW;
GIT_DIR_NEW=$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git; export GIT_DIR_NEW; }
[ x"$VCSH_COMMAND" = x'run' ] && shift 2
elif [ x"$VCSH_COMMAND" = x'foreach' ]; then
[ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a command" 1
shift 1
elif [ x"$VCSH_COMMAND" = x'commit' ] ||
[ x"$VCSH_COMMAND" = x'list' ] ||
[ x"$VCSH_COMMAND" = x'list-tracked' ] ||
[ x"$VCSH_COMMAND" = x'list-untracked' ] ||
[ x"$VCSH_COMMAND" = x'pull' ] ||
[ x"$VCSH_COMMAND" = x'push' ]; then
:
elif [ x"$VCSH_COMMAND" = x'status' ]; then
if [ x"$2" = x'--terse' ]; then
VCSH_STATUS_TERSE=1; export VCSH_STATUS_TERSE
shift
fi
VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
elif [ -n "$2" ]; then
VCSH_COMMAND='run'; export VCSH_COMMAND
VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
[ -d "$GIT_DIR" ] || { help; exit 1; }
shift 1
set -- "@GIT@" "$@"
elif [ -n "$VCSH_COMMAND" ]; then
VCSH_COMMAND='enter'; export VCSH_COMMAND
VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
[ -d "$GIT_DIR" ] || { help; exit 1; }
else
# $1 is empty
help && exit 1
fi
# Did we receive a directory instead of a name?
# Mangle the input to fit normal operation.
if echo "$VCSH_REPO_NAME" | @GREP@ -q '/'; then
GIT_DIR=$VCSH_REPO_NAME; export GIT_DIR
VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git); export VCSH_REPO_NAME
fi
check_dir() {
check_directory="$1"
if [ ! -d "$check_directory" ]; then
if [ -e "$check_directory" ]; then
fatal "'$check_directory' exists but is not a directory" 13
else
verbose "attempting to create '$check_directory'"
mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
fi
fi
}
check_dir "$VCSH_REPO_D"
[ ! "x$VCSH_GITIGNORE" = 'xnone' ] && check_dir "$VCSH_BASE/.gitignore.d"
[ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && check_dir "$VCSH_BASE/.gitattributes.d"
verbose "$VCSH_COMMAND begin"
VCSH_COMMAND=$(echo "$VCSH_COMMAND" | @SED@ 's/-/_/g'); export VCSH_COMMAND
# Source repo-specific configuration file
# shellcheck source=/dev/null
[ -r "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME" ] \
&& . "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME"
# source overlay functions
for overlay in "$VCSH_OVERLAY_D/$VCSH_COMMAND"* "$VCSH_OVERLAY_D/$VCSH_REPO_NAME.$VCSH_COMMAND"*; do
[ -r "$overlay" ] || continue
info "sourcing '$overlay'"
# shellcheck source=/dev/null
. "$overlay"
done
hook pre-command
$VCSH_COMMAND "$@"
hook post-command
verbose "$VCSH_COMMAND end, exiting"
exit $VCSH_COMMAND_RETURN_CODE
# Local Variables:
# mode:shell-script
# sh-shell:sh
# End:
# vim: ft=sh: