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

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