diff --git a/_vcsh b/_vcsh
index 3022c6125d6a4a82db9697ee76a5f7c229d12f95..9aca0f97095021e28777baffbcea58a1dbe1ef5f 100644
--- a/_vcsh
+++ b/_vcsh
@@ -24,7 +24,7 @@ function _vcsh-enter () {
 }
 
 function _vcsh-foreach () {
-	_dispatch git git
+	_dispatch vcsh-foreach git
 }
 
 function _vcsh-help () {
@@ -63,10 +63,12 @@ function _vcsh-rename () {
 
 function _vcsh-run () {
 	(( CURRENT == 2 )) && __vcsh_repositories
-	if (( CURRENT >= 3 )); then
+	(( CURRENT == 3 )) && _command_names -e
+	if (( CURRENT >= 4 )); then
+		# see _precommand in zsh
 		words=( "${(@)words[3,-1]}" )
 		(( CURRENT -= 2 ))
-		_complete
+		_normal
 	fi
 }
 
@@ -95,6 +97,9 @@ function _vcsh () {
 	local state vcshcommand
 	local -a args subcommands
 
+	local VCSH_REPO_D
+        : ${VCSH_REPO_D:="${XDG_CONFIG_HOME:-"$HOME/.config"}/vcsh/repo.d"}
+
 	subcommands=(
 		"clone:clone an existing repository"
 		"commit:commit in all repositories"
@@ -135,10 +140,11 @@ function _vcsh () {
 			if ! (( ${+functions[_vcsh-$vcshcommand]} )); then
 				# There is no handler function, so this is probably the name
 				# of a repository. Act accordingly.
-				_dispatch git git
+				# FIXME: this may want to use '_dispatch vcsh git'
+				GIT_DIR=$VCSH_REPO_D/$words[1].git _dispatch git git
 			else
 				curcontext="${curcontext%:*:*}:vcsh-${vcshcommand}:"
-				_call_function ret _vcsh-${vcshcommand}
+				_call_function ret _vcsh-${vcshcommand} && (( ret ))
 			fi
 		fi
 	fi
diff --git a/_vcsh_bash b/_vcsh_bash
new file mode 100644
index 0000000000000000000000000000000000000000..3cfe88a6ace6594bebc9ce983a34a4c1dd1f4ea5
--- /dev/null
+++ b/_vcsh_bash
@@ -0,0 +1,138 @@
+# bash completion for vcsh.
+
+# run git command
+#   based on bash_completion:_command_offset()
+_vcsh_git_command () {
+	local word_offset=$1
+	for (( i=0; i < $word_offset; i++ )); do
+		for (( j=0; j <= ${#COMP_LINE}; j++ )); do
+			[[ "$COMP_LINE" == "${COMP_WORDS[i]}"* ]] && break
+			COMP_LINE=${COMP_LINE:1}
+			((COMP_POINT--))
+		done
+		COMP_LINE=${COMP_LINE#"${COMP_WORDS[i]}"}
+		((COMP_POINT-=${#COMP_WORDS[i]}))
+	done
+	COMP_LINE="git $COMP_LINE"
+	((COMP_POINT+=4))
+
+	# shift COMP_WORDS elements and adjust COMP_CWORD
+	for (( i=1; i <= COMP_CWORD - $word_offset + 1; i++ )); do
+		COMP_WORDS[i]=${COMP_WORDS[i+$word_offset-1]}
+	done
+	for (( i; i <= COMP_CWORD; i++ )); do
+		unset 'COMP_WORDS[i]'
+	done
+	COMP_WORDS[0]=git
+	((COMP_CWORD -= $word_offset - 1))
+
+	local cspec=$( complete -p git 2>/dev/null )
+	if [[ -n $cspec ]]; then
+		if [[ ${cspec#* -F } != $cspec ]]; then
+			local func=${cspec#*-F }
+			func=${func%% *}
+
+			if [[ ${#COMP_WORDS[@]} -ge 2 ]]; then
+				$func git "${COMP_WORDS[${#COMP_WORDS[@]}-1]}" "${COMP_WORDS[${#COMP_WORDS[@]}-2]}"
+			else
+				$func git "${COMP_WORDS[${#COMP_WORDS[@]}-1]}"
+			fi
+
+			# restore initial compopts
+			local opt
+			while [[ $cspec == *" -o "* ]]; do
+				# FIXME: should we take "+o opt" into account?
+				cspec=${cspec#*-o }
+				opt=${cspec%% *}
+				compopt -o $opt
+				cspec=${cspec#$opt}
+			done
+		fi
+	fi
+}
+
+_vcsh () {
+	local cur prev words cword OPTS
+	_init_completion -n = || return
+
+	local repos cmds
+	repos=( $(command vcsh list) )
+	cmds="clone delete enter foreach help init list list-tracked list-untracked
+		  pull push rename run status upgrade version which write-gitignore"
+
+	local subcword cmd subcmd
+	for (( subcword=1; subcword < ${#words[@]}-1; subcword++ )); do
+		[[ -n $cmd && ${words[subcword]} != -* ]] && subcmd=${words[subcword]} && break
+		[[ ${words[subcword]} != -* ]] && cmd=${words[subcword]}
+	done
+
+	if [[ -z $cmd ]]; then
+		case $prev in
+			-c)
+				COMPREPLY=( $(compgen -f -- $cur) )
+				return
+				;;
+		esac
+
+		case $cur in
+			-*)
+				OPTS='-c -d -h -v'
+				COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
+				return
+				;;
+		esac
+		COMPREPLY=( $(compgen -W "${repos[*]} ${cmds[*]}" -- $cur) )
+		return 0
+	fi
+
+	case $cmd in
+		help|init|list|pull|push|version|which)
+			return
+			;;
+
+		list-untracked)
+			[[ $cur == -* ]] && \
+				COMPREPLY=( $(compgen -W '-a -r' -- $cur) ) && return
+			;;&
+
+		run)
+			if [[ -n $subcmd && -n "${repos[$subcmd]}" ]]; then
+				_command_offset $(( $subcword+1 ))
+				return
+			fi
+			;;&
+
+		delete|enter|list-tracked|list-untracked|rename|run|status|upgrade|write-gitignore)
+			# return repos
+			if [[ -z $subcmd ]]; then
+				COMPREPLY=( $(compgen -W "${repos[*]}" -- $cur) )
+				return
+			fi
+			return
+			;;
+
+		clone)
+			[[ $cur == -* ]] && \
+				COMPREPLY=( $(compgen -W '-b' -- $cur) )
+			return
+			;;
+
+		foreach)
+			[[ $cur == -* ]] \
+				&& COMPREPLY=( $(compgen -W "-g" -- $cur) ) && return
+			_vcsh_git_command $subcword
+			return
+			;;
+
+	esac
+
+	# git command on repository
+	if [[ -n "${repos[$cmd]}" ]]; then
+		_vcsh_git_command $subcword
+	fi
+	return 0
+}
+
+complete -F _vcsh vcsh
+
+# vim: ft=sh:
diff --git a/changelog b/changelog
index 9cc6c16f689d3fc74ccd257ade7a89b76d101133..5e657e04e270aa62a324bcab791202439fe21856 100644
--- a/changelog
+++ b/changelog
@@ -201,7 +201,7 @@
 2011-11-19  Richard Hartmann <richih.mailinglist@gmail.com>
 
 	* Bugfixes
-	* Improve XDG compability
+	* Improve XDG compatibility
 
 2011-11-18  Richard Hartmann <richih.mailinglist@gmail.com>
 
diff --git a/doc/README.md b/doc/README.md
index b14d782e50a428afc403d56a0ac37594949b34b3..8b9b24464ad4e206bd48c107981753831de51b56 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -293,7 +293,7 @@ Note the portage package for myrepos still has the old project name:
 
 vcsh is available via this [AUR](https://aur.archlinux.org/packages/vcsh/)
 package. Likewise myrepos is available [here](https://aur.archlinux.org/packages/myrepos/).
-You may install both useing your favorite AUR helper. e.g. with yaourt:
+You may install both using your favorite AUR helper. e.g. with yaourt:
 
     yaourt -Sya myrepos vcsh
 
diff --git a/doc/vcsh.1.ronn b/doc/vcsh.1.ronn
index d5ee0dc53861d6346b74a64e8932d6e97c375076..6e19ef8be41e4d078f2e90c998598cdc03732c39 100644
--- a/doc/vcsh.1.ronn
+++ b/doc/vcsh.1.ronn
@@ -41,7 +41,7 @@ vcsh(1) - Version Control System for $HOME - multiple Git repositories in $HOME
 
 `vcsh` write-gitignore <repo>
 
-`vcsh` <repo> <git command>
+`vcsh` <repo> <gitcommand>
 
 `vcsh` <repo>
 
@@ -98,7 +98,7 @@ an interactive user.
   Delete an existing repository.
 
 * enter:
-  Enter repository; spawn new <$SHELL>.
+  Enter repository; spawn new <$SHELL> with <$GIT_DIR> set.
 
 * foreach:
   Execute git command for every vcsh repository.
@@ -173,7 +173,7 @@ an interactive user.
   Write .gitignore.d/<repo> via `git ls-files`.
 
 * <repo> <gitcommand>:
-  Shortcut to run `vcsh` on a repo. Will prepend `git` to <command>.
+  Shortcut to run `git` commands on a repo. Will prepend `git` to <gitcommand>.
 
 * <repo>:
   Shortcut to run `vcsh enter <repo>`.
@@ -220,7 +220,7 @@ Interesting knobs you can turn:
 
   Defaults to <exact>.
 
-* <$VCSH_VCSH_WORKTREE>:
+* <$VCSH_WORKTREE>:
   Can be <absolute>, or <relative>.
 
   <absolute> will set an absolute path; defaulting to <$HOME>.
@@ -338,7 +338,7 @@ config files, all of which were soft-linked into <$HOME>.
 Martin F. Krafft aka madduck came up with the concept of fake bare Git
 repositories.
 
-vcsh was initally written by madduck. This version is a re-implementation from
+vcsh was initially written by madduck. This version is a re-implementation from
 scratch with a lot more features. madduck graciously agreed to let the author
 take over the name.
 
diff --git a/t/100-init.t b/t/100-init.t
index 74facc02655ba9db6b733c754d6685b61d43813d..15ce922f0e32040dae47cb795653567b8a90a2b7 100644
--- a/t/100-init.t
+++ b/t/100-init.t
@@ -18,7 +18,7 @@ ok $output eq "", 'No repos set up yet.';
 
 $output = `./vcsh init test1`;
 
-ok $output eq "Initialized empty shared Git repository in " . $ENV{'HOME'} . "/.config/vcsh/repo.d/test1.git/\n";
+ok $output eq "Initialized empty Git repository in " . $ENV{'HOME'} . "/.config/vcsh/repo.d/test1.git/\n";
 
 $output = `./vcsh status`;
 
diff --git a/vcsh b/vcsh
index d38857e1a17539dcc59fd7c71dc3f78e58f6fae6..47d8972cc055ac24fd7ded9fa1f61d5f6fe979a7 100755
--- a/vcsh
+++ b/vcsh
@@ -22,6 +22,9 @@
 VERSION='1.20141026'
 SELF=$(basename $0)
 
+# Ensure all files created are accessible only to the current user.
+umask 0077
+
 fatal() {
 	echo "$SELF: fatal: $1" >&2
 	[ -z $2 ] && exit 1
@@ -106,6 +109,7 @@ help() {
    commit               Commit in all repositories
    delete <repo>        Delete an existing repository
    enter <repo>         Enter repository; spawn new instance of \$SHELL
+                        with \$GIT_DIR set.
    foreach [<-g>]
      <git command>      Execute a command for every repository
    help                 Display this help text
@@ -162,7 +166,7 @@ clone() {
   You should add files to your new repository."
 		exit
 	fi
-	GIT_VERSION_MAJOR=$(git --version | sed -n 's/.* \([0-9]\)\..*/\1/p' )
+	GIT_VERSION_MAJOR=$(git --version | sed -E -n 's/.* ([0-9]+)\..*/\1/p' )
 	if [ 1 -lt "$GIT_VERSION_MAJOR" ];then
 		git fetch origin "$VCSH_BRANCH"
 	else
@@ -186,11 +190,12 @@ clone() {
 
 commit() {
 	hook pre-commit
+        shift  # remove the "commit" command.
 	for VCSH_REPO_NAME in $(list); do
 		echo "$VCSH_REPO_NAME: "
 		GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
 		use
-		git commit --untracked-files=no --quiet $@
+		git commit --untracked-files=no --quiet "$@"
 		VCSH_COMMAND_RETURN_CODE=$?
 		echo
 	done
@@ -261,7 +266,7 @@ init() {
 	[ ! -e "$GIT_DIR" ] || fatal "'$GIT_DIR' exists" 10
 	mkdir -p "$VCSH_BASE" || fatal "could not create '$VCSH_BASE'" 50
 	cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
-	git init --shared=0600
+	git init --shared=false
 	upgrade
 	hook post-init
 }
@@ -274,7 +279,7 @@ list() {
 
 get_files() {
 	GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
-	git ls-files
+	git ls-files --full-name
 }
 
 list_tracked() {
@@ -477,8 +482,8 @@ write_gitignore() {
 	use
 	cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11
 	local GIT_VERSION="$(git --version)"
-	local GIT_VERSION_MAJOR=$(echo $GIT_VERSION | sed -n 's/.* \([0-9]\)\..*/\1/p')
-	local GIT_VERSION_MINOR=$(echo $GIT_VERSION | sed -n 's/.* \([0-9]\)\.\([0-9]\)\..*/\2/p')
+	local GIT_VERSION_MAJOR=$(echo $GIT_VERSION | sed -E -n 's/.* ([0-9]+)\..*/\1/p')
+	local GIT_VERSION_MINOR=$(echo $GIT_VERSION | sed -E -n 's/.* ([0-9]+)\.([0-9]+)\..*/\2/p')
 	OLDIFS=$IFS
 	IFS=$(printf '\n\t')
 	gitignores=$(for file in $(git ls-files); do