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