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

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