diff --git a/doc/vcsh.1.ronn b/doc/vcsh.1.ronn
index 9e37ca131314f2e648278e35d0366c9c94439855..bcea71cf4247e63e558f2b5ffc3c23708783c1f1 100644
--- a/doc/vcsh.1.ronn
+++ b/doc/vcsh.1.ronn
@@ -111,6 +111,24 @@ an interactive user.
 As noted earlier, `vcsh` will set <$GIT_DIR> and <$GIT_WORK_TREE> to the
 appropriate values for fake bare git repositories.
 
+## HOOK SYSTEM
+
+`vcsh` provides a hook system. Hook scripts must be executable and should be
+placed in <$XDG_CONFIG_HOME/vcsh/hooks-available>. From there, they can be
+soft-linked into <$XDG_CONFIG_HOME/vcsh/hooks-enabled>; `vcsh` will only
+execute hooks that are in this directory.
+
+Hooks follow a simple format. `pre-run` will be run before anything is run.
+If you want to have more than one script for a certain hook, just append
+any kind of string to order them. A system of `pre-run`, `pre-run.10`,
+`pre-run.20` etc is suggested; other options would be `pre-run-10` or
+`pre-run.sh`. A dot after the hook name is optional.
+
+If you want to create hooks for a specific `vcsh` repository, simply prepend
+the repository's name, followed by a dot, i.e. `zsh.pre-run`. Otherwise, the
+same rules as above apply. The dot between the repository's name and the hook
+is mandatory, though.
+
 ## DETAILED HOWTO AND FURTHER READING
 
 Man pages are intended to be short and thus often useless to glean best
diff --git a/vcsh b/vcsh
index 52afbe174348bdc1d6f1b78baa9136cb73a8cdbc..4732e467662bd35ef217abb3269c5d8a518e9b58 100755
--- a/vcsh
+++ b/vcsh
@@ -11,6 +11,7 @@
 [ -r "$XDG_CONFIG_HOME/vcsh/config" ] && . "$XDG_CONFIG_HOME/vcsh/config"
 [ -n "$VCSH_DEBUG" ]                  && set -vx
 [ -z "$VCSH_REPO_D" ]                 && VCSH_REPO_D="$XDG_CONFIG_HOME/vcsh/repo.d"
+[ -z "$VCSH_HOOK_D" ]                 && VCSH_HOOK_D="$XDG_CONFIG_HOME/vcsh/hooks-enabled"
 [ -z "$VCSH_BASE" ]                   && VCSH_BASE="$HOME"
 [ -z "$VCSH_GITIGNORE" ]              && VCSH_GITIGNORE='exact'
 
@@ -103,14 +104,23 @@ To continue, type \"Yes, do as I say\""
 }
 
 enter() {
+	hook pre-enter
 	use
 	$SHELL
+	hook post-enter
 }
 
 git_dir_exists() {
 	[ -d "$GIT_DIR" ] || fatal "no repository found for '$VCSH_REPO_NAME'" 12
 }
 
+hook() {
+	for hook in $VCSH_HOOK_D/$1* $VCSH_HOOK_D/$VCSH_REPO_NAME.$1*; do
+		[ -x "$hook" ] || continue
+		"$hook"
+	done
+}
+
 init() {
 	[ ! -e "$GIT_DIR" ] || fatal "'$GIT_DIR' exists" 10
 	export GIT_WORK_TREE="$VCSH_BASE"
@@ -150,16 +160,20 @@ rename() {
 }
 
 run() {
+	hook pre-run
 	use
 	$VCSH_EXTERNAL_COMMAND
+	hook post-run
 }
 
 setup() {
+	hook pre-setup
 	use
 	git config core.worktree     "$GIT_WORK_TREE"
 	git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
 	git config vcsh.vcsh         'true'
 	[ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME"
+	hook post-setup
 }
 
 use() {