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

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