branch: elpa/magit
commit 72d95d60dd67e7fd9e16f912d956d324827ba7fa
Author: Jonas Bernoulli <[email protected]>
Commit: Jonas Bernoulli <[email protected]>

    Use new hook.*.{event,command} variables to call elisp from githooks
    
    After waiting for such functionality for two decades, I came to the
    conclusion that this will never be implemented, and finally added a
    noisy kludge to get the same functionality anyway.  Of course, just
    one release later, upstream adds the missing functionality.
    
    Zap the noise, and instead use the new variables.  A consequence of
    this is that our functionality that depends on githooks can only work
    when using Git v2.54.0 or later.
---
 docs/magit.org                  |  81 ++++++++++++++++++++++
 docs/magit.texi                 | 108 +++++++++++++++++++++++++++++
 git-hooks/applypatch-msg        |   1 -
 git-hooks/commit-msg            |   1 -
 git-hooks/fallthrough           |   8 ---
 git-hooks/fsmonitor-watchman    |   1 -
 git-hooks/p4-changelist         |   1 -
 git-hooks/p4-post-changelist    |   1 -
 git-hooks/p4-pre-submit         |   1 -
 git-hooks/p4-prepare-changelist |   1 -
 git-hooks/post-applypatch       |   1 -
 git-hooks/post-checkout         |   1 -
 git-hooks/post-commit           |  13 ----
 git-hooks/post-index-change     |   1 -
 git-hooks/post-merge            |  13 ----
 git-hooks/post-receive          |   1 -
 git-hooks/post-rewrite          |  13 ----
 git-hooks/post-update           |   1 -
 git-hooks/pre-applypatch        |   1 -
 git-hooks/pre-auto-gc           |   1 -
 git-hooks/pre-commit            |   1 -
 git-hooks/pre-merge-commit      |   1 -
 git-hooks/pre-push              |   1 -
 git-hooks/pre-rebase            |   1 -
 git-hooks/pre-receive           |   1 -
 git-hooks/prepare-commit-msg    |   1 -
 git-hooks/proc-receive          |   1 -
 git-hooks/push-to-checkout      |   1 -
 git-hooks/reference-transaction |   1 -
 git-hooks/sendemail-validate    |   1 -
 git-hooks/update                |   1 -
 githooks/config                 |   6 ++
 githooks/magit-run-git-hook     |   8 +++
 lisp/magit-commit.el            |  12 ++--
 lisp/magit-git.el               | 148 ++++++++++++++++++++++------------------
 lisp/magit-mode.el              |   2 +-
 lisp/magit-process.el           |  18 ++---
 lisp/magit-wip.el               |   5 +-
 38 files changed, 300 insertions(+), 160 deletions(-)

diff --git a/docs/magit.org b/docs/magit.org
index 2e566dfb5a..28aed472cf 100644
--- a/docs/magit.org
+++ b/docs/magit.org
@@ -2049,6 +2049,87 @@ Emacs is searching for ~git~.
   are doing.  And think very hard before adding something; it will be
   used every time Magit runs Git for any purpose.
 
