]> 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: Allow `vcsh list-untracked <repo>`
[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    list-untracked \\
113         [<-r>] [<repo>] List all files not tracked by all or one repositories
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 list_untracked() {
290         command -v 'comm' >/dev/null 2>&1 || fatal "Could not find 'comm'"
291
292         temp_file_others=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
293         temp_file_untracked=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
294         temp_file_untracked_copy=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
295
296         # Hack in support for `vcsh list-untracked -r`...
297         directory_opt="--directory"
298         shift 1
299         while getopts "r" flag; do
300                 if [ x"$1" = x'-r' ]; then
301                         unset directory_opt
302                 fi
303                 shift 1
304         done
305         # ...and parse for a potential parameter afterwards. As we shifted things out of $* in during getops, we need to look at $1
306         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
307
308         if [ -n "$VCSH_REPO_NAME" ]; then
309                 list_untracked_helper $VCSH_REPO_NAME
310         else
311                 for VCSH_REPO_NAME in $(list); do
312                         list_untracked_helper $VCSH_REPO_NAME
313                 done
314         fi
315         cat $temp_file_untracked
316
317         unset directory_opt directory_component
318         rm -f $temp_file_others $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not delete temp files'
319 }
320
321 list_untracked_helper() {
322         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
323         git ls-files --others "$directory_opt" | (
324                 while read line; do
325                         echo "$line"
326                         directory_component=${line%%/*}
327                         [ -d "$directory_component" ] && printf '%s/\n' "$directory_component"
328                 done
329                 ) | sort -u > $temp_file_others
330         if [ -z "$ran_once" ]; then
331                 ran_once=1
332                 cp $temp_file_others $temp_file_untracked || fatal 'Could not copy temp file'
333         fi
334         cp $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not copy temp file'
335         comm -12 --nocheck-order $temp_file_others $temp_file_untracked_copy > $temp_file_untracked
336 }
337
338 rename() {
339         git_dir_exists
340         [ -d "$GIT_DIR_NEW" ] && fatal "'$GIT_DIR_NEW' exists" 54
341         mv -f "$GIT_DIR" "$GIT_DIR_NEW" || fatal "Could not mv '$GIT_DIR' '$GIT_DIR_NEW'" 52
342
343         # Now that the repository has been renamed, we need to fix up its configuration
344         # Overwrite old name..
345         GIT_DIR=$GIT_DIR_NEW
346         VCSH_REPO_NAME=$VCSH_REPO_NAME_NEW
347         # ..and clobber all old configuration
348         upgrade
349 }
350
351 run() {
352         hook pre-run
353         use
354         "$@"
355         VCSH_COMMAND_RETURN_CODE=$?
356         hook post-run
357 }
358
359 status() {
360         if [ -n "$VCSH_REPO_NAME" ]; then
361                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
362                 use
363                 git status --short --untracked-files='no'
364                 VCSH_COMMAND_RETURN_CODE=$?
365         else
366                 for VCSH_REPO_NAME in $(list); do
367                         echo "$VCSH_REPO_NAME:"
368                         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
369                         use
370                         git status --short --untracked-files='no'
371                         VCSH_COMMAND_RETURN_CODE=$?
372                         echo
373                 done
374         fi
375 }
376
377 upgrade() {
378         hook pre-upgrade
379         # fake-bare repositories are not bare, actually. Set this to false
380         # because otherwise Git complains "fatal: core.bare and core.worktree
381         # do not make sense"
382         git config core.bare false
383         # core.worktree may be absolute or relative to $GIT_DIR, depending on
384         # user preference
385         if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ]; then
386                 git config core.worktree "$(cd "$GIT_DIR" && GIT_WORK_TREE=$VCSH_BASE git rev-parse --show-cdup)"
387         elif [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
388                 git config core.worktree "$VCSH_BASE"
389         fi
390         [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
391         [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && git config core.attributesfile ".gitattributes.d/$VCSH_REPO_NAME"
392         git config vcsh.vcsh 'true'
393         use
394         [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
395         [ -e "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME"
396         hook post-upgrade
397 }
398
399 use() {
400         git_dir_exists
401         VCSH_DIRECTORY=$VCSH_REPO_NAME; export VCSH_DIRECTORY
402 }
403
404 which() {
405         for VCSH_REPO_NAME in $(list); do
406                 for VCSH_FILE in $(get_files); do
407                         echo "$VCSH_FILE" | grep -q "$VCSH_COMMAND_PARAMETER" && echo "$VCSH_REPO_NAME: $VCSH_FILE"
408                 done
409         done | sort -u
410 }
411
412 write_gitignore() {
413         # Don't do anything if the user does not want to write gitignore
414         if [ "x$VCSH_GITIGNORE" = 'xnone' ]; then
415                 info "Not writing gitignore as '\$VCSH_GITIGNORE' is set to 'none'"
416                 exit
417         fi
418
419         use
420         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
421         OLDIFS=$IFS
422         IFS=$(printf '\n\t')
423         gitignores=$(for file in $(git ls-files); do
424                 while true; do
425                         echo "$file"; new=${file%/*}
426                         [ x"$file" = x"$new" ] && break
427                         file=$new
428                 done;
429         done | sort -u)
430
431         # Contrary to GNU mktemp, mktemp on BSD/OSX requires a template for temp files
432         # Using a template makes GNU mktemp default to $PWD and not #TMPDIR for tempfile location
433         # To make every OS happy, set full path explicitly
434         tempfile=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal "could not create tempfile: '${tempfile}'" 51
435
436         echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
437         for gitignore in $gitignores; do
438                 echo "$gitignore" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57
439                 if [ "x$VCSH_GITIGNORE" = 'xrecursive' ] && [ -d "$gitignore" ]; then
440                         { echo "$gitignore/*" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; }
441                 fi
442         done
443         IFS=$OLDIFS
444         if diff -N "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then
445                 rm -f "$tempfile" || error "could not delete '$tempfile'"
446                 exit
447         fi
448         if [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ]; then
449                 info "'$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'"
450                 mv -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak" ||
451                         fatal "could not move '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
452         fi
453         mv -f "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ||
454                 fatal "could not move '$tempfile' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME'" 53
455 }
456
457 debug $(git version)
458
459 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
460         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
461 fi
462
463 VCSH_COMMAND=$1; export VCSH_COMMAND
464
465 case $VCSH_COMMAND in
466         clon|clo|cl) VCSH_COMMAND=clone;;
467         commi|comm|com|co) VCSH_COMMAND=commit;;
468         delet|dele|del|de) VCSH_COMMAND=delete;;
469         ente|ent|en) VCSH_COMMAND=enter;;
470         hel|he) VCSH_COMMAND=help;;
471         ini|in) VCSH_COMMAND=init;;
472         pul) VCSH_COMMAND=pull;;
473         pus) VCSH_COMMAND=push;;
474         renam|rena|ren|re) VCSH_COMMAND=rename;;
475         ru) VCSH_COMMAND=run;;
476         statu|stat|sta|st) VCSH_COMMAND=status;;
477         upgrad|upgra|upgr|up) VCSH_COMMAND=upgrade;;
478         versio|versi|vers|ver|ve) VCSH_COMMAND=version;;
479         which|whi|wh) VCSH_COMMAND=which;;
480         write|writ|wri|wr) VCSH_COMMAND=write-gitignore;;
481 esac    
482
483 if [ x"$VCSH_COMMAND" = x'clone' ]; then
484         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a remote" 1
485         GIT_REMOTE="$2"
486         [ -n "$3" ] && VCSH_REPO_NAME=$3 || VCSH_REPO_NAME=$(basename "${GIT_REMOTE#*:}" .git)
487         [ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: could not determine repository name" 1
488         export VCSH_REPO_NAME
489         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
490 elif [ "$VCSH_COMMAND" = 'version' ]; then
491         echo "$SELF $VERSION"
492         git version
493         exit
494 elif [ x"$VCSH_COMMAND" = x'which' ]; then
495         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a filename" 1
496         [ -n "$3" ] && fatal "$VCSH_COMMAND: too many parameters" 1
497         VCSH_COMMAND_PARAMETER=$2; export VCSH_COMMAND_PARAMETER
498 elif [ x"$VCSH_COMMAND" = x'delete' ]           ||
499      [ x"$VCSH_COMMAND" = x'enter' ]            ||
500      [ x"$VCSH_COMMAND" = x'init' ]             ||
501      [ x"$VCSH_COMMAND" = x'list-tracked-by' ]  ||
502      [ x"$VCSH_COMMAND" = x'rename' ]           ||
503      [ x"$VCSH_COMMAND" = x'run' ]              ||
504      [ x"$VCSH_COMMAND" = x'upgrade' ]          ||
505      [ x"$VCSH_COMMAND" = x'write-gitignore' ]; then
506         [ -z "$2" ]                                     && fatal "$VCSH_COMMAND: please specify repository to work on" 1
507         [ x"$VCSH_COMMAND" = x'rename' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1
508         [ x"$VCSH_COMMAND" = x'run'    ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1
509         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
510         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
511         [ x"$VCSH_COMMAND" = x'rename' ] && { VCSH_REPO_NAME_NEW=$3; export VCSH_REPO_NAME_NEW;
512                                               GIT_DIR_NEW=$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git; export GIT_DIR_NEW; }
513         [ x"$VCSH_COMMAND" = x'run' ]    && shift 2
514 elif [ x"$VCSH_COMMAND" = x'commit' ] ||
515      [ x"$VCSH_COMMAND" = x'list'   ] ||
516      [ x"$VCSH_COMMAND" = x'list-tracked' ] ||
517      [ x"$VCSH_COMMAND" = x'list-untracked' ] ||
518      [ x"$VCSH_COMMAND" = x'pull'   ] ||
519      [ x"$VCSH_COMMAND" = x'push'   ]; then
520         :
521 elif [ x"$VCSH_COMMAND" = x'status' ]; then
522         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
523 elif [ -n "$2" ]; then
524         VCSH_COMMAND='run'; export VCSH_COMMAND
525         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
526         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
527         [ -d "$GIT_DIR" ] || { help; exit 1; }
528         shift 1
529         set -- "git" "$@"
530 elif [ -n "$VCSH_COMMAND" ]; then
531         VCSH_COMMAND='enter'; export VCSH_COMMAND
532         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
533         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
534         [ -d "$GIT_DIR" ] || { help; exit 1; }
535 else
536         # $1 is empty, or 'help'
537         help && exit
538 fi
539
540 # Did we receive a directory instead of a name?
541 # Mangle the input to fit normal operation.
542 if echo "$VCSH_REPO_NAME" | grep -q '/'; then
543         GIT_DIR=$VCSH_REPO_NAME; export GIT_DIR
544         VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git); export VCSH_REPO_NAME
545 fi
546
547 check_dir() {
548         check_directory="$1"
549         if [ ! -d "$check_directory" ]; then
550                 if [ -e "$check_directory" ]; then
551                         fatal "'$check_directory' exists but is not a directory" 13
552                 else
553                         verbose "attempting to create '$check_directory'"
554                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
555                 fi
556         fi
557 }
558
559 check_dir "$VCSH_REPO_D"
560 [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && check_dir "$VCSH_BASE/.gitignore.d"
561 [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && check_dir "$VCSH_BASE/.gitattributes.d"
562
563 verbose "$VCSH_COMMAND begin"
564 VCSH_COMMAND=$(echo "$VCSH_COMMAND" | sed 's/-/_/g'); export VCSH_COMMAND
565 hook pre-command
566 $VCSH_COMMAND "$@"
567 hook post-command
568 verbose "$VCSH_COMMAND end, exiting"
569 exit $VCSH_COMMAND_RETURN_CODE