]> git.madduck.net Git - code/vcsh.git/blob - vcsh

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

vcsh: Fix foreach()
[code/vcsh.git] / vcsh
1 #!/bin/sh
2
3 # This program is licensed under the GNU GPL version 2 or later.
4 # (c) Richard "RichiH" Hartmann <richih@debian.org>, 2011-2014
5 # For details, see LICENSE. To submit patches, you have to agree to
6 # license your code under the GNU GPL version 2 or later.
7
8 # While the following is not legally binding, the author would like to
9 # explain the choice of GPLv2+ over GPLv3+.
10 # The author prefers GPLv3+ over GPLv2+ but feels it's better to maintain
11 # full compatibility's with Git. In case Git ever changes its licensing terms,
12 # which is admittedly extremely unlikely to the point of being impossible,
13 # this software will most likely follow suit.
14
15 # This should always be the first line of code to facilitate debugging
16 [ -n "$VCSH_DEBUG" ] && set -vx
17
18
19 # If '.git-HEAD' is appended to the version, you are seeing an unreleased
20 # version of vcsh; the master branch is supposed to be clean at all times
21 # so you can most likely just use it nonetheless
22 VERSION='1.20141026'
23 SELF=$(basename $0)
24
25 fatal() {
26         echo "$SELF: fatal: $1" >&2
27         [ -z $2 ] && exit 1
28         exit $2
29 }
30
31 # We need to run getops as soon as possible so we catch -d and other
32 # options that will modify our behaviour.
33 # Commands are handled at the end of this script.
34 while getopts "c:dv" flag; do
35         if [ x"$1" = x'-d' ] || [ x"$1" = x'--debug' ]; then
36                 set -vx
37                 VCSH_DEBUG=1
38                 echo "debug mode on"
39                 echo "$SELF $VERSION"
40         elif [ x"$1" = x'-v' ]; then
41                 VCSH_VERBOSE=1
42                 echo "verbose mode on"
43                 echo "$SELF $VERSION"
44         elif [ x"$1" = x'-c' ]; then
45                 VCSH_OPTION_CONFIG=$OPTARG
46         fi
47         shift 1
48 done
49
50 source_all() {
51         # Source file even if it's in $PWD and does not have any slashes in it
52         case $1 in
53                 */*) . "$1";;
54                 *)   . "$PWD/$1";;
55         esac;
56 }
57
58
59 # Read configuration and set defaults if anything's not set
60 [ -n "$VCSH_DEBUG" ]                  && set -vx
61 : ${XDG_CONFIG_HOME:="$HOME/.config"}
62
63 # Read configuration files if there are any
64 [ -r "/etc/vcsh/config" ]             && . "/etc/vcsh/config"
65 [ -r "$XDG_CONFIG_HOME/vcsh/config" ] && . "$XDG_CONFIG_HOME/vcsh/config"
66 if [ -n "$VCSH_OPTION_CONFIG" ]; then
67         # Source $VCSH_OPTION_CONFIG if it can be read and is in $PWD of $PATH
68         if [ -r "$VCSH_OPTION_CONFIG" ]; then
69                 source_all "$VCSH_OPTION_CONFIG"
70         else
71                 fatal "Can not read configuration file '$VCSH_OPTION_CONFIG'" 1
72         fi
73 fi
74 [ -n "$VCSH_DEBUG" ]                  && set -vx
75
76 # Read defaults
77 : ${VCSH_REPO_D:="$XDG_CONFIG_HOME/vcsh/repo.d"}
78 : ${VCSH_HOOK_D:="$XDG_CONFIG_HOME/vcsh/hooks-enabled"}
79 : ${VCSH_OVERLAY_D:="$XDG_CONFIG_HOME/vcsh/overlays-enabled"}
80 : ${VCSH_BASE:="$HOME"}
81 : ${VCSH_GITIGNORE:=exact}
82 : ${VCSH_GITATTRIBUTES:=none}
83 : ${VCSH_WORKTREE:=absolute}
84
85 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
86         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
87 fi
88
89 if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ] && [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
90         fatal "'\$VCSH_WORKTREE' must equal 'absolute', or 'relative'" 1
91 fi
92
93
94 help() {
95         echo "usage: $SELF <options> <command>
96
97    options:
98    -c <file>            Source file
99    -d                   Enable debug mode
100    -v                   Enable verbose mode
101
102    commands:
103    clone [-b <branch>] \\
104          <remote> \\
105          [<repo>]       Clone from an existing repository
106    commit               Commit in all repositories
107    delete <repo>        Delete an existing repository
108    enter <repo>         Enter repository; spawn new instance of \$SHELL
109    help                 Display this help text
110    init <repo>          Initialize a new repository
111    list                 List all repositories
112    list-tracked \\
113         [<repo>]        List all files tracked all or one repositories
114    list-untracked \\
115         [<-r>] [<repo>] List all files not tracked by all or one repositories
116    pull                 Pull from all vcsh remotes
117    push                 Push to vcsh remotes
118    rename <repo> \\
119           <newname>     Rename repository
120    run <repo> \\
121        <command>        Use this repository
122    status [<repo>]      Show statuses of all/one vcsh repositories
123    upgrade <repo>       Upgrade repository to currently recommended settings
124    version              Print version information
125    which <substring>    Find substring in name of any tracked file
126    write-gitignore \\
127    <repo>               Write .gitignore.d/<repo> via git ls-files
128
129    <repo> <git command> Shortcut to run git commands directly
130    <repo>               Shortcut to enter repository" >&2
131 }
132
133 debug() {
134         [ -n "$VCSH_DEBUG" ] && echo "$SELF: debug: $@"
135 }
136
137 verbose() {
138         if [ -n "$VCSH_DEBUG" ] || [ -n "$VCSH_VERBOSE" ]; then echo "$SELF: verbose: $@"; fi
139 }
140
141 error() {
142         echo "$SELF: error: $1" >&2
143 }
144
145 info() {
146         echo "$SELF: info: $1"
147 }
148
149 clone() {
150         hook pre-clone
151         init
152         git remote add origin "$GIT_REMOTE"
153         git checkout -b "$VCSH_BRANCH" || return $?
154         git config branch."$VCSH_BRANCH".remote origin
155         git config branch."$VCSH_BRANCH".merge  refs/heads/"$VCSH_BRANCH"
156         if [ $(git ls-remote origin "$VCSH_BRANCH" 2> /dev/null | wc -l ) -lt 1 ]; then
157                 info "remote is empty, not merging anything.
158   You should add files to your new repository."
159                 exit
160         fi
161         git fetch origin "$VCSH_BRANCH"
162         hook pre-merge
163         git ls-tree -r --name-only origin/"$VCSH_BRANCH" | (while read object; do
164                 [ -e "$object" ] &&
165                         error "'$object' exists." &&
166                         VCSH_CONFLICT=1
167         done
168         [ x"$VCSH_CONFLICT" = x'1' ]) &&
169                 fatal "will stop after fetching and not try to merge!
170   Once this situation has been resolved, run 'vcsh $VCSH_REPO_NAME pull' to finish cloning." 17
171         git merge origin/"$VCSH_BRANCH"
172         hook post-merge
173         hook post-clone
174         retire
175         hook post-clone-retired
176 }
177
178 commit() {
179         hook pre-commit
180         for VCSH_REPO_NAME in $(list); do
181                 echo "$VCSH_REPO_NAME: "
182                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
183                 use
184                 git commit --untracked-files=no --quiet
185                 VCSH_COMMAND_RETURN_CODE=$?
186                 echo
187         done
188         hook post-commit
189 }
190
191 delete() {
192         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
193         use
194         info "This operation WILL DESTROY DATA!"
195         files=$(git ls-files)
196         echo "These files will be deleted:
197
198 $files
199
200 AGAIN, THIS WILL DELETE YOUR DATA!
201 To continue, type 'Yes, do as I say'"
202         read answer
203         [ "x$answer" = 'xYes, do as I say' ] || exit 16
204         for file in $files; do
205                 rm -f $file || info "could not delete '$file', continuing with deletion"
206         done
207         rm -rf "$GIT_DIR" || error "could not delete '$GIT_DIR'"
208 }
209
210 enter() {
211         hook pre-enter
212         use
213         $SHELL
214         hook post-enter
215 }
216
217 foreach() {
218         hook pre-foreach
219         for VCSH_REPO_NAME in $(list); do
220                 echo "$VCSH_REPO_NAME:"
221                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
222                 use
223                 git "$@"
224         done
225         hook post-foreach
226 }
227
228 git_dir_exists() {
229         [ -d "$GIT_DIR" ] || fatal "no repository found for '$VCSH_REPO_NAME'" 12
230 }
231
232 hook() {
233         for hook in "$VCSH_HOOK_D/$1"* "$VCSH_HOOK_D/$VCSH_REPO_NAME.$1"*; do
234                 [ -x "$hook" ] || continue
235                 verbose "executing '$hook'"
236                 "$hook"
237         done
238 }
239
240 init() {
241         hook pre-init
242         [ ! -e "$GIT_DIR" ] || fatal "'$GIT_DIR' exists" 10
243         mkdir -p "$VCSH_BASE" || fatal "could not create '$VCSH_BASE'" 50
244         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
245         git init --shared=0600
246         upgrade
247         hook post-init
248 }
249
250 list() {
251         for repo in "$VCSH_REPO_D"/*.git; do
252                 [ -d "$repo" ] && [ -r "$repo" ] && echo "$(basename "$repo" .git)"
253         done
254 }
255
256 get_files() {
257         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
258         git ls-files
259 }
260
261 list_tracked() {
262         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
263         if [ -n "$VCSH_REPO_NAME" ]; then
264                 get_files | list_tracked_helper
265         else
266                 for VCSH_REPO_NAME in $(list); do
267                         get_files
268                 done | list_tracked_helper
269         fi
270 }
271
272 list_tracked_helper() {
273         sed "s,^,$(printf '%s\n' "$VCSH_BASE/" | sed 's/[,\&]/\\&/g')," | sort -u
274 }
275
276 list_tracked_by() {
277         list_tracked $2
278 }
279
280 list_untracked() {
281         command -v 'comm' >/dev/null 2>&1 || fatal "Could not find 'comm'"
282
283         temp_file_others=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
284         temp_file_untracked=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
285         temp_file_untracked_copy=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
286
287         # Hack in support for `vcsh list-untracked -r`...
288         directory_opt="--directory"
289         shift 1
290         while getopts "r" flag; do
291                 if [ x"$1" = x'-r' ]; then
292                         unset directory_opt
293                 fi
294                 shift 1
295         done
296         # ...and parse for a potential parameter afterwards. As we shifted things out of $* in during getops, we need to look at $1
297         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
298
299         if [ -n "$VCSH_REPO_NAME" ]; then
300                 list_untracked_helper $VCSH_REPO_NAME
301         else
302                 for VCSH_REPO_NAME in $(list); do
303                         list_untracked_helper $VCSH_REPO_NAME
304                 done
305         fi
306         cat $temp_file_untracked
307
308         unset directory_opt directory_component
309         rm -f $temp_file_others $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not delete temp files'
310 }
311
312 list_untracked_helper() {
313         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
314         git ls-files --others "$directory_opt" | (
315                 while read line; do
316                         echo "$line"
317                         directory_component=${line%%/*}
318                         [ -d "$directory_component" ] && printf '%s/\n' "$directory_component"
319                 done
320                 ) | sort -u > $temp_file_others
321         if [ -z "$ran_once" ]; then
322                 ran_once=1
323                 cp $temp_file_others $temp_file_untracked || fatal 'Could not copy temp file'
324         fi
325         cp $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not copy temp file'
326         comm -12 --nocheck-order $temp_file_others $temp_file_untracked_copy > $temp_file_untracked
327 }
328
329 pull() {
330         hook pre-pull
331         for VCSH_REPO_NAME in $(list); do
332                 printf '%s: ' "$VCSH_REPO_NAME"
333                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
334                 use
335                 git pull
336                 VCSH_COMMAND_RETURN_CODE=$?
337                 echo
338         done
339         hook post-pull
340 }
341
342 push() {
343         hook pre-push
344         for VCSH_REPO_NAME in $(list); do
345                 printf '%s: ' "$VCSH_REPO_NAME"
346                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
347                 use
348                 git push
349                 VCSH_COMMAND_RETURN_CODE=$?
350                 echo
351         done
352         hook post-push
353 }
354
355 retire() {
356         unset VCSH_DIRECTORY
357 }
358
359 rename() {
360         git_dir_exists
361         [ -d "$GIT_DIR_NEW" ] && fatal "'$GIT_DIR_NEW' exists" 54
362         mv -f "$GIT_DIR" "$GIT_DIR_NEW" || fatal "Could not mv '$GIT_DIR' '$GIT_DIR_NEW'" 52
363
364         # Now that the repository has been renamed, we need to fix up its configuration
365         # Overwrite old name..
366         GIT_DIR=$GIT_DIR_NEW
367         VCSH_REPO_NAME=$VCSH_REPO_NAME_NEW
368         # ..and clobber all old configuration
369         upgrade
370 }
371
372 run() {
373         hook pre-run
374         use
375         "$@"
376         VCSH_COMMAND_RETURN_CODE=$?
377         hook post-run
378 }
379
380 status() {
381         if [ -n "$VCSH_REPO_NAME" ]; then
382                 status_helper $VCSH_REPO_NAME
383         else
384                 for VCSH_REPO_NAME in $(list); do
385                         echo "$VCSH_REPO_NAME:"
386                         status_helper $VCSH_REPO_NAME
387                         echo
388                 done
389         fi
390 }
391
392 status_helper() {
393         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
394         use
395         remote_tracking_branch=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2> /dev/null) && {
396                 commits_behind=$(git log ..${remote_tracking_branch} --oneline | wc -l)
397                 commits_ahead=$(git log ${remote_tracking_branch}.. --oneline | wc -l)
398                 [ ${commits_behind} -ne 0 ] && echo "Behind $remote_tracking_branch by $commits_behind commits"
399                 [ ${commits_ahead} -ne 0 ] && echo "Ahead of $remote_tracking_branch by $commits_ahead commits"
400         }
401         git status --short --untracked-files='no'
402         VCSH_COMMAND_RETURN_CODE=$?
403 }
404
405 upgrade() {
406         hook pre-upgrade
407         # fake-bare repositories are not bare, actually. Set this to false
408         # because otherwise Git complains "fatal: core.bare and core.worktree
409         # do not make sense"
410         git config core.bare false
411         # core.worktree may be absolute or relative to $GIT_DIR, depending on
412         # user preference
413         if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ]; then
414                 git config core.worktree "$(cd "$GIT_DIR" && GIT_WORK_TREE=$VCSH_BASE git rev-parse --show-cdup)"
415         elif [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
416                 git config core.worktree "$VCSH_BASE"
417         fi
418         [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
419         [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && git config core.attributesfile ".gitattributes.d/$VCSH_REPO_NAME"
420         git config vcsh.vcsh 'true'
421         use
422         [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
423         [ -e "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME"
424         hook post-upgrade
425 }
426
427 use() {
428         git_dir_exists
429         VCSH_DIRECTORY=$VCSH_REPO_NAME; export VCSH_DIRECTORY
430 }
431
432 which() {
433         [ -e "$VCSH_COMMAND_PARAMETER" ] || fatal "'$VCSH_COMMAND_PARAMETER' does not exist" 1
434         for VCSH_REPO_NAME in $(list); do
435                 for VCSH_FILE in $(get_files); do
436                         echo "$VCSH_FILE" | grep -q "$VCSH_COMMAND_PARAMETER" && echo "$VCSH_REPO_NAME: $VCSH_FILE"
437                 done
438         done | sort -u
439 }
440
441 write_gitignore() {
442         # Don't do anything if the user does not want to write gitignore
443         if [ "x$VCSH_GITIGNORE" = 'xnone' ]; then
444                 info "Not writing gitignore as '\$VCSH_GITIGNORE' is set to 'none'"
445                 exit
446         fi
447
448         use
449         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
450         OLDIFS=$IFS
451         IFS=$(printf '\n\t')
452         gitignores=$(for file in $(git ls-files); do
453                 while true; do
454                         echo "$file"; new=${file%/*}
455                         [ x"$file" = x"$new" ] && break
456                         file=$new
457                 done;
458         done | sort -u)
459
460         # Contrary to GNU mktemp, mktemp on BSD/OSX requires a template for temp files
461         # Using a template makes GNU mktemp default to $PWD and not #TMPDIR for tempfile location
462         # To make every OS happy, set full path explicitly
463         tempfile=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal "could not create tempfile: '${tempfile}'" 51
464
465         echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
466         for gitignore in $gitignores; do
467                 echo "$gitignore" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57
468                 if [ "x$VCSH_GITIGNORE" = 'xrecursive' ] && [ -d "$gitignore" ]; then
469                         { echo "$gitignore/*" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; }
470                 fi
471         done
472         IFS=$OLDIFS
473         if diff -N "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then
474                 rm -f "$tempfile" || error "could not delete '$tempfile'"
475                 exit
476         fi
477         if [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ]; then
478                 info "'$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'"
479                 mv -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak" ||
480                         fatal "could not move '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
481         fi
482         mv -f "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ||
483                 fatal "could not move '$tempfile' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME'" 53
484 }
485
486 debug $(git version)
487
488 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
489         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
490 fi
491
492 VCSH_COMMAND=$1; export VCSH_COMMAND
493
494 case $VCSH_COMMAND in
495         clon|clo|cl) VCSH_COMMAND=clone;;
496         commi|comm|com|co) VCSH_COMMAND=commit;;
497         delet|dele|del|de) VCSH_COMMAND=delete;;
498         ente|ent|en) VCSH_COMMAND=enter;;
499         hel|he) VCSH_COMMAND=help;;
500         ini|in) VCSH_COMMAND=init;;
501         pul) VCSH_COMMAND=pull;;
502         pus) VCSH_COMMAND=push;;
503         renam|rena|ren|re) VCSH_COMMAND=rename;;
504         ru) VCSH_COMMAND=run;;
505         statu|stat|sta|st) VCSH_COMMAND=status;;
506         upgrad|upgra|upgr|up) VCSH_COMMAND=upgrade;;
507         versio|versi|vers|ver|ve) VCSH_COMMAND=version;;
508         which|whi|wh) VCSH_COMMAND=which;;
509         write|writ|wri|wr) VCSH_COMMAND=write-gitignore;;
510 esac    
511
512 if [ x"$VCSH_COMMAND" = x'clone' ]; then
513         VCSH_BRANCH=
514         if [ "$2" = -b ]; then
515                 VCSH_BRANCH=$3
516                 shift
517                 shift
518         fi
519         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a remote" 1
520         GIT_REMOTE="$2"
521         [ -n "$VCSH_BRANCH" ] || if [ "$3" = -b ]; then
522                 VCSH_BRANCH=$4
523                 shift
524                 shift
525         fi
526         if [ -n "$3" ]; then
527                 VCSH_REPO_NAME=$3
528                 [ -z "$VCSH_BRANCH" ] && [ "$4" = -b ] && VCSH_BRANCH=$5
529         else
530                 VCSH_REPO_NAME=$(basename "${GIT_REMOTE#*:}" .git)
531         fi
532         [ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: could not determine repository name" 1
533         export VCSH_REPO_NAME
534         [ -n "$VCSH_BRANCH" ] || VCSH_BRANCH=master
535         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
536 elif [ "$VCSH_COMMAND" = 'version' ]; then
537         echo "$SELF $VERSION"
538         git version
539         exit
540 elif [ x"$VCSH_COMMAND" = x'which' ]; then
541         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a filename" 1
542         [ -n "$3" ] && fatal "$VCSH_COMMAND: too many parameters" 1
543         VCSH_COMMAND_PARAMETER=$2; export VCSH_COMMAND_PARAMETER
544 elif [ x"$VCSH_COMMAND" = x'delete' ]           ||
545      [ x"$VCSH_COMMAND" = x'enter' ]            ||
546      [ x"$VCSH_COMMAND" = x'init' ]             ||
547      [ x"$VCSH_COMMAND" = x'list-tracked-by' ]  ||
548      [ x"$VCSH_COMMAND" = x'rename' ]           ||
549      [ x"$VCSH_COMMAND" = x'run' ]              ||
550      [ x"$VCSH_COMMAND" = x'upgrade' ]          ||
551      [ x"$VCSH_COMMAND" = x'write-gitignore' ]; then
552         [ -z "$2" ]                                     && fatal "$VCSH_COMMAND: please specify repository to work on" 1
553         [ x"$VCSH_COMMAND" = x'rename' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1
554         [ x"$VCSH_COMMAND" = x'run'    ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1
555         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
556         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
557         [ x"$VCSH_COMMAND" = x'rename' ] && { VCSH_REPO_NAME_NEW=$3; export VCSH_REPO_NAME_NEW;
558                                               GIT_DIR_NEW=$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git; export GIT_DIR_NEW; }
559         [ x"$VCSH_COMMAND" = x'run' ]    && shift 2
560 elif [ x"$VCSH_COMMAND" = x'foreach' ]; then
561         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a command" 1
562         shift 1
563 elif [ x"$VCSH_COMMAND" = x'commit' ] ||
564      [ x"$VCSH_COMMAND" = x'list'   ] ||
565      [ x"$VCSH_COMMAND" = x'list-tracked' ] ||
566      [ x"$VCSH_COMMAND" = x'list-untracked' ] ||
567      [ x"$VCSH_COMMAND" = x'pull'   ] ||
568      [ x"$VCSH_COMMAND" = x'push'   ]; then
569         :
570 elif [ x"$VCSH_COMMAND" = x'status' ]; then
571         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
572 elif [ -n "$2" ]; then
573         VCSH_COMMAND='run'; export VCSH_COMMAND
574         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
575         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
576         [ -d "$GIT_DIR" ] || { help; exit 1; }
577         shift 1
578         set -- "git" "$@"
579 elif [ -n "$VCSH_COMMAND" ]; then
580         VCSH_COMMAND='enter'; export VCSH_COMMAND
581         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
582         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
583         [ -d "$GIT_DIR" ] || { help; exit 1; }
584 else
585         # $1 is empty, or 'help'
586         help && exit
587 fi
588
589 # Did we receive a directory instead of a name?
590 # Mangle the input to fit normal operation.
591 if echo "$VCSH_REPO_NAME" | grep -q '/'; then
592         GIT_DIR=$VCSH_REPO_NAME; export GIT_DIR
593         VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git); export VCSH_REPO_NAME
594 fi
595
596 check_dir() {
597         check_directory="$1"
598         if [ ! -d "$check_directory" ]; then
599                 if [ -e "$check_directory" ]; then
600                         fatal "'$check_directory' exists but is not a directory" 13
601                 else
602                         verbose "attempting to create '$check_directory'"
603                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
604                 fi
605         fi
606 }
607
608 check_dir "$VCSH_REPO_D"
609 [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && check_dir "$VCSH_BASE/.gitignore.d"
610 [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && check_dir "$VCSH_BASE/.gitattributes.d"
611
612 verbose "$VCSH_COMMAND begin"
613 VCSH_COMMAND=$(echo "$VCSH_COMMAND" | sed 's/-/_/g'); export VCSH_COMMAND
614
615 # Source repo-specific configuration file
616 [ -r "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME" ] && . "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME"
617
618 # source overlay functions
619 for overlay in "$VCSH_OVERLAY_D/$VCSH_COMMAND"* "$VCSH_OVERLAY_D/$VCSH_REPO_NAME.$VCSH_COMMAND"*; do
620         [ -r "$overlay" ] || continue
621         info "sourcing '$overlay'"
622         . "$overlay"
623 done
624
625 hook pre-command
626 $VCSH_COMMAND "$@"
627 hook post-command
628 verbose "$VCSH_COMMAND end, exiting"
629 exit $VCSH_COMMAND_RETURN_CODE