+*** Mapping Git Hooks to Lisp Hooks
+
+Git hooks can be configured to call Lisp hooks, starting with Git
+v2.54.0.  The mechanism described here is only used when ~git~ is
+invoked by Magit, and ~git~ is run asynchronously and on the local
+machine.
+
+- User option: magit-run-hooks-from-githooks ::
+
+  This option controls whether Git hooks may run Lisp hooks.
+
+  Magit only defines one such hook mapping, but users can add more.
+
+- User option: magit-user-githook-file ::
+
+  This option controls the location of a user-editable file containing
+  additional hook mappings.
+
+- Variable: magit-githook-directory ::
+
+  The value of this variable is the directory containing files Magit
+  needs to map Git hooks to Lisp hooks.
+
+  This directory must contains two files; ~config~, which maps Git
+  hooks to the Lisp hook ~magit-common-git-post-commit-functions~,
+  and an executable ~magit-run-git-hook~, which is used in that, and
+  potentially other, hook mappings.
+
+  The value of this variable is set by ~magit-process-git-arguments~,
+  when it is first needed.  Users can set it to another directory,
+  but that should rarely be necessary.  Additional hook mappings
+  should instead be defined in ~magit-user-githook-file~.
+
+- Function: magit-run-git-hook (hook &rest args) ::
+
+  This function runs the Lisp hook HOOK with the specified arguments
+  ARGS.  ARGS are the arguments (a possibly empty list of strings),
+  which the Git hook was called with.  HOOK is a string naming a hook.
+
+  This function is only intended to be called via ~emacsclient~, by an
+  executable by the same name, which in turn is only intended to be
+  called by Git hooks.
+
+- Variable: magit-common-git-post-commit-functions ::
+
+  This Lisp hook is run by the Git hooks ~post-commit~, ~post-merge~ and
+  ~post-rewrite~.  There is not a single Git hook, which is called after
+  a commit is created; to achieve that, all of these hooks have to be
+  used.
+
+Git hooks are documented in [[man:githooks]] Also see [[man:git-hook]] Using
+~magit-common-git-post-commit-functions~ as an example, the following
+is how Magit gets Git to map Git hooks to Lisp hooks.
+
+Iff Magit calls Git asynchronously and on the local machine, it
+injects the global arguments ~-c include.path=/path/to/githooks/config~.
+That file contains:
+
+#+begin_src conf-mode
+  [hook "magit-common-post-commit"]
+    event = post-commit
+    event = post-merge
+    event = post-rewrite
+    command = magit-run-git-hook magit-common-git-post-commit-functions
+#+end_src
+
+Consequently when one of the Git hooks ~post-commit~, ~post-merge~ or
+~post-rewrite~ is triggered, that causes the ~magit-run-git-hook~
+executable to be called with ~magit-common-git-post-commit-functions~
+as the first argument.
+
+That executable used the ~emacsclient~ to call a Lisp function by the
+same name, which takes the name of a Lisp hook as the first argument.
+The executable may receive additional arguments from the Git hook,
+which it passes on to the function.  All arguments are strings.
+
+The function ~magit-run-git-hook~ runs the Lisp hook using
+~run-hook-with-args~, passing along all arguments, starting with the
+second.  It also arranges for ~message~ to output to standard output,
+so that messages appear in Magit's process buffer.
+
 * Inspecting
 
 The functionality provided by Magit can be roughly divided into three
diff --git a/docs/magit.texi b/docs/magit.texi
index 880e789ab4..e6bed2e1f9 100644
--- a/docs/magit.texi
+++ b/docs/magit.texi
@@ -2406,6 +2406,114 @@ are doing.  And think very hard before adding 
something; it will be
 used every time Magit runs Git for any purpose.
 @end defopt
 
