]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'bk/p4-pre-edit-changelist'
authorJunio C Hamano <gitster@pobox.com>
Wed, 22 Apr 2020 20:42:43 +0000 (13:42 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 22 Apr 2020 20:42:43 +0000 (13:42 -0700)
"git p4" learned four new hooks and also "--no-verify" option to
bypass them (and the existing "p4-pre-submit" hook).

* bk/p4-pre-edit-changelist:
  git-p4: add RCS keyword status message
  git-p4: add p4 submit hooks
  git-p4: restructure code in submit
  git-p4: add --no-verify option
  git-p4: add p4-pre-submit exit text
  git-p4: create new function run_git_hook
  git-p4: rewrite prompt to be Windows compatible

1  2 
Documentation/githooks.txt
git-p4.py

Simple merge
diff --cc git-p4.py
index 1fe03cd3392f209ce985f1446ac403c8c0dca9bb,258b9b98b93f92632d2079ecf4b2a321e675cb08..b8b2a1679e7336e27d794947e9191cd4b5ec1b57
+++ b/git-p4.py
@@@ -34,16 -26,38 +34,17 @@@ import zipfil
  import zlib
  import ctypes
  import errno
+ import glob
  
 +# On python2.7 where raw_input() and input() are both availble,
 +# we want raw_input's semantics, but aliased to input for python3
 +# compatibility
  # support basestring in python3
  try:
 -    unicode = unicode
 -except NameError:
 -    # 'unicode' is undefined, must be Python 3
 -    str = str
 -    unicode = str
 -    bytes = bytes
 -    basestring = (str,bytes)
 -else:
 -    # 'unicode' exists, must be Python 2
 -    str = str
 -    unicode = unicode
 -    bytes = str
 -    basestring = basestring
 -
 -try:
 -    from subprocess import CalledProcessError
 -except ImportError:
 -    # from python2.7:subprocess.py
 -    # Exception classes used by this module.
 -    class CalledProcessError(Exception):
 -        """This exception is raised when a process run by check_call() returns
 -        a non-zero exit status.  The exit status will be stored in the
 -        returncode attribute."""
 -        def __init__(self, returncode, cmd):
 -            self.returncode = returncode
 -            self.cmd = cmd
 -        def __str__(self):
 -            return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
 +    if raw_input and input:
 +        input = raw_input
 +except:
 +    pass
  
  verbose = False
  
@@@ -172,36 -186,73 +176,103 @@@ def prompt(prompt_text)
          if response in choices:
              return response
  
 +# We need different encoding/decoding strategies for text data being passed
 +# around in pipes depending on python version
 +if bytes is not str:
 +    # For python3, always encode and decode as appropriate
 +    def decode_text_stream(s):
 +        return s.decode() if isinstance(s, bytes) else s
 +    def encode_text_stream(s):
 +        return s.encode() if isinstance(s, str) else s
 +else:
 +    # For python2.7, pass read strings as-is, but also allow writing unicode
 +    def decode_text_stream(s):
 +        return s
 +    def encode_text_stream(s):
 +        return s.encode('utf_8') if isinstance(s, unicode) else s
 +
 +def decode_path(path):
 +    """Decode a given string (bytes or otherwise) using configured path encoding options
 +    """
 +    encoding = gitConfig('git-p4.pathEncoding') or 'utf_8'
 +    if bytes is not str:
 +        return path.decode(encoding, errors='replace') if isinstance(path, bytes) else path
 +    else:
 +        try:
 +            path.decode('ascii')
 +        except:
 +            path = path.decode(encoding, errors='replace')
 +            if verbose:
 +                print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path))
 +        return path
 +
+ 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))
@@@ -2074,54 -2125,64 +2176,64 @@@ class P4Submit(Command, P4UserMap)
          tmpFile = os.fdopen(handle, "w+b")
          if self.isWindows:
              submitTemplate = submitTemplate.replace("\n", "\r\n")
 -        tmpFile.write(submitTemplate)
 +        tmpFile.write(encode_text_stream(submitTemplate))
          tmpFile.close()
  
-         if self.prepare_p4_only:
-             #
-             # Leave the p4 tree prepared, and the submit template around
-             # and let the user decide what to do next
-             #
-             print()
-             print("P4 workspace prepared for submission.")
-             print("To submit or revert, go to client workspace")
-             print("  " + self.clientPath)
-             print()
-             print("To submit, use \"p4 submit\" to write a new description,")
-             print("or \"p4 submit -i <%s\" to use the one prepared by" \
-                   " \"git p4\"." % fileName)
-             print("You can delete the file \"%s\" when finished." % fileName)
-             if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
-                 print("To preserve change ownership by user %s, you must\n" \
-                       "do \"p4 change -f <change>\" after submitting and\n" \
-                       "edit the User field.")
-             if pureRenameCopy:
-                 print("After submitting, renamed files must be re-synced.")
-                 print("Invoke \"p4 sync -f\" on each of these files:")
-                 for f in pureRenameCopy:
-                     print("  " + f)
-             print()
-             print("To revert the changes, use \"p4 revert ...\", and delete")
-             print("the submit template file \"%s\"" % fileName)
-             if filesToAdd:
-                 print("Since the commit adds new files, they must be deleted:")
-                 for f in filesToAdd:
-                     print("  " + f)
-             print()
-             return True
-         #
-         # Let the user edit the change description, then submit it.
-         #
          submitted = False
  
          try:
+             # Allow the hook to edit the changelist text before presenting it
+             # to the user.
+             if not run_git_hook("p4-prepare-changelist", [fileName]):
+                 return False
+             if self.prepare_p4_only:
+                 #
+                 # Leave the p4 tree prepared, and the submit template around
+                 # and let the user decide what to do next
+                 #
+                 submitted = True
+                 print("")
+                 print("P4 workspace prepared for submission.")
+                 print("To submit or revert, go to client workspace")
+                 print("  " + self.clientPath)
+                 print("")
+                 print("To submit, use \"p4 submit\" to write a new description,")
+                 print("or \"p4 submit -i <%s\" to use the one prepared by" \
+                       " \"git p4\"." % fileName)
+                 print("You can delete the file \"%s\" when finished." % fileName)
+                 if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
+                     print("To preserve change ownership by user %s, you must\n" \
+                           "do \"p4 change -f <change>\" after submitting and\n" \
+                           "edit the User field.")
+                 if pureRenameCopy:
+                     print("After submitting, renamed files must be re-synced.")
+                     print("Invoke \"p4 sync -f\" on each of these files:")
+                     for f in pureRenameCopy:
+                         print("  " + f)
+                 print("")
+                 print("To revert the changes, use \"p4 revert ...\", and delete")
+                 print("the submit template file \"%s\"" % fileName)
+                 if filesToAdd:
+                     print("Since the commit adds new files, they must be deleted:")
+                     for f in filesToAdd:
+                         print("  " + f)
+                 print("")
+                 sys.stdout.flush()
+                 return True
              if self.edit_template(fileName):
+                 if not self.no_verify:
+                     if not run_git_hook("p4-changelist", [fileName]):
+                         print("The p4-changelist hook failed.")
+                         sys.stdout.flush()
+                         return False
                  # read the edited message and submit
                  tmpFile = open(fileName, "rb")
 -                message = tmpFile.read()
 +                message = decode_text_stream(tmpFile.read())
                  tmpFile.close()
                  if self.isWindows:
                      message = message.replace("\r\n", "\n")