]> 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:

58ecb7f3ea7415fa4f94c0351985fe11af016161
[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.20141009'
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 <remote> \\
104          [<repo>]       Clone from an existing repository
105    commit               Commit in all repositories
106    delete <repo>        Delete an existing repository
107    enter <repo>         Enter repository; spawn new instance of \$SHELL
108    help                 Display this help text
109    init <repo>          Initialize a new repository
110    list                 List all repositories
111    list-tracked \\
112         [<repo>]        List all files tracked all or one repositories
113    list-untracked \\
114         [<-r>] [<repo>] List all files not tracked by all or one repositories
115    pull                 Pull from all vcsh remotes
116    push                 Push to vcsh remotes
117    rename <repo> \\
118           <newname>     Rename repository
119    run <repo> \\
120        <command>        Use this repository
121    status [<repo>]      Show statuses of all/one vcsh repositories
122    upgrade <repo>       Upgrade repository to currently recommended settings
123    version              Print version information
124    which <substring>    Find substring in name of any tracked file
125    write-gitignore \\
126    <repo>               Write .gitignore.d/<repo> via git ls-files
127
128    <repo> <git command> Shortcut to run git commands directly
129    <repo>               Shortcut to enter repository" >&2
130 }
131
132 debug() {
133         [ -n "$VCSH_DEBUG" ] && echo "$SELF: debug: $@"
134 }
135
136 verbose() {
137         if [ -n "$VCSH_DEBUG" ] || [ -n "$VCSH_VERBOSE" ]; then echo "$SELF: verbose: $@"; fi
138 }
139
140 error() {
141         echo "$SELF: error: $1" >&2
142 }
143
144 info() {
145         echo "$SELF: info: $1"
146 }
147
148 clone() {
149         hook pre-clone
150         init
151         git remote add origin "$GIT_REMOTE"
152         git config branch.master.remote origin
153         git config branch.master.merge  refs/heads/master
154         VCSH_CLONE_ERROR=$(git ls-remote origin master 2>&1)
155         if [ -n "$VCSH_CLONE_ERROR" ]; then
156                 rm -rf "$GIT_DIR"
157                 fatal "$VCSH_CLONE_ERROR" 1
158         fi
159         git fetch
160         hook pre-merge
161         git ls-tree -r --name-only origin/master | (while read object; do
162                 [ -e "$object" ] &&
163                         error "'$object' exists." &&
164                         VCSH_CONFLICT=1
165         done
166         [ x"$VCSH_CONFLICT" = x'1' ]) &&
167                 fatal "will stop after fetching and not try to merge!
168   Once this situation has been resolved, run 'vcsh $VCSH_REPO_NAME pull' to finish cloning." 17
169         git merge origin/master
170         hook post-merge
171         hook post-clone
172         retire
173         hook post-clone-retired
174 }
175
176 commit() {
177         hook pre-commit
178         for VCSH_REPO_NAME in $(list); do
179                 echo "$VCSH_REPO_NAME: "
180                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
181                 use
182                 git commit --untracked-files=no --quiet
183                 VCSH_COMMAND_RETURN_CODE=$?
184                 echo
185         done
186         hook post-commit
187 }
188
189 delete() {
190         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
191         use
192         info "This operation WILL DESTROY DATA!"
193         files=$(git ls-files)
194         echo "These files will be deleted:
195
196 $files
197
198 AGAIN, THIS WILL DELETE YOUR DATA!
199 To continue, type 'Yes, do as I say'"
200         read answer
201         [ "x$answer" = 'xYes, do as I say' ] || exit 16
202         for file in $files; do
203                 rm -f $file || info "could not delete '$file', continuing with deletion"
204         done
205         rm -rf "$GIT_DIR" || error "could not delete '$GIT_DIR'"
206 }
207
208 enter() {
209         hook pre-enter
210         use
211         $SHELL
212         hook post-enter
213 }
214
215 git_dir_exists() {
216         [ -d "$GIT_DIR" ] || fatal "no repository found for '$VCSH_REPO_NAME'" 12
217 }
218
219 hook() {
220         for hook in "$VCSH_HOOK_D/$1"* "$VCSH_HOOK_D/$VCSH_REPO_NAME.$1"*; do
221                 [ -x "$hook" ] || continue
222                 verbose "executing '$hook'"
223                 "$hook"
224         done
225 }
226
227 init() {
228         hook pre-init
229         [ ! -e "$GIT_DIR" ] || fatal "'$GIT_DIR' exists" 10
230         mkdir -p "$VCSH_BASE" || fatal "could not create '$VCSH_BASE'" 50
231         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
232         git init --shared=0600
233         upgrade
234         hook post-init
235 }
236
237 list() {
238         for repo in "$VCSH_REPO_D"/*.git; do
239                 [ -d "$repo" ] && [ -r "$repo" ] && echo "$(basename "$repo" .git)"
240         done
241 }
242
243 get_files() {
244         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
245         git ls-files
246 }
247
248 list_tracked() {
249         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
250         if [ -n "$VCSH_REPO_NAME" ]; then
251                 get_files | list_tracked_helper
252         else
253                 for VCSH_REPO_NAME in $(list); do
254                         get_files
255                 done | list_tracked_helper
256         fi
257 }
258
259 list_tracked_helper() {
260         sed "s,^,$(printf '%s\n' "$VCSH_BASE/" | sed 's/[,\&]/\\&/g')," | sort -u
261 }
262
263 list_tracked_by() {
264         list_tracked $2
265 }
266
267 list_untracked() {
268         command -v 'comm' >/dev/null 2>&1 || fatal "Could not find 'comm'"
269
270         temp_file_others=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
271         temp_file_untracked=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
272         temp_file_untracked_copy=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
273
274         # Hack in support for `vcsh list-untracked -r`...
275         directory_opt="--directory"
276         shift 1
277         while getopts "r" flag; do
278                 if [ x"$1" = x'-r' ]; then
279                         unset directory_opt
280                 fi
281                 shift 1
282         done
283         # ...and parse for a potential parameter afterwards. As we shifted things out of $* in during getops, we need to look at $1
284         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
285
286         if [ -n "$VCSH_REPO_NAME" ]; then
287                 list_untracked_helper $VCSH_REPO_NAME
288         else
289                 for VCSH_REPO_NAME in $(list); do
290                         list_untracked_helper $VCSH_REPO_NAME
291                 done
292         fi
293         cat $temp_file_untracked
294
295         unset directory_opt directory_component
296         rm -f $temp_file_others $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not delete temp files'
297 }
298
299 list_untracked_helper() {
300         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
301         git ls-files --others "$directory_opt" | (
302                 while read line; do
303                         echo "$line"
304                         directory_component=${line%%/*}
305                         [ -d "$directory_component" ] && printf '%s/\n' "$directory_component"
306                 done
307                 ) | sort -u > $temp_file_others
308         if [ -z "$ran_once" ]; then
309                 ran_once=1
310                 cp $temp_file_others $temp_file_untracked || fatal 'Could not copy temp file'
311         fi
312         cp $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not copy temp file'
313         comm -12 --nocheck-order $temp_file_others $temp_file_untracked_copy > $temp_file_untracked
314 }
315
316 pull() {
317         hook pre-pull
318         for VCSH_REPO_NAME in $(list); do
319                 printf '%s: ' "$VCSH_REPO_NAME"
320                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
321                 use
322                 git pull
323                 VCSH_COMMAND_RETURN_CODE=$?
324                 echo
325         done
326         hook post-pull
327 }
328
329 push() {
330         hook pre-push
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 push
336                 VCSH_COMMAND_RETURN_CODE=$?
337                 echo
338         done
339         hook post-push
340 }
341
342 retire() {
343         unset VCSH_DIRECTORY
344 }
345
346 rename() {
347         git_dir_exists
348         [ -d "$GIT_DIR_NEW" ] && fatal "'$GIT_DIR_NEW' exists" 54
349         mv -f "$GIT_DIR" "$GIT_DIR_NEW" || fatal "Could not mv '$GIT_DIR' '$GIT_DIR_NEW'" 52
350
351         # Now that the repository has been renamed, we need to fix up its configuration
352         # Overwrite old name..
353         GIT_DIR=$GIT_DIR_NEW
354         VCSH_REPO_NAME=$VCSH_REPO_NAME_NEW
355         # ..and clobber all old configuration
356         upgrade
357 }
358
359 run() {
360         hook pre-run
361         use
362         "$@"
363         VCSH_COMMAND_RETURN_CODE=$?
364         hook post-run
365 }
366
367 status() {
368         if [ -n "$VCSH_REPO_NAME" ]; then
369                 status_helper $VCSH_REPO_NAME
370         else
371                 for VCSH_REPO_NAME in $(list); do
372                         echo "$VCSH_REPO_NAME:"
373                         status_helper $VCSH_REPO_NAME
374                         echo
375                 done
376         fi
377 }
378
379 status_helper() {
380         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
381         use
382         git status --short --untracked-files='no'
383         VCSH_COMMAND_RETURN_CODE=$?
384 }
385
386 upgrade() {
387         hook pre-upgrade
388         # fake-bare repositories are not bare, actually. Set this to false
389         # because otherwise Git complains "fatal: core.bare and core.worktree
390         # do not make sense"
391         git config core.bare false
392         # core.worktree may be absolute or relative to $GIT_DIR, depending on
393         # user preference
394         if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ]; then
395                 git config core.worktree "$(cd "$GIT_DIR" && GIT_WORK_TREE=$VCSH_BASE git rev-parse --show-cdup)"
396         elif [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
397                 git config core.worktree "$VCSH_BASE"
398         fi
399         [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
400         [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && git config core.attributesfile ".gitattributes.d/$VCSH_REPO_NAME"
401         git config vcsh.vcsh 'true'
402         use
403         [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
404         [ -e "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME"
405         hook post-upgrade
406 }
407
408 use() {
409         git_dir_exists
410         VCSH_DIRECTORY=$VCSH_REPO_NAME; export VCSH_DIRECTORY
411 }
412
413 which() {
414         [ -e "$VCSH_COMMAND_PARAMETER" ] || fatal "'$VCSH_COMMAND_PARAMETER' does not exist" 1
415         for VCSH_REPO_NAME in $(list); do
416                 for VCSH_FILE in $(get_files); do
417                         echo "$VCSH_FILE" | grep -q "$VCSH_COMMAND_PARAMETER" && echo "$VCSH_REPO_NAME: $VCSH_FILE"
418                 done
419         done | sort -u
420 }
421
422 write_gitignore() {
423         # Don't do anything if the user does not want to write gitignore
424         if [ "x$VCSH_GITIGNORE" = 'xnone' ]; then
425                 info "Not writing gitignore as '\$VCSH_GITIGNORE' is set to 'none'"
426                 exit
427         fi
428
429         use
430         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
431         OLDIFS=$IFS
432         IFS=$(printf '\n\t')
433         gitignores=$(for file in $(git ls-files); do
434                 while true; do
435                         echo "$file"; new=${file%/*}
436                         [ x"$file" = x"$new" ] && break
437                         file=$new
438                 done;
439         done | sort -u)
440
441         # Contrary to GNU mktemp, mktemp on BSD/OSX requires a template for temp files
442         # Using a template makes GNU mktemp default to $PWD and not #TMPDIR for tempfile location
443         # To make every OS happy, set full path explicitly
444         tempfile=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal "could not create tempfile: '${tempfile}'" 51
445
446         echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
447         for gitignore in $gitignores; do
448                 echo "$gitignore" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57
449                 if [ "x$VCSH_GITIGNORE" = 'xrecursive' ] && [ -d "$gitignore" ]; then
450                         { echo "$gitignore/*" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; }
451                 fi
452         done
453         IFS=$OLDIFS
454         if diff -N "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then
455                 rm -f "$tempfile" || error "could not delete '$tempfile'"
456                 exit
457         fi
458         if [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ]; then
459                 info "'$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'"
460                 mv -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak" ||
461                         fatal "could not move '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
462         fi
463         mv -f "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ||
464                 fatal "could not move '$tempfile' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME'" 53
465 }
466
467 debug $(git version)
468
469 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
470         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
471 fi
472
473 VCSH_COMMAND=$1; export VCSH_COMMAND
474
475 case $VCSH_COMMAND in
476         clon|clo|cl) VCSH_COMMAND=clone;;
477         commi|comm|com|co) VCSH_COMMAND=commit;;
478         delet|dele|del|de) VCSH_COMMAND=delete;;
479         ente|ent|en) VCSH_COMMAND=enter;;
480         hel|he) VCSH_COMMAND=help;;
481         ini|in) VCSH_COMMAND=init;;
482         pul) VCSH_COMMAND=pull;;
483         pus) VCSH_COMMAND=push;;
484         renam|rena|ren|re) VCSH_COMMAND=rename;;
485         ru) VCSH_COMMAND=run;;
486         statu|stat|sta|st) VCSH_COMMAND=status;;
487         upgrad|upgra|upgr|up) VCSH_COMMAND=upgrade;;
488         versio|versi|vers|ver|ve) VCSH_COMMAND=version;;
489         which|whi|wh) VCSH_COMMAND=which;;
490         write|writ|wri|wr) VCSH_COMMAND=write-gitignore;;
491 esac    
492
493 if [ x"$VCSH_COMMAND" = x'clone' ]; then
494         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a remote" 1
495         GIT_REMOTE="$2"
496         [ -n "$3" ] && VCSH_REPO_NAME=$3 || VCSH_REPO_NAME=$(basename "${GIT_REMOTE#*:}" .git)
497         [ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: could not determine repository name" 1
498         export VCSH_REPO_NAME
499         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
500 elif [ "$VCSH_COMMAND" = 'version' ]; then
501         echo "$SELF $VERSION"
502         git version
503         exit
504 elif [ x"$VCSH_COMMAND" = x'which' ]; then
505         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a filename" 1
506         [ -n "$3" ] && fatal "$VCSH_COMMAND: too many parameters" 1
507         VCSH_COMMAND_PARAMETER=$2; export VCSH_COMMAND_PARAMETER
508 elif [ x"$VCSH_COMMAND" = x'delete' ]           ||
509      [ x"$VCSH_COMMAND" = x'enter' ]            ||
510      [ x"$VCSH_COMMAND" = x'init' ]             ||
511      [ x"$VCSH_COMMAND" = x'list-tracked-by' ]  ||
512      [ x"$VCSH_COMMAND" = x'rename' ]           ||
513      [ x"$VCSH_COMMAND" = x'run' ]              ||
514      [ x"$VCSH_COMMAND" = x'upgrade' ]          ||
515      [ x"$VCSH_COMMAND" = x'write-gitignore' ]; then
516         [ -z "$2" ]                                     && fatal "$VCSH_COMMAND: please specify repository to work on" 1
517         [ x"$VCSH_COMMAND" = x'rename' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1
518         [ x"$VCSH_COMMAND" = x'run'    ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1
519         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
520         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
521         [ x"$VCSH_COMMAND" = x'rename' ] && { VCSH_REPO_NAME_NEW=$3; export VCSH_REPO_NAME_NEW;
522                                               GIT_DIR_NEW=$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git; export GIT_DIR_NEW; }
523         [ x"$VCSH_COMMAND" = x'run' ]    && shift 2
524 elif [ x"$VCSH_COMMAND" = x'commit' ] ||
525      [ x"$VCSH_COMMAND" = x'list'   ] ||
526      [ x"$VCSH_COMMAND" = x'list-tracked' ] ||
527      [ x"$VCSH_COMMAND" = x'list-untracked' ] ||
528      [ x"$VCSH_COMMAND" = x'pull'   ] ||
529      [ x"$VCSH_COMMAND" = x'push'   ]; then
530         :
531 elif [ x"$VCSH_COMMAND" = x'status' ]; then
532         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
533 elif [ -n "$2" ]; then
534         VCSH_COMMAND='run'; export VCSH_COMMAND
535         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
536         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
537         [ -d "$GIT_DIR" ] || { help; exit 1; }
538         shift 1
539         set -- "git" "$@"
540 elif [ -n "$VCSH_COMMAND" ]; then
541         VCSH_COMMAND='enter'; export VCSH_COMMAND
542         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
543         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
544         [ -d "$GIT_DIR" ] || { help; exit 1; }
545 else
546         # $1 is empty, or 'help'
547         help && exit
548 fi
549
550 # Did we receive a directory instead of a name?
551 # Mangle the input to fit normal operation.
552 if echo "$VCSH_REPO_NAME" | grep -q '/'; then
553         GIT_DIR=$VCSH_REPO_NAME; export GIT_DIR
554         VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git); export VCSH_REPO_NAME
555 fi
556
557 check_dir() {
558         check_directory="$1"
559         if [ ! -d "$check_directory" ]; then
560                 if [ -e "$check_directory" ]; then
561                         fatal "'$check_directory' exists but is not a directory" 13
562                 else
563                         verbose "attempting to create '$check_directory'"
564                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
565                 fi
566         fi
567 }
568
569 check_dir "$VCSH_REPO_D"
570 [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && check_dir "$VCSH_BASE/.gitignore.d"
571 [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && check_dir "$VCSH_BASE/.gitattributes.d"
572
573 verbose "$VCSH_COMMAND begin"
574 VCSH_COMMAND=$(echo "$VCSH_COMMAND" | sed 's/-/_/g'); export VCSH_COMMAND
575
576 # source overlay functions
577 for overlay in "$VCSH_OVERLAY_D/$VCSH_COMMAND"* "$VCSH_OVERLAY_D/$VCSH_REPO_NAME.$VCSH_COMMAND"*; do
578         [ -r "$overlay" ] || continue
579         info "sourcing '$overlay'"
580         . "$overlay"
581 done
582
583 hook pre-command
584 $VCSH_COMMAND "$@"
585 hook post-command
586 verbose "$VCSH_COMMAND end, exiting"
587 exit $VCSH_COMMAND_RETURN_CODE