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
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))
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")