git-p4: create new function run_git_hook

This commit is in preparation of introducing new p4 submit hooks.

The current code in the python script git-p4.py makes the assumption
that the git hooks can be executed by subprocess.call() function.
However, when git is run on Windows, this may not work as expected.

The subprocess.call() does not cover all the use cases for properly
executing the various types of executable files on Windows.

Prepare for remediation by adding a new function, run_git_hook, that
takes 2 parameters:
* the short filename of an optionally registered git hook
* an optional list of parameters

The run_git_hook function will honor the existing behavior seen in the
current code for executing the p4-pre-submit hook:

* Hooks are looked for in core.hooksPath directory.
* If core.hooksPath is not set, then the current .git/hooks directory
  is checked.
* If the hook does not exist, the function returns True.
* If the hook file is not accessible, the function returns True.
* If the hook returns a zero exit code when executed, the function
  return True.
* If the hook returns a non-zero exit code, the function returns False.

Add the following additional functionality if git-p4.py is run on
Windows.
* If hook file is not located without an extension, search for
  any file in the associated hook directory (from the list above) that
  has the same name but with an extension.
* If the file is still not found, return True (the hook is missing)

Add a new function run_hook_command() that wraps the OS dependent
functionality for actually running the subprocess.call() with OS
dependent behavior:

If a hook file exists on Windows:
* If there is no extension, set the launch executable to be SH.EXE
  - Look for SH.EXE under the environmental variable EXEPATH in the
    bin/ directory.
  - If %EXEPATH%/bin/sh.exe exists, use this as the actual executable.
  - If %EXEPATH%/bin/sh.exe does not exist, use sh.exe
  - Execute subprocess.call() without the shell (shell=False)
* If there is an extension, execute subprocess.call() with teh shell
  (shell=True) and consider the file to be the executable.

The return value from run_hook_command() is the subprocess.call()
return value.

These functions are added in this commit, but are only staged and not
yet used.

Signed-off-by: Ben Keene <seraphire@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Ben Keene 2020-02-11 18:57:59 +00:00 committed by Junio C Hamano
parent 6b602a2f97
commit 9f59ca4d6a

View File

@ -26,6 +26,7 @@ import zipfile
import zlib
import ctypes
import errno
import glob
# support basestring in python3
try:
@ -185,6 +186,73 @@ def prompt(prompt_text):
if response in choices:
return response
def run_git_hook(cmd, param=[]):
"""Execute a hook if the hook exists."""
if verbose:
sys.stderr.write("Looking for hook: %s\n" % cmd)
sys.stderr.flush()
hooks_path = gitConfig("core.hooksPath")
if len(hooks_path) <= 0:
hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
if not isinstance(param, list):
param=[param]
# resolve hook file name, OS depdenent
hook_file = os.path.join(hooks_path, cmd)
if platform.system() == 'Windows':
if not os.path.isfile(hook_file):
# look for the file with an extension
files = glob.glob(hook_file + ".*")
if not files:
return True
files.sort()
hook_file = files.pop()
while hook_file.upper().endswith(".SAMPLE"):
# The file is a sample hook. We don't want it
if len(files) > 0:
hook_file = files.pop()
else:
return True
if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
return True
return run_hook_command(hook_file, param) == 0
def run_hook_command(cmd, param):
"""Executes a git hook command
cmd = the command line file to be executed. This can be
a file that is run by OS association.
param = a list of parameters to pass to the cmd command
On windows, the extension is checked to see if it should
be run with the Git for Windows Bash shell. If there
is no file extension, the file is deemed a bash shell
and will be handed off to sh.exe. Otherwise, Windows
will be called with the shell to handle the file assocation.
For non Windows operating systems, the file is called
as an executable.
"""
cli = [cmd] + param
use_shell = False
if platform.system() == 'Windows':
(root,ext) = os.path.splitext(cmd)
if ext == "":
exe_path = os.environ.get("EXEPATH")
if exe_path is None:
exe_path = ""
else:
exe_path = os.path.join(exe_path, "bin")
cli = [os.path.join(exe_path, "SH.EXE")] + cli
else:
use_shell = True
return subprocess.call(cli, shell=use_shell)
def write_pipe(c, stdin):
if verbose:
sys.stderr.write('Writing pipe: %s\n' % str(c))
@ -2337,12 +2405,7 @@ class P4Submit(Command, P4UserMap):
sys.exit("number of commits (%d) must match number of shelved changelist (%d)" %
(len(commits), num_shelves))
hooks_path = gitConfig("core.hooksPath")
if len(hooks_path) <= 0:
hooks_path = os.path.join(os.environ.get("GIT_DIR", ".git"), "hooks")
hook_file = os.path.join(hooks_path, "p4-pre-submit")
if os.path.isfile(hook_file) and os.access(hook_file, os.X_OK) and subprocess.call([hook_file]) != 0:
if not run_git_hook("p4-pre-submit"):
sys.exit(1)
#
@ -4124,7 +4187,6 @@ commands = {
"unshelve" : P4Unshelve,
}
def main():
if len(sys.argv[1:]) == 0:
printUsage(commands.keys())