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

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