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

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