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