]> 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: Remove list-tracked-by completely
[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-2015
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.20141026'
23 SELF=$(basename $0)
24
25 fatal() {
26         echo "$SELF: fatal: $1" >&2
27         [ -z $2 ] && exit 1
28         exit $2
29 }
30
31 # We need to run getops as soon as possible so we catch -d and other
32 # options that will modify our behaviour.
33 # Commands are handled at the end of this script.
34 while getopts "c:dv" flag; do
35         if [ x"$1" = x'-d' ] || [ x"$1" = x'--debug' ]; then
36                 set -vx
37                 VCSH_DEBUG=1
38                 echo "debug mode on"
39                 echo "$SELF $VERSION"
40         elif [ x"$1" = x'-v' ]; then
41                 VCSH_VERBOSE=1
42                 echo "verbose mode on"
43                 echo "$SELF $VERSION"
44         elif [ x"$1" = x'-c' ]; then
45                 VCSH_OPTION_CONFIG=$OPTARG
46         fi
47         shift 1
48 done
49
50 source_all() {
51         # Source file even if it's in $PWD and does not have any slashes in it
52         case $1 in
53                 */*) . "$1";;
54                 *)   . "$PWD/$1";;
55         esac;
56 }
57
58
59 # Read configuration and set defaults if anything's not set
60 [ -n "$VCSH_DEBUG" ]                  && set -vx
61 : ${XDG_CONFIG_HOME:="$HOME/.config"}
62
63 # Read configuration files if there are any
64 [ -r "/etc/vcsh/config" ]             && . "/etc/vcsh/config"
65 [ -r "$XDG_CONFIG_HOME/vcsh/config" ] && . "$XDG_CONFIG_HOME/vcsh/config"
66 if [ -n "$VCSH_OPTION_CONFIG" ]; then
67         # Source $VCSH_OPTION_CONFIG if it can be read and is in $PWD of $PATH
68         if [ -r "$VCSH_OPTION_CONFIG" ]; then
69                 source_all "$VCSH_OPTION_CONFIG"
70         else
71                 fatal "Can not read configuration file '$VCSH_OPTION_CONFIG'" 1
72         fi
73 fi
74 [ -n "$VCSH_DEBUG" ]                  && set -vx
75
76 # Read defaults
77 : ${VCSH_REPO_D:="$XDG_CONFIG_HOME/vcsh/repo.d"}
78 : ${VCSH_HOOK_D:="$XDG_CONFIG_HOME/vcsh/hooks-enabled"}
79 : ${VCSH_OVERLAY_D:="$XDG_CONFIG_HOME/vcsh/overlays-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 [-b <branch>] \\
104          <remote> \\
105          [<repo>]       Clone from an existing repository
106    commit               Commit in all repositories
107    delete <repo>        Delete an existing repository
108    enter <repo>         Enter repository; spawn new instance of \$SHELL
109    help                 Display this help text
110    init <repo>          Initialize a new repository
111    list                 List all repositories
112    list-tracked \\
113         [<repo>]        List all files tracked all or one repositories
114    list-untracked \\
115         [<-a>] [<-r>]
116         [<repo>]        List all files not tracked by all or one repositories
117    pull                 Pull from all vcsh remotes
118    push                 Push to vcsh remotes
119    rename <repo> \\
120           <newname>     Rename repository
121    run <repo> \\
122        <command>        Use this repository
123    status \\
124      [--terse] [<repo>] Show statuses of all/one vcsh repositories
125    upgrade <repo>       Upgrade repository to currently recommended settings
126    version              Print version information
127    which <substring>    Find substring in name of any tracked file
128    write-gitignore \\
129    <repo>               Write .gitignore.d/<repo> via git ls-files
130
131    <repo> <git command> Shortcut to run git commands directly
132    <repo>               Shortcut to enter repository" >&2
133 }
134
135 debug() {
136         [ -n "$VCSH_DEBUG" ] && echo "$SELF: debug: $@"
137 }
138
139 verbose() {
140         if [ -n "$VCSH_DEBUG" ] || [ -n "$VCSH_VERBOSE" ]; then echo "$SELF: verbose: $@"; fi
141 }
142
143 error() {
144         echo "$SELF: error: $1" >&2
145 }
146
147 info() {
148         echo "$SELF: info: $1"
149 }
150
151 clone() {
152         hook pre-clone
153         init
154         git remote add origin "$GIT_REMOTE"
155         git checkout -b "$VCSH_BRANCH" || return $?
156         git config branch."$VCSH_BRANCH".remote origin
157         git config branch."$VCSH_BRANCH".merge  refs/heads/"$VCSH_BRANCH"
158         if [ $(git ls-remote origin "$VCSH_BRANCH" 2> /dev/null | wc -l ) -lt 1 ]; then
159                 info "remote is empty, not merging anything.
160   You should add files to your new repository."
161                 exit
162         fi
163         git fetch origin "$VCSH_BRANCH"
164         hook pre-merge
165         git ls-tree -r --name-only origin/"$VCSH_BRANCH" | (while read object; do
166                 [ -e "$object" ] &&
167                         error "'$object' exists." &&
168                         VCSH_CONFLICT=1
169         done
170         [ x"$VCSH_CONFLICT" = x'1' ]) &&
171                 fatal "will stop after fetching and not try to merge!
172   Once this situation has been resolved, run 'vcsh $VCSH_REPO_NAME pull' to finish cloning." 17
173         git -c merge.ff=true merge origin/"$VCSH_BRANCH"
174         hook post-merge
175         hook post-clone
176         retire
177         hook post-clone-retired
178 }
179
180 commit() {
181         hook pre-commit
182         for VCSH_REPO_NAME in $(list); do
183                 echo "$VCSH_REPO_NAME: "
184                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
185                 use
186                 git commit --untracked-files=no --quiet
187                 VCSH_COMMAND_RETURN_CODE=$?
188                 echo
189         done
190         hook post-commit
191 }
192
193 delete() {
194         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
195         use
196         info "This operation WILL DESTROY DATA!"
197         files=$(git ls-files)
198         echo "These files will be deleted:
199
200 $files
201
202 AGAIN, THIS WILL DELETE YOUR DATA!
203 To continue, type 'Yes, do as I say'"
204         read answer
205         [ "x$answer" = 'xYes, do as I say' ] || exit 16
206         for file in $files; do
207                 rm -f $file || info "could not delete '$file', continuing with deletion"
208         done
209         rm -rf "$GIT_DIR" || error "could not delete '$GIT_DIR'"
210 }
211
212 enter() {
213         hook pre-enter
214         use
215         $SHELL
216         hook post-enter
217 }
218
219 git_dir_exists() {
220         [ -d "$GIT_DIR" ] || fatal "no repository found for '$VCSH_REPO_NAME'" 12
221 }
222
223 hook() {
224         for hook in "$VCSH_HOOK_D/$1"* "$VCSH_HOOK_D/$VCSH_REPO_NAME.$1"*; do
225                 [ -x "$hook" ] || continue
226                 verbose "executing '$hook'"
227                 "$hook"
228         done
229 }
230
231 init() {
232         hook pre-init
233         [ ! -e "$GIT_DIR" ] || fatal "'$GIT_DIR' exists" 10
234         mkdir -p "$VCSH_BASE" || fatal "could not create '$VCSH_BASE'" 50
235         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
236         git init --shared=0600
237         upgrade
238         hook post-init
239 }
240
241 list() {
242         for repo in "$VCSH_REPO_D"/*.git; do
243                 [ -d "$repo" ] && [ -r "$repo" ] && echo "$(basename "$repo" .git)"
244         done
245 }
246
247 get_files() {
248         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
249         git ls-files
250 }
251
252 list_tracked() {
253         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
254         if [ -n "$VCSH_REPO_NAME" ]; then
255                 get_files | list_tracked_helper
256         else
257                 for VCSH_REPO_NAME in $(list); do
258                         get_files
259                 done | list_tracked_helper
260         fi
261 }
262
263 list_tracked_helper() {
264         sed "s,^,$(printf '%s\n' "$VCSH_BASE/" | sed 's/[,\&]/\\&/g')," | sort -u
265 }
266
267 list_tracked_by() {
268         list_tracked '' $2
269 }
270
271 list_untracked() {
272         command -v 'comm' >/dev/null 2>&1 || fatal "Could not find 'comm'"
273
274         temp_file_others=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
275         temp_file_untracked=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
276         temp_file_untracked_copy=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file'
277
278         # Hack in support for `vcsh list-untracked -r`...
279         exclude_standard_opt='--exclude-standard'
280         directory_opt="--directory"
281         shift 1
282         while getopts "ar" flag; do
283                 if [ x"$1" = x'-a' ]; then
284                         unset exclude_standard_opt
285                 elif [ x"$1" = x'-r' ]; then
286                         unset directory_opt
287                 fi
288                 shift 1
289         done
290         # ...and parse for a potential parameter afterwards. As we shifted things out of $* in during getops, we need to look at $1
291         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
292
293         if [ -n "$VCSH_REPO_NAME" ]; then
294                 list_untracked_helper $VCSH_REPO_NAME
295         else
296                 for VCSH_REPO_NAME in $(list); do
297                         list_untracked_helper $VCSH_REPO_NAME
298                 done
299         fi
300         cat $temp_file_untracked
301
302         unset directory_opt directory_component
303         rm -f $temp_file_others $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not delete temp files'
304 }
305
306 list_untracked_helper() {
307         export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git"
308         git ls-files --others $exclude_standard_opt "$directory_opt" | (
309                 while read line; do
310                         echo "$line"
311                         directory_component=${line%%/*}
312                         [ -d "$directory_component" ] && printf '%s/\n' "$directory_component"
313                 done
314                 ) | sort -u > $temp_file_others
315         if [ -z "$ran_once" ]; then
316                 ran_once=1
317                 cp $temp_file_others $temp_file_untracked || fatal 'Could not copy temp file'
318         fi
319         cp $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not copy temp file'
320         comm -12 $temp_file_others $temp_file_untracked_copy > $temp_file_untracked
321 }
322
323 pull() {
324         hook pre-pull
325         for VCSH_REPO_NAME in $(list); do
326                 printf '%s: ' "$VCSH_REPO_NAME"
327                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
328                 use
329                 git pull
330                 VCSH_COMMAND_RETURN_CODE=$?
331                 echo
332         done
333         hook post-pull
334 }
335
336 push() {
337         hook pre-push
338         for VCSH_REPO_NAME in $(list); do
339                 printf '%s: ' "$VCSH_REPO_NAME"
340                 GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
341                 use
342                 git push
343                 VCSH_COMMAND_RETURN_CODE=$?
344                 echo
345         done
346         hook post-push
347 }
348
349 retire() {
350         unset VCSH_DIRECTORY
351 }
352
353 rename() {
354         git_dir_exists
355         [ -d "$GIT_DIR_NEW" ] && fatal "'$GIT_DIR_NEW' exists" 54
356         mv -f "$GIT_DIR" "$GIT_DIR_NEW" || fatal "Could not mv '$GIT_DIR' '$GIT_DIR_NEW'" 52
357
358         # Now that the repository has been renamed, we need to fix up its configuration
359         # Overwrite old name..
360         GIT_DIR=$GIT_DIR_NEW
361         VCSH_REPO_NAME=$VCSH_REPO_NAME_NEW
362         # ..and clobber all old configuration
363         upgrade
364 }
365
366 run() {
367         hook pre-run
368         use
369         "$@"
370         VCSH_COMMAND_RETURN_CODE=$?
371         hook post-run
372 }
373
374 status() {
375         if [ -t 1 ]; then
376                 COLORING="-c color.status=always"
377         fi
378         if [ -n "$VCSH_REPO_NAME" ]; then
379                 status_helper $VCSH_REPO_NAME
380         else
381                 for VCSH_REPO_NAME in $(list); do
382                         STATUS=$(status_helper $VCSH_REPO_NAME "$COLORING")
383                         [ -n "$STATUS" -o -z "$VCSH_STATUS_TERSE" ] && echo "$VCSH_REPO_NAME:"
384                         [ -n "$STATUS" ]            && echo "$STATUS"
385                         [ -z "$VCSH_STATUS_TERSE" ] && echo
386                 done
387         fi
388 }
389
390 status_helper() {
391         GIT_DIR=$VCSH_REPO_D/$1.git; export GIT_DIR
392         VCSH_GIT_OPTIONS=$2
393         use
394         remote_tracking_branch=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2> /dev/null) && {
395                 commits_behind=$(git log ..${remote_tracking_branch} --oneline | wc -l)
396                 commits_ahead=$(git log ${remote_tracking_branch}.. --oneline | wc -l)
397                 [ ${commits_behind} -ne 0 ] && echo "Behind $remote_tracking_branch by $commits_behind commits"
398                 [ ${commits_ahead} -ne 0 ] && echo "Ahead of $remote_tracking_branch by $commits_ahead commits"
399         }
400         git ${VCSH_GIT_OPTIONS} status --short --untracked-files='no'
401         VCSH_COMMAND_RETURN_CODE=$?
402 }
403
404 upgrade() {
405         hook pre-upgrade
406         # fake-bare repositories are not bare, actually. Set this to false
407         # because otherwise Git complains "fatal: core.bare and core.worktree
408         # do not make sense"
409         git config core.bare false
410         # core.worktree may be absolute or relative to $GIT_DIR, depending on
411         # user preference
412         if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ]; then
413                 git config core.worktree "$(cd "$GIT_DIR" && GIT_WORK_TREE=$VCSH_BASE git rev-parse --show-cdup)"
414         elif [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then
415                 git config core.worktree "$VCSH_BASE"
416         fi
417         [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
418         [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && git config core.attributesfile ".gitattributes.d/$VCSH_REPO_NAME"
419         git config vcsh.vcsh 'true'
420         use
421         [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
422         [ -e "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME"
423         hook post-upgrade
424 }
425
426 use() {
427         git_dir_exists
428         VCSH_DIRECTORY=$VCSH_REPO_NAME; export VCSH_DIRECTORY
429 }
430
431 which() {
432         output=$(for VCSH_REPO_NAME in $(list); do
433                 get_files | grep -- "$VCSH_COMMAND_PARAMETER" | sed "s/^/$VCSH_REPO_NAME: /"
434         done | sort -u)
435         if [ -z "$output" ]; then
436                 fatal "'$VCSH_COMMAND_PARAMETER' does not exist" 1
437         else
438                 echo "$output"
439         fi
440 }
441
442 write_gitignore() {
443         # Don't do anything if the user does not want to write gitignore
444         if [ "x$VCSH_GITIGNORE" = 'xnone' ]; then
445                 info "Not writing gitignore as '\$VCSH_GITIGNORE' is set to 'none'"
446                 exit
447         fi
448
449         use
450         cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
451         OLDIFS=$IFS
452         IFS=$(printf '\n\t')
453         gitignores=$(for file in $(git ls-files); do
454                 while true; do
455                         echo "$file"; new=${file%/*}
456                         [ x"$file" = x"$new" ] && break
457                         file=$new
458                 done;
459         done | sort -u)
460
461         # Contrary to GNU mktemp, mktemp on BSD/OSX requires a template for temp files
462         # Using a template makes GNU mktemp default to $PWD and not #TMPDIR for tempfile location
463         # To make every OS happy, set full path explicitly
464         tempfile=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal "could not create tempfile: '${tempfile}'" 51
465
466         echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
467         for gitignore in $gitignores; do
468                 echo "$gitignore" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57
469                 if [ "x$VCSH_GITIGNORE" = 'xrecursive' ] && [ -d "$gitignore" ]; then
470                         { echo "$gitignore/*" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; }
471                 fi
472         done
473         IFS=$OLDIFS
474         if diff -N "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then
475                 rm -f "$tempfile" || error "could not delete '$tempfile'"
476                 exit
477         fi
478         if [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ]; then
479                 info "'$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'"
480                 mv -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak" ||
481                         fatal "could not move '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
482         fi
483         mv -f "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ||
484                 fatal "could not move '$tempfile' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME'" 53
485 }
486
487 debug $(git version)
488
489 if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then
490         fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1
491 fi
492
493 VCSH_COMMAND=$1; export VCSH_COMMAND
494
495 case $VCSH_COMMAND in
496         clon|clo|cl) VCSH_COMMAND=clone;;
497         commi|comm|com|co) VCSH_COMMAND=commit;;
498         delet|dele|del|de) VCSH_COMMAND=delete;;
499         ente|ent|en) VCSH_COMMAND=enter;;
500         hel|he) VCSH_COMMAND=help;;
501         ini|in) VCSH_COMMAND=init;;
502         pul) VCSH_COMMAND=pull;;
503         pus) VCSH_COMMAND=push;;
504         renam|rena|ren|re) VCSH_COMMAND=rename;;
505         ru) VCSH_COMMAND=run;;
506         statu|stat|sta|st) VCSH_COMMAND=status;;
507         upgrad|upgra|upgr|up) VCSH_COMMAND=upgrade;;
508         versio|versi|vers|ver|ve) VCSH_COMMAND=version;;
509         which|whi|wh) VCSH_COMMAND=which;;
510         write|writ|wri|wr) VCSH_COMMAND=write-gitignore;;
511 esac    
512
513 if [ x"$VCSH_COMMAND" = x'clone' ]; then
514         VCSH_BRANCH=
515         if [ "$2" = -b ]; then
516                 VCSH_BRANCH=$3
517                 shift
518                 shift
519         fi
520         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a remote" 1
521         GIT_REMOTE="$2"
522         [ -n "$VCSH_BRANCH" ] || if [ "$3" = -b ]; then
523                 VCSH_BRANCH=$4
524                 shift
525                 shift
526         fi
527         if [ -n "$3" ]; then
528                 VCSH_REPO_NAME=$3
529                 [ -z "$VCSH_BRANCH" ] && [ "$4" = -b ] && VCSH_BRANCH=$5
530         else
531                 VCSH_REPO_NAME=$(basename "${GIT_REMOTE#*:}" .git)
532         fi
533         [ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: could not determine repository name" 1
534         export VCSH_REPO_NAME
535         [ -n "$VCSH_BRANCH" ] || VCSH_BRANCH=master
536         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
537 elif [ "$VCSH_COMMAND" = 'version' ]; then
538         echo "$SELF $VERSION"
539         git version
540         exit
541 elif [ x"$VCSH_COMMAND" = x'which' ]; then
542         [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a filename" 1
543         [ -n "$3" ] && fatal "$VCSH_COMMAND: too many parameters" 1
544         VCSH_COMMAND_PARAMETER=$2; export VCSH_COMMAND_PARAMETER
545 elif [ x"$VCSH_COMMAND" = x'delete' ]           ||
546      [ x"$VCSH_COMMAND" = x'enter' ]            ||
547      [ x"$VCSH_COMMAND" = x'init' ]             ||
548      [ x"$VCSH_COMMAND" = x'list-tracked-by' ]  ||
549      [ x"$VCSH_COMMAND" = x'rename' ]           ||
550      [ x"$VCSH_COMMAND" = x'run' ]              ||
551      [ x"$VCSH_COMMAND" = x'upgrade' ]          ||
552      [ x"$VCSH_COMMAND" = x'write-gitignore' ]; then
553         [ -z "$2" ]                                     && fatal "$VCSH_COMMAND: please specify repository to work on" 1
554         [ x"$VCSH_COMMAND" = x'rename' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1
555         [ x"$VCSH_COMMAND" = x'run'    ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1
556         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
557         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
558         [ x"$VCSH_COMMAND" = x'rename' ] && { VCSH_REPO_NAME_NEW=$3; export VCSH_REPO_NAME_NEW;
559                                               GIT_DIR_NEW=$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git; export GIT_DIR_NEW; }
560         [ x"$VCSH_COMMAND" = x'run' ]    && shift 2
561 elif [ x"$VCSH_COMMAND" = x'commit' ] ||
562      [ x"$VCSH_COMMAND" = x'list'   ] ||
563      [ x"$VCSH_COMMAND" = x'list-tracked' ] ||
564      [ x"$VCSH_COMMAND" = x'list-untracked' ] ||
565      [ x"$VCSH_COMMAND" = x'pull'   ] ||
566      [ x"$VCSH_COMMAND" = x'push'   ]; then
567         :
568 elif [ x"$VCSH_COMMAND" = x'status' ]; then
569         if [ x"$2" = x'--terse' ]; then
570                 VCSH_STATUS_TERSE=1; export VCSH_STATUS_TERSE
571                 shift
572         fi
573         VCSH_REPO_NAME=$2; export VCSH_REPO_NAME
574 elif [ -n "$2" ]; then
575         VCSH_COMMAND='run'; export VCSH_COMMAND
576         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
577         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
578         [ -d "$GIT_DIR" ] || { help; exit 1; }
579         shift 1
580         set -- "git" "$@"
581 elif [ -n "$VCSH_COMMAND" ]; then
582         VCSH_COMMAND='enter'; export VCSH_COMMAND
583         VCSH_REPO_NAME=$1; export VCSH_REPO_NAME
584         GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
585         [ -d "$GIT_DIR" ] || { help; exit 1; }
586 else
587         # $1 is empty, or 'help'
588         help && exit
589 fi
590
591 # Did we receive a directory instead of a name?
592 # Mangle the input to fit normal operation.
593 if echo "$VCSH_REPO_NAME" | grep -q '/'; then
594         GIT_DIR=$VCSH_REPO_NAME; export GIT_DIR
595         VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git); export VCSH_REPO_NAME
596 fi
597
598 check_dir() {
599         check_directory="$1"
600         if [ ! -d "$check_directory" ]; then
601                 if [ -e "$check_directory" ]; then
602                         fatal "'$check_directory' exists but is not a directory" 13
603                 else
604                         verbose "attempting to create '$check_directory'"
605                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
606                 fi
607         fi
608 }
609
610 check_dir "$VCSH_REPO_D"
611 [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && check_dir "$VCSH_BASE/.gitignore.d"
612 [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && check_dir "$VCSH_BASE/.gitattributes.d"
613
614 verbose "$VCSH_COMMAND begin"
615 VCSH_COMMAND=$(echo "$VCSH_COMMAND" | sed 's/-/_/g'); export VCSH_COMMAND
616
617 # Source repo-specific configuration file
618 [ -r "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME" ] && . "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME"
619
620 # source overlay functions
621 for overlay in "$VCSH_OVERLAY_D/$VCSH_COMMAND"* "$VCSH_OVERLAY_D/$VCSH_REPO_NAME.$VCSH_COMMAND"*; do
622         [ -r "$overlay" ] || continue
623         info "sourcing '$overlay'"
624         . "$overlay"
625 done
626
627 hook pre-command
628 $VCSH_COMMAND "$@"
629 hook post-command
630 verbose "$VCSH_COMMAND end, exiting"
631 exit $VCSH_COMMAND_RETURN_CODE