+@anchor{Mapping Git Hooks to Lisp Hooks}
+@subsection Mapping Git Hooks to Lisp Hooks
+
+Git hooks can be configured to call Lisp hooks, starting with Git
+v2.54.0.  The mechanism described here is only used when @code{git} is
+invoked by Magit, and @code{git} is run asynchronously and on the local
+machine.
+
+@table @asis
+@item @kbd{magit-run-hooks-from-githooks}
+@kindex magit-run-hooks-from-githooks
+This option controls whether Git hooks may run Lisp hooks.
+
+Magit only defines one such hook mapping, but users can add more.
+
+@item @kbd{magit-user-githook-file}
+@kindex magit-user-githook-file
+This option controls the location of a user-editable file containing
+additional hook mappings.
+@end table
+
+@defvar magit-githook-directory
+The value of this variable is the directory containing files Magit
+needs to map Git hooks to Lisp hooks.
+
+This directory must contains two files; @code{config}, which maps Git
+hooks to the Lisp hook @code{magit-common-git-post-commit-functions},
+and an executable @code{magit-run-git-hook}, which is used in that, and
+potentially other, hook mappings.
+
+The value of this variable is set by @code{magit-process-git-arguments},
+when it is first needed.  Users can set it to another directory,
+but that should rarely be necessary.  Additional hook mappings
+should instead be defined in @code{magit-user-githook-file}.
+@end defvar
+
+@defun magit-run-git-hook (hook &rest args)
+This function runs the Lisp hook HOOK with the specified arguments
+ARGS@.  ARGS are the arguments (a possibly empty list of strings),
+which the Git hook was called with.  HOOK is a string naming a hook.
+
+This function is only intended to be called via @code{emacsclient}, by an
+executable by the same name, which in turn is only intended to be
+called by Git hooks.
+@end defun
+
+@defvar magit-common-git-post-commit-functions
+This Lisp hook is run by the Git hooks @code{post-commit}, @code{post-merge} 
and
+@code{post-rewrite}.  There is not a single Git hook, which is called after
+a commit is created; to achieve that, all of these hooks have to be
+used.
+@end defvar
+
+Git hooks are documented in 
+@ifinfo
+@ref{githooks,,,gitman,}.
+@end ifinfo
+@ifhtml
+@html
+the <a href="http://git-scm.com/docs/githooks";>githooks(1)</a> manpage.
+@end html
+@end ifhtml
+@iftex
+the githooks(1) manpage.
+@end iftex
+ Also see 
+@ifinfo
+@ref{git-hook,,,gitman,}.
+@end ifinfo
+@ifhtml
+@html
+the <a href="http://git-scm.com/docs/git-hook";>git-hook(1)</a> manpage.
+@end html
+@end ifhtml
+@iftex
+the git-hook(1) manpage.
+@end iftex
+ Using
+@code{magit-common-git-post-commit-functions} as an example, the following
+is how Magit gets Git to map Git hooks to Lisp hooks.
+
+Iff Magit calls Git asynchronously and on the local machine, it
+injects the global arguments @code{-c include.path=/path/to/githooks/config}.
+That file contains:
+
+@example
+[hook "magit-common-post-commit"]
+  event = post-commit
+  event = post-merge
+  event = post-rewrite
+  command = magit-run-git-hook magit-common-git-post-commit-functions
+@end example
+
+Consequently when one of the Git hooks @code{post-commit}, @code{post-merge} or
+@code{post-rewrite} is triggered, that causes the @code{magit-run-git-hook}
+executable to be called with @code{magit-common-git-post-commit-functions}
+as the first argument.
+
+That executable used the @code{emacsclient} to call a Lisp function by the
+same name, which takes the name of a Lisp hook as the first argument.
+The executable may receive additional arguments from the Git hook,
+which it passes on to the function.  All arguments are strings.
+
+The function @code{magit-run-git-hook} runs the Lisp hook using
+@code{run-hook-with-args}, passing along all arguments, starting with the
+second.  It also arranges for @code{message} to output to standard output,
+so that messages appear in Magit's process buffer.
+
 @node Inspecting
 @chapter Inspecting
 
