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

e4f6f33dc2779ff1bd08c5f20c79dd04ec26845f
[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:dvr" 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'-r' ]; then
44                 VCSH_OPTION_RECURSIVE=1
45         elif [ x"$1" = x'-c' ]; then
46                 VCSH_OPTION_CONFIG=$OPTARG
47         fi
48         shift 1
49 done
50
51 source_all() {
52         # Source file even if it's in $PWD and does not have any slashes in it
53         case $1 in
54                 */*) . "$1";;
55                 *)   . "$PWD/$1";;
56         esac;
57 }
58
59
60 # Read configuration and set defaults if anything's not set
61 [ -n "$VCSH_DEBUG" ]                  && set -vx
62 : ${XDG_CONFIG_HOME:="$HOME/.config"}
63
64 # Read configuration files if there are any
65 [ -r "/etc/vcsh/config" ]             && . "/etc/vcsh/config"
66 [ -r "$XDG_CONFIG_HOME/vcsh/config" ] && . "$XDG_CONFIG_HOME/vcsh/config"
67 if [ -n "$VCSH_OPTION_CONFIG" ]; then
68         # Source $VCSH_OPTION_CONFIG if it can be read and is in $PWD of $PATH
69         if [ -r "$VCSH_OPTION_CONFIG" ]; then
70                 source_all "$VCSH_OPTION_CONFIG"
71         else
72                 fatal "Can not read configuration file '$VCSH_OPTION_CONFIG'" 1
73         fi
74 fi
75 [ -n "$VCSH_DEBUG" ]                  && set -vx
76
77 # Read defaults
78 : ${VCSH_REPO_D:="$XDG_CONFIG_HOME/vcsh/repo.d"}
79 : ${VCSH_HOOK_D:="$XDG_CONFIG_HOME/vcsh/hooks-enabled"}
80 : ${VCSH_BASE:="$HOME"}
81 : ${VCSH_GITIGNORE:=exact}
82 : ${VCSH_GITATTRIBUTES:=none}
83 : ${VCSH_WORKTREE:=absolute}
84
85 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
86         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
87 fi
88
89 if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ] && [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
90         fatal "'\$VCSH_WORKTREE' must equal 'absolute', or 'relative'" 1
91 fi
92
93
94 help() {
95         echo "usage: $SELF <options> <command>
96
97    options:
98    -c <file>            Source file
99    -d                   Enable debug mode
100    -v                   Enable verbose mode
101
102    commands:
103    clone <remote> \\
104          [<repo>]       Clone from an existing repository
105    commit               Commit in all repositories
106    delete <repo>        Delete an existing repository
107    enter <repo>         Enter repository; spawn new instance of \$SHELL
108    help                 Display this help text
109    init <repo>          Initialize a new repository
110    list                 List all repositories
111    list-tracked         List all files tracked by vcsh
112    list-tracked-by \\
113         <repo>          List files tracked by a repository
114    pull                 Pull from all vcsh remotes
115    push                 Push to vcsh remotes
116    rename <repo> \\
117           <newname>     Rename repository
118    run <repo> \\
119        <command>        Use this repository
120    status [<repo>]      Show statuses of all/one vcsh repositories
121    upgrade <repo>       Upgrade repository to currently recommended settings
122    version              Print version information
123    which <substring>    Find substring in name of any tracked file
124    write-gitignore \\
125    <repo>               Write .gitignore.d/<repo> via git ls-files
126
127    <repo> <git command> Shortcut to run git commands directly
128    <repo>               Shortcut to enter repository" >&2
129 }
130
131 debug() {
132         [ -n "$VCSH_DEBUG" ] && echo "$SELF: debug: $@"
133 }
134
135 verbose() {
136         if [ -n "$VCSH_DEBUG" ] || [ -n "$VCSH_VERBOSE" ]; then echo "$SELF: verbose: $@"; fi
137 }
138
139 error() {
140         echo "$SELF: error: $1" >&2
141 }
142
143 info() {
144         echo "$SELF: info: $1"
145 }
146
147 clone() {
148         hook pre-clone
149         init
150         git remote add origin "$GIT_REMOTE"
151         git config branch.master.remote origin
152         git config branch.master.merge  refs/heads/master
153         if [ $(git ls-remote origin master 2> /dev/null | wc -l ) -lt 1 ]; then
154                 info "remote is empty, not merging anything"
155                 exit
156         fi
157         git fetch
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/master
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 command_exists() {
290         command -v "$1" >/dev/null 2>&1 || fatal "Could not find '$1' command"
291 }
292
293 list_untracked() {
294         command_exists comm
295
296         temp_file_others=$(mktemp) || fatal 'Could not create temp file'
297         temp_file_untracked=$(mktemp) || fatal 'Could not create temp file'
298         temp_file_untracked_copy=$(mktemp) || fatal 'Could not create temp file'
299         
300         # create dummy git repo
301         temp_repo=$(mktemp -d) || fatal 'Could not create temp repo'
302
303         cd $temp_repo || fatal 'Could not cd into temp repo'
304         git init -q
305         mktemp -q -p $(pwd) > /dev/null || fatal 'Could not create dummy file'
306         git add .
307         git commit -q -m "dummy"
308         cd - > /dev/null 2>&1 || fatal 'Could not cd back'
309
310         [ -z "$VCSH_OPTION_RECURSIVE" ] && directory_opt="--directory"
311
312         export GIT_DIR=$temp_repo/.git
313         git ls-files --others "$directory_opt" | sort -u > $temp_file_untracked
314
315         for VCSH_REPO_NAME in $(list); do
316                 export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
317                 git ls-files --others "$directory_opt" | (
318                         while read line; do
319                                 echo "$line"
320                                 printf '%s/\n' "$(echo "$line" | cut -d'/' -f1)"
321                         done
322                         ) | sort -u > $temp_file_others
323                 cp $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not copy temp file'
324                 comm -12 --nocheck-order $temp_file_others $temp_file_untracked_copy > $temp_file_untracked
325         done
326         cat $temp_file_untracked
327         
328         unset directory_opt
329         rm -f $temp_file_others $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not delete temp files'
330         rm -rf $temp_repo || fatal 'Could not delete temp repo'
331 }
332
333 rename() {
334         git_dir_exists
335         [ -d "$GIT_DIR_NEW" ] && fatal "'$GIT_DIR_NEW' exists" 54
336         mv -f "$GIT_DIR" "$GIT_DIR_NEW" || fatal "Could not mv '$GIT_DIR' '$GIT_DIR_NEW'" 52
337
338         # Now that the repository has been renamed, we need to fix up its configuration
339         # Overwrite old name..
340         GIT_DIR=$GIT_DIR_NEW
341         VCSH_REPO_NAME=$VCSH_REPO_NAME_NEW
342         # ..and clobber all old configuration
343         upgrade
344 }
345
346 run() {
347         hook pre-run
348         use
349         "$@"
350         VCSH_COMMAND_RETURN_CODE=$?
351         hook post-run
352 }
353
354 status() {
355         if [ -n "$VCSH_REPO_NAME" ]; then
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         else
361                 for VCSH_REPO_NAME in $(list); do
362                         echo "$VCSH_REPO_NAME:"
363                         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
364                         use
365                         git status --short --untracked-files='no'
366                         VCSH_COMMAND_RETURN_CODE=$?
367                         echo
368                 done
369         fi
370 }
371
372 upgrade() {
373         hook pre-upgrade
374         # fake-bare repositories are not bare, actually. Set this to false
375         # because otherwise Git complains "fatal: core.bare and core.worktree
376         # do not make sense"
377         git config core.bare false
378         # core.worktree may be absolute or relative to $GIT_DIR, depending on
379         # user preference
380         if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ]; then
381                 git config core.worktree "$(cd "$GIT_DIR" && GIT_WORK_TREE=$VCSH_BASE git rev-parse --show-cdup)"
382         elif [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
383                 git config core.worktree "$VCSH_BASE"
384         fi
385         [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
386         [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && git config core.attributesfile ".gitattributes.d/$VCSH_REPO_NAME"
387         git config vcsh.vcsh 'true'
388         use
389         [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
390         [ -e "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME"
391         hook post-upgrade
392 }
393
394 use() {
395         git_dir_exists
396         VCSH_DIRECTORY=$VCSH_REPO_NAME; export VCSH_DIRECTORY
397 }
398
399 which() {
400         for VCSH_REPO_NAME in $(list); do
401                 for VCSH_FILE in $(get_files); do
402                         echo "$VCSH_FILE" | grep -q "$VCSH_COMMAND_PARAMETER" && echo "$VCSH_REPO_NAME: $VCSH_FILE"
403                 done
404         done | sort -u
405 }
406
407 write_gitignore() {
408         # Don't do anything if the user does not want to write gitignore
409         if [ "x$VCSH_GITIGNORE" = 'xnone' ]; then
410                 info "Not writing gitignore as '\$VCSH_GITIGNORE' is set to 'none'"
411                 exit
412         fi
413
414         use
415         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
416         OLDIFS=$IFS
417         IFS=$(printf '\n\t')
418         gitignores=$(for file in $(git ls-files); do
419                 while true; do
420                         echo "$file"; new=${file%/*}
421                         [ x"$file" = x"$new" ] && break
422                         file=$new
423                 done;
424         done | sort -u)
425
426         # Contrary to GNU mktemp, mktemp on BSD/OSX requires a template for temp files
427         # Using a template makes GNU mktemp default to $PWD and not #TMPDIR for tempfile location
428         # To make every OS happy, set full path explicitly
429         tempfile=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal "could not create tempfile: '${tempfile}'" 51
430
431         echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
432         for gitignore in $gitignores; do
433                 echo "$gitignore" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57
434                 if [ "x$VCSH_GITIGNORE" = 'xrecursive' ] && [ -d "$gitignore" ]; then
435                         { echo "$gitignore/*" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; }
436                 fi
437         done
438         IFS=$OLDIFS
439         if diff -N "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then
440                 rm -f "$tempfile" || error "could not delete '$tempfile'"
441                 exit
442         fi
443         if [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ]; then
444                 info "'$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'"
445                 mv -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak" ||
446                         fatal "could not move '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
447         fi
448         mv -f "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ||
449                 fatal "could not move '$tempfile' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME'" 53
450 }
451
452 debug $(git version)
453
454 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
455         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
456 fi
457
458 VCSH_COMMAND=$1; export VCSH_COMMAND
459
460 case $VCSH_COMMAND in
461         clon|clo|cl) VCSH_COMMAND=clone;;
462         commi|comm|com|co) VCSH_COMMAND=commit;;
463         delet|dele|del|de) VCSH_COMMAND=delete;;
464         ente|ent|en) VCSH_COMMAND=enter;;
465         hel|he) VCSH_COMMAND=help;;
466         ini|in) VCSH_COMMAND=init;;
467         pul) VCSH_COMMAND=pull;;
468         pus) VCSH_COMMAND=push;;
469         renam|rena|ren|re) VCSH_COMMAND=rename;;
470         ru) VCSH_COMMAND=run;;
471         statu|stat|sta|st) VCSH_COMMAND=status;;
472         upgrad|upgra|upgr|up) VCSH_COMMAND=upgrade;;
473         versio|versi|vers|ver|ve) VCSH_COMMAND=version;;
474         which|whi|wh) VCSH_COMMAND=which;;
475         write|writ|wri|wr) VCSH_COMMAND=write-gitignore;;
476 esac    
477
478 if [ x"$VCSH_COMMAND" = x'clone' ]; then
479         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a remote" 1
480         GIT_REMOTE="$2"
481         [ -n "$3" ] && VCSH_REPO_NAME=$3 || VCSH_REPO_NAME=$(basename "${GIT_REMOTE#*:}" .git)
482         [ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: could not determine repository name" 1
483         export VCSH_REPO_NAME
484         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
485 elif [ "$VCSH_COMMAND" = 'version' ]; then
486         echo "$SELF $VERSION"
487         git version
488         exit
489 elif [ x"$VCSH_COMMAND" = x'which' ]; then
490         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a filename" 1
491         [ -n "$3" ] && fatal "$VCSH_COMMAND: too many parameters" 1
492         VCSH_COMMAND_PARAMETER=$2; export VCSH_COMMAND_PARAMETER
493 elif [ x"$VCSH_COMMAND" = x'delete' ]           ||
494      [ x"$VCSH_COMMAND" = x'enter' ]            ||
495      [ x"$VCSH_COMMAND" = x'init' ]             ||
496      [ x"$VCSH_COMMAND" = x'list-tracked-by' ]  ||
497      [ x"$VCSH_COMMAND" = x'rename' ]           ||
498      [ x"$VCSH_COMMAND" = x'run' ]              ||
499      [ x"$VCSH_COMMAND" = x'upgrade' ]          ||
500      [ x"$VCSH_COMMAND" = x'write-gitignore' ]; then
501         [ -z "$2" ]                                     && fatal "$VCSH_COMMAND: please specify repository to work on" 1
502         [ x"$VCSH_COMMAND" = x'rename' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1
503         [ x"$VCSH_COMMAND" = x'run'    ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1
504         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
505         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
506         [ x"$VCSH_COMMAND" = x'rename' ] && { VCSH_REPO_NAME_NEW=$3; export VCSH_REPO_NAME_NEW;
507                                               GIT_DIR_NEW=$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git; export GIT_DIR_NEW; }
508         [ x"$VCSH_COMMAND" = x'run' ]    && shift 2
509 elif [ x"$VCSH_COMMAND" = x'commit' ] ||
510      [ x"$VCSH_COMMAND" = x'list'   ] ||
511      [ x"$VCSH_COMMAND" = x'list-tracked' ] ||
512      [ x"$VCSH_COMMAND" = x'list-untracked' ] ||
513      [ x"$VCSH_COMMAND" = x'pull'   ] ||
514      [ x"$VCSH_COMMAND" = x'push'   ]; then
515         :
516 elif [ x"$VCSH_COMMAND" = x'status' ]; then
517         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
518 elif [ -n "$2" ]; then
519         VCSH_COMMAND='run'; 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         shift 1
524         set -- "git" "$@"
525 elif [ -n "$VCSH_COMMAND" ]; then
526         VCSH_COMMAND='enter'; export VCSH_COMMAND
527         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
528         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
529         [ -d "$GIT_DIR" ] || { help; exit 1; }
530 else
531         # $1 is empty, or 'help'
532         help && exit
533 fi
534
535 # Did we receive a directory instead of a name?
536 # Mangle the input to fit normal operation.
537 if echo "$VCSH_REPO_NAME" | grep -q '/'; then
538         GIT_DIR=$VCSH_REPO_NAME; export GIT_DIR
539         VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git); export VCSH_REPO_NAME
540 fi
541
542 check_dir() {
543         check_directory="$1"
544         if [ ! -d "$check_directory" ]; then
545                 if [ -e "$check_directory" ]; then
546                         fatal "'$check_directory' exists but is not a directory" 13
547                 else
548                         verbose "attempting to create '$check_directory'"
549                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
550                 fi
551         fi
552 }
553
554 check_dir "$VCSH_REPO_D"
555 [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && check_dir "$VCSH_BASE/.gitignore.d"
556 [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && check_dir "$VCSH_BASE/.gitattributes.d"
557
558 verbose "$VCSH_COMMAND begin"
559 VCSH_COMMAND=$(echo "$VCSH_COMMAND" | sed 's/-/_/g'); export VCSH_COMMAND
560 hook pre-command
561 $VCSH_COMMAND "$@"
562 hook post-command
563 verbose "$VCSH_COMMAND end, exiting"
564 exit $VCSH_COMMAND_RETURN_CODE