]> 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 remote-tracking branch 'archwyrm/clone-trailing-slash-fix'
[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-2013
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 basename() {
19         # Implemented in shell to avoid spawning another process
20         local file
21         file="${1%/}"
22         file="${file##*/}"
23         [ -z "$2" ] || file="${file%$2}"
24         echo "$file"
25 }
26
27 SELF=$(basename $0)
28 VERSION='1.20130909.git-HEAD'
29
30 fatal() {
31         echo "$SELF: fatal: $1" >&2
32         exit $2
33 }
34
35 # We need to run getops as soon as possible so we catch -d and other
36 # options that will modify our behaviour.
37 # Commands are handled at the end of this script.
38 while getopts "c:dv" flag; do
39         if [ "$1" = '-d' ] || [ "$1" = '--debug' ]; then
40                 set -vx
41                 VCSH_DEBUG=1
42                 echo "debug mode on"
43                 echo "$SELF $VERSION"
44         elif [ "$1" = '-v' ];then
45                 VCSH_VERBOSE=1
46                 echo "verbose mode on"
47                 echo "$SELF $VERSION"
48         elif [ "$1" = '-c' ];then
49                 VCSH_OPTION_CONFIG=$OPTARG
50         fi
51         shift 1
52 done
53
54 source_all() {
55         # Source file even if it's in $PWD and does not have any slashes in it
56         case "$1" in
57                 */*) . "$1";;
58                 *)   . "$PWD/$1";;
59         esac;
60 }
61
62
63 # Read configuration and set defaults if anything's not set
64 [ -n "$VCSH_DEBUG" ]                  && set -vx
65 [ -z "$XDG_CONFIG_HOME" ]             && XDG_CONFIG_HOME="$HOME/.config"
66
67 # Read configuration files if there are any
68 [ -r "/etc/vcsh/config" ]             && . "/etc/vcsh/config"
69 [ -r "$XDG_CONFIG_HOME/vcsh/config" ] && . "$XDG_CONFIG_HOME/vcsh/config"
70 if [ -n "$VCSH_OPTION_CONFIG" ]; then
71         # Source $VCSH_OPTION_CONFIG if it can be read and is in $PWD of $PATH
72         if [ -r "$VCSH_OPTION_CONFIG" ]; then
73                 source_all "$VCSH_OPTION_CONFIG"
74         else
75                 fatal "Can not read configuration file '$VCSH_OPTION_CONFIG'" 1
76         fi
77 fi
78 [ -n "$VCSH_DEBUG" ]                  && set -vx
79
80 # Read defaults
81 [ -z "$VCSH_REPO_D" ]                 && VCSH_REPO_D="$XDG_CONFIG_HOME/vcsh/repo.d"
82 [ -z "$VCSH_HOOK_D" ]                 && VCSH_HOOK_D="$XDG_CONFIG_HOME/vcsh/hooks-enabled"
83 [ -z "$VCSH_BASE" ]                   && VCSH_BASE="$HOME"
84 [ -z "$VCSH_GITIGNORE" ]              && VCSH_GITIGNORE='exact'
85 [ -z "$VCSH_WORKTREE" ]               && VCSH_WORKTREE='absolute'
86
87 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
88         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
89 fi
90
91 if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ] && [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
92         fatal "'\$VCSH_WORKTREE' must equal 'absolute', or 'relative'" 1
93 fi
94
95
96 help() {
97         echo "usage: $SELF <options> <command>
98
99    options:
100    -c <file>            Source file
101    -d                   Enable debug mode
102    -v                   Enable verbose mode
103
104    commands:
105    clone <remote> \\
106          [<repo>]       Clone from an existing repository
107    commit               Commit in all repositories
108    delete <repo>        Delete an existing repository
109    enter <repo>         Enter repository; spawn new instance of \$SHELL
110    help                 Display this help text
111    init <repo>          Initialize a new repository
112    list                 List all repositories
113    list-tracked         List all files tracked by vcsh
114    list-tracked-by \\
115         <repo>          List files tracked by a repository
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         if [ $(git ls-remote origin master 2> /dev/null | wc -l ) -lt 1 ]; then
156                 info "remote is empty, not merging anything"
157                 exit
158         fi
159         git fetch
160         for object in $(git ls-tree -r origin/master | awk '{print $4}'); do
161                 [ -e "$object" ] &&
162                         error "'$object' exists." &&
163                         VCSH_CONFLICT=1
164         done
165         [ "$VCSH_CONFLICT" = '1' ] &&
166                 fatal "will stop after fetching and not try to merge!
167   Once this situation has been resolved, run 'vcsh run $VCSH_REPO_NAME git pull' to finish cloning." 17
168         git merge origin/master
169         hook post-clone
170         retire
171         hook post-clone-retired
172 }
173
174 commit() {
175         hook pre-commit
176         for VCSH_REPO_NAME in $(list); do
177                 echo "$VCSH_REPO_NAME: "
178                 export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
179                 use
180                 git commit --untracked-files=no --quiet
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
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         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
242         git ls-files
243 }
244
245 list_tracked() {
246         for VCSH_REPO_NAME in $(list); do
247                 get_files
248         done | sort -u
249 }
250
251 list_tracked_by() {
252         use
253         git ls-files | sort -u
254 }
255
256 pull() {
257         hook pre-pull
258         for VCSH_REPO_NAME in $(list); do
259                 echo -n "$VCSH_REPO_NAME: "
260                 export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
261                 use
262                 git pull
263                 echo
264         done
265         hook post-pull
266 }
267
268 push() {
269         hook pre-push
270         for VCSH_REPO_NAME in $(list); do
271                 echo -n "$VCSH_REPO_NAME: "
272                 export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
273                 use
274                 git push
275                 echo
276         done
277         hook post-push
278 }
279
280 retire() {
281         unset VCSH_DIRECTORY
282 }
283
284 rename() {
285         git_dir_exists
286         [ -d "$GIT_DIR_NEW" ] && fatal "'$GIT_DIR_NEW' exists" 54
287         mv -f "$GIT_DIR" "$GIT_DIR_NEW" || fatal "Could not mv '$GIT_DIR' '$GIT_DIR_NEW'" 52
288
289         # Now that the repository has been renamed, we need to fix up its configuration
290         # Overwrite old name..
291         GIT_DIR="$GIT_DIR_NEW"
292         VCSH_REPO_NAME="$VCSH_REPO_NAME_NEW"
293         # ..and clobber all old configuration
294         upgrade
295 }
296
297 run() {
298         hook pre-run
299         use
300         "$@"
301         hook post-run
302 }
303
304 status() {
305         if [ ! "x$VCSH_REPO_NAME" = "x" ]; then
306                 export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
307                 use
308                 git status --short --untracked-files='no'
309         else
310                 for VCSH_REPO_NAME in $(list); do
311                         echo "$VCSH_REPO_NAME:"
312                         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
313                         use
314                         git status --short --untracked-files='no'
315                         echo
316                 done
317         fi
318 }
319
320 upgrade() {
321         hook pre-upgrade
322         # fake-bare repositories are not bare, actually. Set this to false
323         # because otherwise Git complains "fatal: core.bare and core.worktree
324         # do not make sense"
325         git config core.bare false
326         # core.worktree may be absolute or relative to $GIT_DIR, depending on
327         # user preference
328         if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ]; then
329                 git config core.worktree $(cd $GIT_DIR && GIT_WORK_TREE="$VCSH_BASE" git rev-parse --show-cdup)
330         elif [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
331                 git config core.worktree "$VCSH_BASE"
332         fi
333         [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
334         git config vcsh.vcsh         'true'
335         use
336         [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
337         hook post-upgrade
338 }
339
340 use() {
341         git_dir_exists
342         export VCSH_DIRECTORY="$VCSH_REPO_NAME"
343 }
344
345 which() {
346         for VCSH_REPO_NAME in $(list); do
347                 for VCSH_FILE in $(get_files); do
348                         echo $VCSH_FILE | grep -q "$VCSH_COMMAND_PARAMETER" && echo "$VCSH_REPO_NAME: $VCSH_FILE"
349                 done
350         done | sort -u
351 }
352
353 write_gitignore() {
354         # Don't do anything if the user does not want to write gitignore
355         if [ "x$VCSH_GITIGNORE" = 'xnone' ]; then
356                 info "Not writing gitignore as '\$VCSH_GITIGNORE' is set to 'none'"
357                 exit
358         fi
359
360         use
361         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
362         gitignores=$(for file in $(git ls-files); do
363                 while true; do
364                         echo $file; new="${file%/*}"
365                         [ "$file" = "$new" ] && break
366                         file="$new"
367                 done;
368         done | sort -u)
369
370         # Contrary to GNU mktemp, mktemp on BSD/OSX requires a template for temp files
371         # Use the template GNU mktemo defaults to
372         tempfile=$(mktemp tmp.XXXXXXXXXX) || fatal "could not create tempfile" 51
373
374         echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
375         for gitignore in $gitignores; do
376                 echo "$gitignore" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57
377                 if [ "x$VCSH_GITIGNORE" = 'xrecursive' ] && [ -d "$gitignore" ]; then
378                         { echo "$gitignore/*" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; }
379                 fi
380         done
381         if diff -N "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then
382                 rm -f "$tempfile" || error "could not delete '$tempfile'"
383                 exit
384         fi
385         if [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ]; then
386                 info "'$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'"
387                 mv -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak" ||
388                         fatal "could not move '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
389         fi
390         mv -f "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ||
391                 fatal "could not move '$tempfile' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME'" 53
392 }
393
394 debug `git version`
395
396 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
397         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
398 fi
399
400 export VCSH_COMMAND="$1"
401
402 [ "$VCSH_COMMAND" = 'clon' ]   || [ "$VCSH_COMMAND" = 'clo' ]   || [ "$VCSH_COMMAND" = 'cl' ]                                                                && VCSH_COMMAND='clone'
403 [ "$VCSH_COMMAND" = 'commi' ]  || [ "$VCSH_COMMAND" = 'comm' ]  || [ "$VCSH_COMMAND" = 'com' ]  || [ "$VCSH_COMMAND" = 'co' ]                                && VCSH_COMMAND='commit'
404 [ "$VCSH_COMMAND" = 'delet' ]  || [ "$VCSH_COMMAND" = 'dele' ]  || [ "$VCSH_COMMAND" = 'del' ]  || [ "$VCSH_COMMAND" = 'de' ]                                && VCSH_COMMAND='delete'
405 [ "$VCSH_COMMAND" = 'ente' ]   || [ "$VCSH_COMMAND" = 'ent' ]   || [ "$VCSH_COMMAND" = 'en' ]                                                                && VCSH_COMMAND='enter'
406 [ "$VCSH_COMMAND" = 'hel' ]    || [ "$VCSH_COMMAND" = 'he' ]                                                                                                 && VCSH_COMMAND='help'
407 [ "$VCSH_COMMAND" = 'ini' ]    || [ "$VCSH_COMMAND" = 'in' ]                                                                                                 && VCSH_COMMAND='init'
408 [ "$VCSH_COMMAND" = 'pul' ]                                                                                                                                  && VCSH_COMMAND='pull'
409 [ "$VCSH_COMMAND" = 'pus' ]                                                                                                                                  && VCSH_COMMAND='push'
410 [ "$VCSH_COMMAND" = 'renam' ]  || [ "$VCSH_COMMAND" = 'rena' ]  || [ "$VCSH_COMMAND" = 'ren' ]  || [ "$VCSH_COMMAND" = 're' ]                                && VCSH_COMMAND='rename'
411 [ "$VCSH_COMMAND" = 'ru' ]                                                                                                                                   && VCSH_COMMAND='run'
412 [ "$VCSH_COMMAND" = 'statu' ]  || [ "$VCSH_COMMAND" = 'stat' ]  || [ "$VCSH_COMMAND" = 'sta' ]  || [ "$VCSH_COMMAND" = 'st' ]                                && VCSH_COMMAND='status'
413 [ "$VCSH_COMMAND" = 'upgrad' ] || [ "$VCSH_COMMAND" = 'upgra' ] || [ "$VCSH_COMMAND" = 'upgr' ] || [ "$VCSH_COMMAND" = 'upg' ]                               && VCSH_COMMAND='upgrade'
414 [ "$VCSH_COMMAND" = 'versio' ] || [ "$VCSH_COMMAND" = 'versi' ] || [ "$VCSH_COMMAND" = 'vers' ] || [ "$VCSH_COMMAND" = 'ver' ] || [ "$VCSH_COMMAND" = 've' ] && VCSH_COMMAND='version'
415 [ "$VCSH_COMMAND" = 'whic' ]   || [ "$VCSH_COMMAND" = 'whi' ]   || [ "$VCSH_COMMAND" = 'wh' ]                                                                && VCSH_COMMAND='which'
416 [ "$VCSH_COMMAND" = 'write' ]  || [ "$VCSH_COMMAND" = 'writ' ]  || [ "$VCSH_COMMAND" = 'wri' ]  || [ "$VCSH_COMMAND" = 'wr' ]                                && VCSH_COMMAND='write-gitignore'
417
418
419 if [ "$VCSH_COMMAND" = 'clone' ]; then
420         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a remote" 1
421         GIT_REMOTE="$2"
422         [ -n "$3" ] && VCSH_REPO_NAME="$3" || VCSH_REPO_NAME=$(basename "$GIT_REMOTE" .git)
423         [ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: could not determine repository name" 1
424         export VCSH_REPO_NAME
425         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
426 elif [ "$VCSH_COMMAND" = 'version' ]; then
427         echo "$SELF $VERSION"
428         git version
429         exit
430 elif [ "$VCSH_COMMAND" = 'which' ]; then
431         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a filename" 1
432         [ -n "$3" ] && fatal "$VCSH_COMMAND: too many parameters" 1
433         export VCSH_COMMAND_PARAMETER="$2"
434 elif [ "$VCSH_COMMAND" = 'delete' ]           ||
435      [ "$VCSH_COMMAND" = 'enter' ]            ||
436      [ "$VCSH_COMMAND" = 'init' ]             ||
437      [ "$VCSH_COMMAND" = 'list-tracked-by' ]  ||
438      [ "$VCSH_COMMAND" = 'rename' ]           ||
439      [ "$VCSH_COMMAND" = 'run' ]              ||
440      [ "$VCSH_COMMAND" = 'upgrade' ]          ||
441      [ "$VCSH_COMMAND" = 'write-gitignore' ]; then
442         [ -z $2 ]                                 && fatal "$VCSH_COMMAND: please specify repository to work on" 1
443         [ "$VCSH_COMMAND" = 'rename' -a -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1
444         [ "$VCSH_COMMAND" = 'run'    -a -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1
445         export VCSH_REPO_NAME="$2"
446         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
447         [ "$VCSH_COMMAND" = 'rename' ] && { export VCSH_REPO_NAME_NEW="$3";
448                                             export GIT_DIR_NEW="$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git"; }
449         [ "$VCSH_COMMAND" = 'run' ]    && shift 2
450 elif [ "$VCSH_COMMAND" = 'commit' ] ||
451      [ "$VCSH_COMMAND" = 'list' ] ||
452      [ "$VCSH_COMMAND" = 'list-tracked' ] ||
453      [ "$VCSH_COMMAND" = 'pull' ] ||
454      [ "$VCSH_COMMAND" = 'push' ]; then
455         :
456 elif [ "$VCSH_COMMAND" = 'status' ]; then
457         export VCSH_REPO_NAME="$2"
458 elif [ -n "$2" ]; then
459         export VCSH_COMMAND='run'
460         export VCSH_REPO_NAME="$1"
461         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
462         [ -d $GIT_DIR ] || { help; exit 1; }
463         shift 1
464         set -- "git" "$@"
465 elif [ -n "$VCSH_COMMAND" ]; then
466         export VCSH_COMMAND='enter'
467         export VCSH_REPO_NAME="$1"
468         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
469         [ -d $GIT_DIR ] || { help; exit 1; }
470 else
471         # $1 is empty, or 'help'
472         help && exit
473 fi
474
475 # Did we receive a directory instead of a name?
476 # Mangle the input to fit normal operation.
477 if echo $VCSH_REPO_NAME | grep -q '/'; then
478         export GIT_DIR=$VCSH_REPO_NAME
479         export VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git)
480 fi
481
482 check_dir() {
483         check_directory="$1"
484         if [ ! -d "$check_directory" ]; then
485                 if [ -e "$check_directory" ]; then
486                         fatal "'$check_directory' exists but is not a directory" 13
487                 else
488                         verbose "attempting to create '$check_directory'"
489                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
490                 fi
491         fi
492 }
493
494 check_dir "$VCSH_REPO_D"
495 [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && check_dir "$VCSH_BASE/.gitignore.d"
496
497 verbose "$VCSH_COMMAND begin"
498 export VCSH_COMMAND=$(echo $VCSH_COMMAND | sed 's/-/_/g')
499 hook pre-command
500 $VCSH_COMMAND "$@"
501 hook post-command
502 verbose "$VCSH_COMMAND end, exiting"