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