diff --git a/git-hooks/applypatch-msg b/git-hooks/applypatch-msg
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/applypatch-msg
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/commit-msg b/git-hooks/commit-msg
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/commit-msg
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/fallthrough b/git-hooks/fallthrough
deleted file mode 100755
index 51928e7f3d..0000000000
--- a/git-hooks/fallthrough
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-
-hook="$SHADOWED_GITHOOK_DIRECTORY/$(basename $0)"
-
-if [[ -x "$hook" ]]
-then
-    "$hook" "$@"
-fi
diff --git a/git-hooks/fsmonitor-watchman b/git-hooks/fsmonitor-watchman
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/fsmonitor-watchman
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/p4-changelist b/git-hooks/p4-changelist
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/p4-changelist
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/p4-post-changelist b/git-hooks/p4-post-changelist
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/p4-post-changelist
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/p4-pre-submit b/git-hooks/p4-pre-submit
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/p4-pre-submit
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/p4-prepare-changelist b/git-hooks/p4-prepare-changelist
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/p4-prepare-changelist
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/post-applypatch b/git-hooks/post-applypatch
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/post-applypatch
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/post-checkout b/git-hooks/post-checkout
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/post-checkout
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/post-commit b/git-hooks/post-commit
deleted file mode 100755
index e02e5e7c3f..0000000000
--- a/git-hooks/post-commit
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env bash
-
-if [[ "$INSIDE_EMACS" == *magit ]]
-then
-    for arg in "$@"; do args+="\"$arg\""; done
-    $GIT_EDITOR --eval \
-      "(magit-run-git-hook 'common-post-commit ${args[@]})"
-fi
-
-if [[ -x "$SHADOWED_GITHOOK_DIRECTORY" ]]
-then
-    "$SHADOWED_GITHOOK_DIRECTORY/$(basename $0)" "$@"
-fi
diff --git a/git-hooks/post-index-change b/git-hooks/post-index-change
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/post-index-change
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/post-merge b/git-hooks/post-merge
deleted file mode 100755
index e02e5e7c3f..0000000000
--- a/git-hooks/post-merge
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env bash
-
-if [[ "$INSIDE_EMACS" == *magit ]]
-then
-    for arg in "$@"; do args+="\"$arg\""; done
-    $GIT_EDITOR --eval \
-      "(magit-run-git-hook 'common-post-commit ${args[@]})"
-fi
-
-if [[ -x "$SHADOWED_GITHOOK_DIRECTORY" ]]
-then
-    "$SHADOWED_GITHOOK_DIRECTORY/$(basename $0)" "$@"
-fi
diff --git a/git-hooks/post-receive b/git-hooks/post-receive
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/post-receive
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/post-rewrite b/git-hooks/post-rewrite
deleted file mode 100755
index e02e5e7c3f..0000000000
--- a/git-hooks/post-rewrite
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env bash
-
-if [[ "$INSIDE_EMACS" == *magit ]]
-then
-    for arg in "$@"; do args+="\"$arg\""; done
-    $GIT_EDITOR --eval \
-      "(magit-run-git-hook 'common-post-commit ${args[@]})"
-fi
-
-if [[ -x "$SHADOWED_GITHOOK_DIRECTORY" ]]
-then
-    "$SHADOWED_GITHOOK_DIRECTORY/$(basename $0)" "$@"
-fi
diff --git a/git-hooks/post-update b/git-hooks/post-update
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/post-update
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/pre-applypatch b/git-hooks/pre-applypatch
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/pre-applypatch
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/pre-auto-gc b/git-hooks/pre-auto-gc
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/pre-auto-gc
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/pre-commit
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/pre-merge-commit b/git-hooks/pre-merge-commit
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/pre-merge-commit
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/pre-push b/git-hooks/pre-push
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/pre-push
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/pre-rebase b/git-hooks/pre-rebase
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/pre-rebase
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/pre-receive b/git-hooks/pre-receive
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/pre-receive
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/prepare-commit-msg b/git-hooks/prepare-commit-msg
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/prepare-commit-msg
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/proc-receive b/git-hooks/proc-receive
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/proc-receive
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/push-to-checkout b/git-hooks/push-to-checkout
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/push-to-checkout
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/reference-transaction b/git-hooks/reference-transaction
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/reference-transaction
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/sendemail-validate b/git-hooks/sendemail-validate
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/sendemail-validate
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/git-hooks/update b/git-hooks/update
deleted file mode 120000
index 833950e79b..0000000000
--- a/git-hooks/update
+++ /dev/null
@@ -1 +0,0 @@
-fallthrough
\ No newline at end of file
diff --git a/githooks/config b/githooks/config
new file mode 100644
index 0000000000..cc44e82b41
--- /dev/null
+++ b/githooks/config
@@ -0,0 +1,6 @@
+# -*- mode: gitconfig -*-
+[hook "magit-common-post-commit"]
+        event = post-commit
+        event = post-merge
+        event = post-rewrite
+        command = magit-run-git-hook magit-common-git-post-commit-functions
diff --git a/githooks/magit-run-git-hook b/githooks/magit-run-git-hook
new file mode 100755
index 0000000000..228621635b
--- /dev/null
+++ b/githooks/magit-run-git-hook
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+if [[ "$INSIDE_EMACS" == *magit ]]
+then
+    for arg in "$@"; do args+="\"$arg\""; done
+    $GIT_EDITOR --eval "(magit-run-git-hook ${args[@]})"
+else
+    true
+fi
diff --git a/lisp/magit-commit.el b/lisp/magit-commit.el
index 6d1c1541ec..7f7899a0f4 100644
--- a/lisp/magit-commit.el
+++ b/lisp/magit-commit.el
@@ -102,10 +102,14 @@ Also see https://github.com/magit/magit/issues/4132.";
 (defvar magit-common-git-post-commit-functions nil
   "Hook run by Git hooks `post-commit', `post-merge' and `post-rewrite'.
 
-This hook is run if `magit-overriding-githook-directory' is non-nil.
-The functions are called with the same arguments as the Git hook.
-
-This hook is still experimental.")
+There is not a single Git hook, which is called after a commit is
+created; to achieve that, all of these Git hooks have to be used.
+
+Git hooks are documented in the githooks(5) manpage.  This Lisp hook
+is only run if `magit-run-hooks-from-githooks' is non-nil, and Magit
+runs Git asynchronously and on the local machine.  The hook functions
+are called with the same arguments as the Git hook; see the mentioned
+manpage for details.")
 
 ;;; Popup
 
diff --git a/lisp/magit-git.el b/lisp/magit-git.el
index b340f98fbf..9a302b9c92 100644
--- a/lisp/magit-git.el
+++ b/lisp/magit-git.el
@@ -155,48 +155,57 @@ option."
   :group 'magit-process
   :type 'string)
 
-(defvar magit--githook-directory nil)
+(defcustom magit-run-hooks-from-githooks t
+  "Whether Git hooks may run Lisp hooks.
 
-(defcustom magit-githook-directory nil
-  "Directory containing the Git hook scripts used by Magit.
+By default the Lisp hook `magit-common-git-post-commit-functions' is
+run by the Git hooks `post-commit', `post-merge' and `post-rewrite'.
+Use `magit-user-githook-file' (which see) to define additional hooks.
 
-No Magit-specific Git hook scripts are used if this is nil, which it
-is the default.  This feature is still experimental.
-
-Git does not allow overriding just an individual hook.  It is only
-possible to point Git at an alternative directory containing hook
-scripts, using the Git variable `core.hooksPath'.  When doing that,
-the hooks located in `$GIT_DIR/hooks' are ignored.
+Git hooks can only run Lisp hooks, if Magit invokes Git asynchronously
+and on the local machine, and at least Git v2.54.0 is used."
+  :package-version '(magit . "4.6.0")
+  :group 'magit-process
+  :type 'boolean)
 
-If `magit', use the directory containing Git hook scripts distributed
-with Magit.  To counteract Git's limited granularity, Magit provides a
-script for every Git hook, most of which only run the respective script
-located in `$GIT_DIR/hooks', provided it exists and is executable.
+(defcustom magit-user-githook-file (locate-user-emacs-file "magit-githooks")
+  "File containing user Git hook to Lisp hook mappings.
 
-A few Git hooks additionally run Lisp hooks:
+Magit ships with one such mapping, defined in the included file
+\"githooks/config\", which looks like this:
 
-- `post-commit'  runs `magit-git-post-commit-functions'
-- `post-merge'   runs `magit-git-post-merge-functions'
-- `post-rewrite' runs `magit-git-post-rewrite-functions'
+[hook \"magit-common-post-commit\"]
+  event = post-commit
+  event = post-merge
+  event = post-rewrite
+  command = magit-run-git-hook magit-common-git-post-commit-functions
 
-All of these hooks also run `magit-common-git-post-commit-functions'.
-For many uses this hook variable is more useful than the three above.
+That file should not be edited by users, as those edits would be lost
+when Magit is updated; instead the file specified by this option, has
+to be used, to add additional mappings.
 
-If you want to teach additional Git hooks to run Lisp hooks, you have to
-copy Magit's hook script directory elsewhere, modify the hook scripts in
-question, and point this variable at the used directory.
+See the git-hook(1) and githooks(5) manpages for details about the
+Git part.  The command should always use the \"magit-run-git-hook\"
+executable, which takes the name of the Lisp hook to be run as the
+first argument.
 
-Magit only sets `core.hooksPath' when calling Git asynchronously.  Doing
-the same when calling Git synchronously would cause Git and Magit to wait
-on one another."
-  :package-version '(magit . "4.5.0")
+See also `magit-run-hooks-from-githooks'."
+  :package-version '(magit . "4.6.0")
   :group 'magit-process
-  :set (lambda (symbol value)
-         (set-default-toplevel-value symbol value)
-         (setq magit--githook-directory nil))
-  :type '(choice (const :tag "Do not shadow Git's hook directory" nil)
-                 (const :tag "Use Magit's hook directory" magit)
-                 (directory :tag "Custom directory")))
+  :type 'file)
+
+(defvar magit-githook-directory nil
+  "Directory containing files Magit needs to map Git to Lisp hooks.
+
+This directory must contains two files; \"config\", which maps Git
+hooks to the `magit-common-git-post-commit-functions' hook, and an
+executable \"magit-run-git-hook\", which is used in that, and
+potentially other hooks.
+
+The value of this variable is set by `magit-process-git-arguments',
+when it is first needed.  Users can set it to another directory,
+but that should rarely be necessary.  Additional hook mappings
+should instead be defined in `magit-user-githook-file'.")
 
 (defcustom magit-git-global-arguments
   `("--no-pager" "--literal-pathspecs"
@@ -407,7 +416,7 @@ is remote."
       (let* ((length (length magit-git-global-arguments))
              (global (seq-take args length))
              (local  (seq-drop args length)))
-        (when (equal (car local) "-c")
+        (while (equal (car local) "-c")
           (setq global (append global (seq-take local 2)))
           (setq local (seq-drop local 2)))
         (list global local))
@@ -421,31 +430,38 @@ pass arguments through this function before handing them 
to Git,
 to do the following.
 
 * Prepend `magit-git-global-arguments' to ARGS.
-* If ASYNC is non-nil and `magit-githook-directory' is non-nil
-  and valid, set `core.hooksPath' by adding additional arguments to ARGS.
+* If ASYNC is non-nil, potentially add additional arguments to load
+  the hook configuration in \"/path/to/magit/githooks/config\" and/or
+  `magit-user-githook-file'.
 * Flatten ARGS, removing nil arguments.
 * If `system-type' is `windows-nt', encode ARGS to `w32-ansi-code-page'."
-  (let ((githookp (and async (not (file-remote-p default-directory)))))
+  (let ((githookp (and async
+                       (magit-git-version>= "2.54")
+                       (not (file-remote-p default-directory)))))
     (cond
       ((not githookp))
-      (magit--githook-directory)
-      ((eq magit-githook-directory 'magit)
-       (setq magit--githook-directory
+      (magit-githook-directory)
+      ((and (stringp magit-run-hooks-from-githooks)
+            (file-directory-p magit-run-hooks-from-githooks))
+       (setq magit-githook-directory
              (magit-convert-filename-for-git
-              (expand-file-name "git-hooks"
-                                (locate-dominating-file
-                                 (locate-library "magit.el") "git-hooks")))))
-      ((and magit-githook-directory
-            (file-directory-p magit-githook-directory))
-       (setq magit--githook-directory
+              magit-run-hooks-from-githooks)))
+      (magit-run-hooks-from-githooks
+       (setq magit-githook-directory
              (magit-convert-filename-for-git
-              magit-githook-directory))))
+              (expand-file-name "githooks"
+                                (locate-dominating-file
+                                 (locate-library "magit.el") "githooks"))))))
     (setq args
           (append magit-git-global-arguments
                   (and githookp
-                       magit--githook-directory
-                       (list "-c" (format "core.hooksPath=%s"
-                                          magit--githook-directory)))
+                       magit-githook-directory
+                       `("-c" ,(format "include.path=%s/config"
+                                       magit-githook-directory)
+                         ,@(and magit-user-githook-file
+                                (file-exists-p magit-user-githook-file)
+                                `("-c" ,(concat "include.path="
+                                                magit-user-githook-file)))))
                   (flatten-tree args))))
   (if (and (eq system-type 'windows-nt) (boundp 'w32-ansi-code-page))
       ;; On w32, the process arguments *must* be encoded in the
@@ -3086,21 +3102,23 @@ out.  Only existing branches can be selected."
 
 ;;; Git Hooks
 
-(defun magit-run-git-hook (githook &rest args)
-  (dolist (githook (ensure-list githook))
-    (let* ((githook (symbol-name githook))
-           (hook (save-match-data
-                   (if (string-match "\\`common-" githook)
-                       (intern (format "magit-common-git-%s-functions"
-                                       (substring githook (match-end 0))))
-                     (intern (format "magit-git-%s-functions" githook))))))
-      (when (and (boundp hook)
-                 (symbol-value hook))
-        (magit--client-message "Running %s..." hook)
-        (advice-add 'message :override #'magit--client-message)
-        (unwind-protect (apply #'run-hook-with-args hook args)
-          (advice-remove 'message #'magit--client-message))
-        (magit--client-message "Running %s...done" hook))))
+(defun magit-run-git-hook (hook &rest args)
+  "Run the Lisp HOOK with the specified arguments ARGS.
+
+ARGS are the arguments (a possibly empty list of strings), which
+the Git hook was called with.  HOOK is a string naming a hook.
+
+This function is only intended to be called via \"emacsclient\", by
+an executable by the same name, which in turn is only intended to
+be called by Git hooks."
+  (setq hook (intern hook))
+  (when (and (boundp hook)
+             (symbol-value hook))
+    (magit--client-message "Running %s..." hook)
+    (advice-add 'message :override #'magit--client-message)
+    (unwind-protect (apply #'run-hook-with-args hook args)
+      (advice-remove 'message #'magit--client-message))
+    (magit--client-message "Running %s...done" hook))
   '---) ; Emacsclient insists on printing the value.
 
 (defun magit--client-message (format-string &rest args)
diff --git a/lisp/magit-mode.el b/lisp/magit-mode.el
index 2e81447b65..c068e7f324 100644
--- a/lisp/magit-mode.el
+++ b/lisp/magit-mode.el
@@ -1571,7 +1571,7 @@ mentioned caches completely."
   (cond (all
          (setq magit-repository-local-cache nil)
          (setq magit--host-git-version-cache nil)
-         (setq magit--githook-directory nil)
+         (setq magit-githook-directory nil)
          (dolist (buffer (buffer-list))
            (with-current-buffer buffer
              (when (derived-mode-p 'magit-mode)
diff --git a/lisp/magit-process.el b/lisp/magit-process.el
index 3968c4009f..c35cfe1d3b 100644
--- a/lisp/magit-process.el
+++ b/lisp/magit-process.el
@@ -524,27 +524,17 @@ eol conversion."
         (default-process-coding-system (magit--process-coding-system)))
     (apply #'process-file process infile buffer display args)))
 
-(defvar magit--shadowed-githook-directory nil)
-
-(defun magit--shadowed-githook-directory ()
-  (or magit--shadowed-githook-directory
-      (setq magit--shadowed-githook-directory
-            (let ((magit-git-global-arguments nil))
-              (cl-letf (((symbol-function 'magit-process-environment)
-                         (lambda () process-environment)))
-                (or (magit-get "core.hooksPath")
-                    (expand-file-name "hooks" (magit-gitdir))))))))
-
 (defun magit-process-environment ()
   (cond
     ((file-remote-p default-directory)
      `(,@magit-git-environment
        ,@process-environment))
     (`(,@magit-git-environment
-       ,@(and magit--githook-directory
+       ,@(and magit-githook-directory
               (not (file-remote-p default-directory))
-              (list (concat "SHADOWED_GITHOOK_DIRECTORY="
-                            (magit--shadowed-githook-directory))))
+              (list (format "PATH=%s:%s"
+                            magit-githook-directory
+                            (getenv "PATH"))))
        ;; The various w32 hacks are only applicable when running on the
        ;; local machine.  A local binding of process-environment different
        ;; from the top-level value affects the environment used by Tramp.
diff --git a/lisp/magit-wip.el b/lisp/magit-wip.el
index 24a8503c83..e6cdc642f2 100644
--- a/lisp/magit-wip.el
+++ b/lisp/magit-wip.el
@@ -60,10 +60,7 @@ makes it easier to inspect wip history and the wip commits 
are
 never garbage collected.
 
 If `githook', then use `magit-common-git-post-commit-hook' to
-create the merge commit.  This uses the experimental support for
-calling Lisp hooks from Git hooks, which is disabled by default,
-Customize `magit-overriding-githook-directory' to enable use of
-Git hooks."
+create the merge commit."
   :package-version '(magit . "2.90.0")
   :group 'magit-wip
   :type '(choice

Reply via email to