diff --git a/_vcsh b/_vcsh
index 73a04a59dcb16d8451d8d941eaf311f2fcaac18a..3022c6125d6a4a82db9697ee76a5f7c229d12f95 100644
--- a/_vcsh
+++ b/_vcsh
@@ -23,6 +23,10 @@ function _vcsh-enter () {
 	(( CURRENT == 2 )) && __vcsh_repositories
 }
 
+function _vcsh-foreach () {
+	_dispatch git git
+}
+
 function _vcsh-help () {
 	_nothing
 }
@@ -96,6 +100,7 @@ function _vcsh () {
 		"commit:commit in all repositories"
 		"delete:delete an existing repository"
 		"enter:enter repository; spawn new <\$SHELL>"
+		"foreach:execute for all repos"
 		"help:display help"
 		"init:initialize an empty repository"
 		"list:list all local vcsh repositories"
diff --git a/doc/vcsh.1.ronn b/doc/vcsh.1.ronn
index 2bf8a77ebd8c394d58a402e78b916af39ed4d197..d5ee0dc53861d6346b74a64e8932d6e97c375076 100644
--- a/doc/vcsh.1.ronn
+++ b/doc/vcsh.1.ronn
@@ -11,6 +11,8 @@ vcsh(1) - Version Control System for $HOME - multiple Git repositories in $HOME
 
 `vcsh` enter <repo>
 
+`vcsh` foreach [-g] <git command>
+
 `vcsh` help
 
 `vcsh` init <repo>
@@ -98,6 +100,11 @@ an interactive user.
 * enter:
   Enter repository; spawn new <$SHELL>.
 
+* foreach:
+  Execute git command for every vcsh repository.
+
+  `-g`: Execute in general context.
+
 * help:
   Display help.
 
diff --git a/vcsh b/vcsh
index afed6eb33abbcc16885365cd560095a3eeb94a9d..06f6c331edabbdc6ea2360f54e84ea0d65bf768e 100755
--- a/vcsh
+++ b/vcsh
@@ -106,6 +106,8 @@ help() {
    commit               Commit in all repositories
    delete <repo>        Delete an existing repository
    enter <repo>         Enter repository; spawn new instance of \$SHELL
+   foreach [<-g>]
+     <git command>      Execute a command for every repository
    help                 Display this help text
    init <repo>          Initialize a new repository
    list                 List all repositories
@@ -216,6 +218,27 @@ enter() {
 	hook post-enter
 }
 
+foreach() {
+	hook pre-foreach
+
+	# We default to prefixing `git` to all commands passed to foreach, but
+	# allow running in general context with -g
+	command_prefix=git
+	while getopts "g" flag; do
+		if [ x"$1" = x'-g' ]; then
+			unset command_prefix
+		fi
+		shift 1
+	done
+	for VCSH_REPO_NAME in $(list); do
+		echo "$VCSH_REPO_NAME:"
+		GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR
+		use
+		$command_prefix "$@"
+	done
+	hook post-foreach
+}
+
 git_dir_exists() {
 	[ -d "$GIT_DIR" ] || fatal "no repository found for '$VCSH_REPO_NAME'" 12
 }
@@ -558,6 +581,9 @@ elif [ x"$VCSH_COMMAND" = x'delete' ]           ||
 	[ x"$VCSH_COMMAND" = x'rename' ] && { VCSH_REPO_NAME_NEW=$3; export VCSH_REPO_NAME_NEW;
 	                                      GIT_DIR_NEW=$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git; export GIT_DIR_NEW; }
 	[ x"$VCSH_COMMAND" = x'run' ]    && shift 2
+elif [ x"$VCSH_COMMAND" = x'foreach' ]; then
+	[ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a command" 1
+	shift 1
 elif [ x"$VCSH_COMMAND" = x'commit' ] ||
      [ x"$VCSH_COMMAND" = x'list'   ] ||
      [ x"$VCSH_COMMAND" = x'list-tracked' ] ||