Merge branch 'yz/p4-py3'
Update "git p4" to work with Python 3. * yz/p4-py3: ci: use python3 in linux-gcc and osx-gcc and python2 elsewhere git-p4: use python3's input() everywhere git-p4: simplify regex pattern generation for parsing diff-tree git-p4: use dict.items() iteration for python3 compatibility git-p4: use functools.reduce instead of reduce git-p4: fix freezing while waiting for fast-import progress git-p4: use marshal format version 2 when sending to p4 git-p4: open .gitp4-usercache.txt in text mode git-p4: convert path to unicode before processing them git-p4: encode/decode communication with git for python3 git-p4: encode/decode communication with p4 for python3 git-p4: remove string type aliasing git-p4: change the expansion test from basestring to list git-p4: make python2.7 the oldest supported version
This commit is contained in:
commit
9a0fa1709c
@ -162,6 +162,9 @@ linux-clang|linux-gcc)
|
|||||||
if [ "$jobname" = linux-gcc ]
|
if [ "$jobname" = linux-gcc ]
|
||||||
then
|
then
|
||||||
export CC=gcc-8
|
export CC=gcc-8
|
||||||
|
MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)"
|
||||||
|
else
|
||||||
|
MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export GIT_TEST_HTTPD=true
|
export GIT_TEST_HTTPD=true
|
||||||
@ -182,6 +185,9 @@ osx-clang|osx-gcc)
|
|||||||
if [ "$jobname" = osx-gcc ]
|
if [ "$jobname" = osx-gcc ]
|
||||||
then
|
then
|
||||||
export CC=gcc-9
|
export CC=gcc-9
|
||||||
|
MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)"
|
||||||
|
else
|
||||||
|
MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# t9810 occasionally fails on Travis CI OS X
|
# t9810 occasionally fails on Travis CI OS X
|
||||||
|
235
git-p4.py
235
git-p4.py
@ -16,12 +16,12 @@
|
|||||||
# pylint: disable=too-many-branches,too-many-nested-blocks
|
# pylint: disable=too-many-branches,too-many-nested-blocks
|
||||||
#
|
#
|
||||||
import sys
|
import sys
|
||||||
if sys.hexversion < 0x02040000:
|
if sys.version_info.major < 3 and sys.version_info.minor < 7:
|
||||||
# The limiter is the subprocess module
|
sys.stderr.write("git-p4: requires Python 2.7 or later.\n")
|
||||||
sys.stderr.write("git-p4: requires Python 2.4 or later.\n")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
import os
|
import os
|
||||||
import optparse
|
import optparse
|
||||||
|
import functools
|
||||||
import marshal
|
import marshal
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -35,36 +35,15 @@ import zlib
|
|||||||
import ctypes
|
import ctypes
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
|
# 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
|
# support basestring in python3
|
||||||
try:
|
try:
|
||||||
unicode = unicode
|
if raw_input and input:
|
||||||
except NameError:
|
input = raw_input
|
||||||
# 'unicode' is undefined, must be Python 3
|
except:
|
||||||
str = str
|
pass
|
||||||
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)
|
|
||||||
|
|
||||||
verbose = False
|
verbose = False
|
||||||
|
|
||||||
@ -113,7 +92,7 @@ def p4_build_cmd(cmd):
|
|||||||
# Provide a way to not pass this option by setting git-p4.retries to 0
|
# Provide a way to not pass this option by setting git-p4.retries to 0
|
||||||
real_cmd += ["-r", str(retries)]
|
real_cmd += ["-r", str(retries)]
|
||||||
|
|
||||||
if isinstance(cmd,basestring):
|
if not isinstance(cmd, list):
|
||||||
real_cmd = ' '.join(real_cmd) + ' ' + cmd
|
real_cmd = ' '.join(real_cmd) + ' ' + cmd
|
||||||
else:
|
else:
|
||||||
real_cmd += cmd
|
real_cmd += cmd
|
||||||
@ -186,18 +165,48 @@ def prompt(prompt_text):
|
|||||||
"""
|
"""
|
||||||
choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
|
choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
|
||||||
while True:
|
while True:
|
||||||
response = raw_input(prompt_text).strip().lower()
|
response = input(prompt_text).strip().lower()
|
||||||
if not response:
|
if not response:
|
||||||
continue
|
continue
|
||||||
response = response[0]
|
response = response[0]
|
||||||
if response in choices:
|
if response in choices:
|
||||||
return response
|
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 write_pipe(c, stdin):
|
def write_pipe(c, stdin):
|
||||||
if verbose:
|
if verbose:
|
||||||
sys.stderr.write('Writing pipe: %s\n' % str(c))
|
sys.stderr.write('Writing pipe: %s\n' % str(c))
|
||||||
|
|
||||||
expand = isinstance(c,basestring)
|
expand = not isinstance(c, list)
|
||||||
p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
|
p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
|
||||||
pipe = p.stdin
|
pipe = p.stdin
|
||||||
val = pipe.write(stdin)
|
val = pipe.write(stdin)
|
||||||
@ -209,6 +218,8 @@ def write_pipe(c, stdin):
|
|||||||
|
|
||||||
def p4_write_pipe(c, stdin):
|
def p4_write_pipe(c, stdin):
|
||||||
real_cmd = p4_build_cmd(c)
|
real_cmd = p4_build_cmd(c)
|
||||||
|
if bytes is not str and isinstance(stdin, str):
|
||||||
|
stdin = encode_text_stream(stdin)
|
||||||
return write_pipe(real_cmd, stdin)
|
return write_pipe(real_cmd, stdin)
|
||||||
|
|
||||||
def read_pipe_full(c):
|
def read_pipe_full(c):
|
||||||
@ -219,15 +230,17 @@ def read_pipe_full(c):
|
|||||||
if verbose:
|
if verbose:
|
||||||
sys.stderr.write('Reading pipe: %s\n' % str(c))
|
sys.stderr.write('Reading pipe: %s\n' % str(c))
|
||||||
|
|
||||||
expand = isinstance(c,basestring)
|
expand = not isinstance(c, list)
|
||||||
p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
|
p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
|
||||||
(out, err) = p.communicate()
|
(out, err) = p.communicate()
|
||||||
return (p.returncode, out, err)
|
return (p.returncode, out, decode_text_stream(err))
|
||||||
|
|
||||||
def read_pipe(c, ignore_error=False):
|
def read_pipe(c, ignore_error=False, raw=False):
|
||||||
""" Read output from command. Returns the output text on
|
""" Read output from command. Returns the output text on
|
||||||
success. On failure, terminates execution, unless
|
success. On failure, terminates execution, unless
|
||||||
ignore_error is True, when it returns an empty string.
|
ignore_error is True, when it returns an empty string.
|
||||||
|
|
||||||
|
If raw is True, do not attempt to decode output text.
|
||||||
"""
|
"""
|
||||||
(retcode, out, err) = read_pipe_full(c)
|
(retcode, out, err) = read_pipe_full(c)
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
@ -235,6 +248,8 @@ def read_pipe(c, ignore_error=False):
|
|||||||
out = ""
|
out = ""
|
||||||
else:
|
else:
|
||||||
die('Command failed: %s\nError: %s' % (str(c), err))
|
die('Command failed: %s\nError: %s' % (str(c), err))
|
||||||
|
if not raw:
|
||||||
|
out = decode_text_stream(out)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def read_pipe_text(c):
|
def read_pipe_text(c):
|
||||||
@ -245,23 +260,22 @@ def read_pipe_text(c):
|
|||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return out.rstrip()
|
return decode_text_stream(out).rstrip()
|
||||||
|
|
||||||
def p4_read_pipe(c, ignore_error=False):
|
def p4_read_pipe(c, ignore_error=False, raw=False):
|
||||||
real_cmd = p4_build_cmd(c)
|
real_cmd = p4_build_cmd(c)
|
||||||
return read_pipe(real_cmd, ignore_error)
|
return read_pipe(real_cmd, ignore_error, raw=raw)
|
||||||
|
|
||||||
def read_pipe_lines(c):
|
def read_pipe_lines(c):
|
||||||
if verbose:
|
if verbose:
|
||||||
sys.stderr.write('Reading pipe: %s\n' % str(c))
|
sys.stderr.write('Reading pipe: %s\n' % str(c))
|
||||||
|
|
||||||
expand = isinstance(c, basestring)
|
expand = not isinstance(c, list)
|
||||||
p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
|
p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
|
||||||
pipe = p.stdout
|
pipe = p.stdout
|
||||||
val = pipe.readlines()
|
val = [decode_text_stream(line) for line in pipe.readlines()]
|
||||||
if pipe.close() or p.wait():
|
if pipe.close() or p.wait():
|
||||||
die('Command failed: %s' % str(c))
|
die('Command failed: %s' % str(c))
|
||||||
|
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def p4_read_pipe_lines(c):
|
def p4_read_pipe_lines(c):
|
||||||
@ -289,6 +303,7 @@ def p4_has_move_command():
|
|||||||
cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
|
cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
|
||||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
(out, err) = p.communicate()
|
(out, err) = p.communicate()
|
||||||
|
err = decode_text_stream(err)
|
||||||
# return code will be 1 in either case
|
# return code will be 1 in either case
|
||||||
if err.find("Invalid option") >= 0:
|
if err.find("Invalid option") >= 0:
|
||||||
return False
|
return False
|
||||||
@ -298,7 +313,7 @@ def p4_has_move_command():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def system(cmd, ignore_error=False):
|
def system(cmd, ignore_error=False):
|
||||||
expand = isinstance(cmd,basestring)
|
expand = not isinstance(cmd, list)
|
||||||
if verbose:
|
if verbose:
|
||||||
sys.stderr.write("executing %s\n" % str(cmd))
|
sys.stderr.write("executing %s\n" % str(cmd))
|
||||||
retcode = subprocess.call(cmd, shell=expand)
|
retcode = subprocess.call(cmd, shell=expand)
|
||||||
@ -310,7 +325,7 @@ def system(cmd, ignore_error=False):
|
|||||||
def p4_system(cmd):
|
def p4_system(cmd):
|
||||||
"""Specifically invoke p4 as the system command. """
|
"""Specifically invoke p4 as the system command. """
|
||||||
real_cmd = p4_build_cmd(cmd)
|
real_cmd = p4_build_cmd(cmd)
|
||||||
expand = isinstance(real_cmd, basestring)
|
expand = not isinstance(real_cmd, list)
|
||||||
retcode = subprocess.call(real_cmd, shell=expand)
|
retcode = subprocess.call(real_cmd, shell=expand)
|
||||||
if retcode:
|
if retcode:
|
||||||
raise CalledProcessError(retcode, real_cmd)
|
raise CalledProcessError(retcode, real_cmd)
|
||||||
@ -548,7 +563,7 @@ def getP4OpenedType(file):
|
|||||||
# Return the set of all p4 labels
|
# Return the set of all p4 labels
|
||||||
def getP4Labels(depotPaths):
|
def getP4Labels(depotPaths):
|
||||||
labels = set()
|
labels = set()
|
||||||
if isinstance(depotPaths,basestring):
|
if not isinstance(depotPaths, list):
|
||||||
depotPaths = [depotPaths]
|
depotPaths = [depotPaths]
|
||||||
|
|
||||||
for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
|
for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
|
||||||
@ -565,12 +580,7 @@ def getGitTags():
|
|||||||
gitTags.add(tag)
|
gitTags.add(tag)
|
||||||
return gitTags
|
return gitTags
|
||||||
|
|
||||||
def diffTreePattern():
|
_diff_tree_pattern = None
|
||||||
# This is a simple generator for the diff tree regex pattern. This could be
|
|
||||||
# a class variable if this and parseDiffTreeEntry were a part of a class.
|
|
||||||
pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
|
|
||||||
while True:
|
|
||||||
yield pattern
|
|
||||||
|
|
||||||
def parseDiffTreeEntry(entry):
|
def parseDiffTreeEntry(entry):
|
||||||
"""Parses a single diff tree entry into its component elements.
|
"""Parses a single diff tree entry into its component elements.
|
||||||
@ -591,7 +601,11 @@ def parseDiffTreeEntry(entry):
|
|||||||
|
|
||||||
If the pattern is not matched, None is returned."""
|
If the pattern is not matched, None is returned."""
|
||||||
|
|
||||||
match = diffTreePattern().next().match(entry)
|
global _diff_tree_pattern
|
||||||
|
if not _diff_tree_pattern:
|
||||||
|
_diff_tree_pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
|
||||||
|
|
||||||
|
match = _diff_tree_pattern.match(entry)
|
||||||
if match:
|
if match:
|
||||||
return {
|
return {
|
||||||
'src_mode': match.group(1),
|
'src_mode': match.group(1),
|
||||||
@ -643,7 +657,7 @@ def isModeExecChanged(src_mode, dst_mode):
|
|||||||
def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
|
def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
|
||||||
errors_as_exceptions=False):
|
errors_as_exceptions=False):
|
||||||
|
|
||||||
if isinstance(cmd,basestring):
|
if not isinstance(cmd, list):
|
||||||
cmd = "-G " + cmd
|
cmd = "-G " + cmd
|
||||||
expand = True
|
expand = True
|
||||||
else:
|
else:
|
||||||
@ -660,11 +674,12 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
|
|||||||
stdin_file = None
|
stdin_file = None
|
||||||
if stdin is not None:
|
if stdin is not None:
|
||||||
stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
|
stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
|
||||||
if isinstance(stdin,basestring):
|
if not isinstance(stdin, list):
|
||||||
stdin_file.write(stdin)
|
stdin_file.write(stdin)
|
||||||
else:
|
else:
|
||||||
for i in stdin:
|
for i in stdin:
|
||||||
stdin_file.write(i + '\n')
|
stdin_file.write(encode_text_stream(i))
|
||||||
|
stdin_file.write(b'\n')
|
||||||
stdin_file.flush()
|
stdin_file.flush()
|
||||||
stdin_file.seek(0)
|
stdin_file.seek(0)
|
||||||
|
|
||||||
@ -677,6 +692,20 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
|
|||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
entry = marshal.load(p4.stdout)
|
entry = marshal.load(p4.stdout)
|
||||||
|
if bytes is not str:
|
||||||
|
# Decode unmarshalled dict to use str keys and values, except for:
|
||||||
|
# - `data` which may contain arbitrary binary data
|
||||||
|
# - `depotFile[0-9]*`, `path`, or `clientFile` which may contain non-UTF8 encoded text
|
||||||
|
decoded_entry = {}
|
||||||
|
for key, value in entry.items():
|
||||||
|
key = key.decode()
|
||||||
|
if isinstance(value, bytes) and not (key in ('data', 'path', 'clientFile') or key.startswith('depotFile')):
|
||||||
|
value = value.decode()
|
||||||
|
decoded_entry[key] = value
|
||||||
|
# Parse out data if it's an error response
|
||||||
|
if decoded_entry.get('code') == 'error' and 'data' in decoded_entry:
|
||||||
|
decoded_entry['data'] = decoded_entry['data'].decode()
|
||||||
|
entry = decoded_entry
|
||||||
if skip_info:
|
if skip_info:
|
||||||
if 'code' in entry and entry['code'] == 'info':
|
if 'code' in entry and entry['code'] == 'info':
|
||||||
continue
|
continue
|
||||||
@ -727,7 +756,8 @@ def p4Where(depotPath):
|
|||||||
if "depotFile" in entry:
|
if "depotFile" in entry:
|
||||||
# Search for the base client side depot path, as long as it starts with the branch's P4 path.
|
# Search for the base client side depot path, as long as it starts with the branch's P4 path.
|
||||||
# The base path always ends with "/...".
|
# The base path always ends with "/...".
|
||||||
if entry["depotFile"].find(depotPath) == 0 and entry["depotFile"][-4:] == "/...":
|
entry_path = decode_path(entry['depotFile'])
|
||||||
|
if entry_path.find(depotPath) == 0 and entry_path[-4:] == "/...":
|
||||||
output = entry
|
output = entry
|
||||||
break
|
break
|
||||||
elif "data" in entry:
|
elif "data" in entry:
|
||||||
@ -742,11 +772,11 @@ def p4Where(depotPath):
|
|||||||
return ""
|
return ""
|
||||||
clientPath = ""
|
clientPath = ""
|
||||||
if "path" in output:
|
if "path" in output:
|
||||||
clientPath = output.get("path")
|
clientPath = decode_path(output['path'])
|
||||||
elif "data" in output:
|
elif "data" in output:
|
||||||
data = output.get("data")
|
data = output.get("data")
|
||||||
lastSpace = data.rfind(" ")
|
lastSpace = data.rfind(b" ")
|
||||||
clientPath = data[lastSpace + 1:]
|
clientPath = decode_path(data[lastSpace + 1:])
|
||||||
|
|
||||||
if clientPath.endswith("..."):
|
if clientPath.endswith("..."):
|
||||||
clientPath = clientPath[:-3]
|
clientPath = clientPath[:-3]
|
||||||
@ -894,6 +924,7 @@ def branch_exists(branch):
|
|||||||
cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
|
cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
|
||||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
out, _ = p.communicate()
|
out, _ = p.communicate()
|
||||||
|
out = decode_text_stream(out)
|
||||||
if p.returncode:
|
if p.returncode:
|
||||||
return False
|
return False
|
||||||
# expect exactly one line of output: the branch name
|
# expect exactly one line of output: the branch name
|
||||||
@ -1171,7 +1202,7 @@ class LargeFileSystem(object):
|
|||||||
assert False, "Method 'pushFile' required in " + self.__class__.__name__
|
assert False, "Method 'pushFile' required in " + self.__class__.__name__
|
||||||
|
|
||||||
def hasLargeFileExtension(self, relPath):
|
def hasLargeFileExtension(self, relPath):
|
||||||
return reduce(
|
return functools.reduce(
|
||||||
lambda a, b: a or b,
|
lambda a, b: a or b,
|
||||||
[relPath.endswith('.' + e) for e in gitConfigList('git-p4.largeFileExtensions')],
|
[relPath.endswith('.' + e) for e in gitConfigList('git-p4.largeFileExtensions')],
|
||||||
False
|
False
|
||||||
@ -1278,7 +1309,7 @@ class GitLFS(LargeFileSystem):
|
|||||||
['git', 'lfs', 'pointer', '--file=' + contentFile],
|
['git', 'lfs', 'pointer', '--file=' + contentFile],
|
||||||
stdout=subprocess.PIPE
|
stdout=subprocess.PIPE
|
||||||
)
|
)
|
||||||
pointerFile = pointerProcess.stdout.read()
|
pointerFile = decode_text_stream(pointerProcess.stdout.read())
|
||||||
if pointerProcess.wait():
|
if pointerProcess.wait():
|
||||||
os.remove(contentFile)
|
os.remove(contentFile)
|
||||||
die('git-lfs pointer command failed. Did you install the extension?')
|
die('git-lfs pointer command failed. Did you install the extension?')
|
||||||
@ -1414,14 +1445,14 @@ class P4UserMap:
|
|||||||
for (key, val) in self.users.items():
|
for (key, val) in self.users.items():
|
||||||
s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
|
s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
|
||||||
|
|
||||||
open(self.getUserCacheFilename(), "wb").write(s)
|
open(self.getUserCacheFilename(), 'w').write(s)
|
||||||
self.userMapFromPerforceServer = True
|
self.userMapFromPerforceServer = True
|
||||||
|
|
||||||
def loadUserMapFromCache(self):
|
def loadUserMapFromCache(self):
|
||||||
self.users = {}
|
self.users = {}
|
||||||
self.userMapFromPerforceServer = False
|
self.userMapFromPerforceServer = False
|
||||||
try:
|
try:
|
||||||
cache = open(self.getUserCacheFilename(), "rb")
|
cache = open(self.getUserCacheFilename(), 'r')
|
||||||
lines = cache.readlines()
|
lines = cache.readlines()
|
||||||
cache.close()
|
cache.close()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@ -1698,7 +1729,8 @@ class P4Submit(Command, P4UserMap):
|
|||||||
c = changes[0]
|
c = changes[0]
|
||||||
if c['User'] == newUser: return # nothing to do
|
if c['User'] == newUser: return # nothing to do
|
||||||
c['User'] = newUser
|
c['User'] = newUser
|
||||||
input = marshal.dumps(c)
|
# p4 does not understand format version 3 and above
|
||||||
|
input = marshal.dumps(c, 2)
|
||||||
|
|
||||||
result = p4CmdList("change -f -i", stdin=input)
|
result = p4CmdList("change -f -i", stdin=input)
|
||||||
for r in result:
|
for r in result:
|
||||||
@ -1762,7 +1794,7 @@ class P4Submit(Command, P4UserMap):
|
|||||||
break
|
break
|
||||||
if not change_entry:
|
if not change_entry:
|
||||||
die('Failed to decode output of p4 change -o')
|
die('Failed to decode output of p4 change -o')
|
||||||
for key, value in change_entry.iteritems():
|
for key, value in change_entry.items():
|
||||||
if key.startswith('File'):
|
if key.startswith('File'):
|
||||||
if 'depot-paths' in settings:
|
if 'depot-paths' in settings:
|
||||||
if not [p for p in settings['depot-paths']
|
if not [p for p in settings['depot-paths']
|
||||||
@ -2042,7 +2074,7 @@ class P4Submit(Command, P4UserMap):
|
|||||||
tmpFile = os.fdopen(handle, "w+b")
|
tmpFile = os.fdopen(handle, "w+b")
|
||||||
if self.isWindows:
|
if self.isWindows:
|
||||||
submitTemplate = submitTemplate.replace("\n", "\r\n")
|
submitTemplate = submitTemplate.replace("\n", "\r\n")
|
||||||
tmpFile.write(submitTemplate)
|
tmpFile.write(encode_text_stream(submitTemplate))
|
||||||
tmpFile.close()
|
tmpFile.close()
|
||||||
|
|
||||||
if self.prepare_p4_only:
|
if self.prepare_p4_only:
|
||||||
@ -2089,7 +2121,7 @@ class P4Submit(Command, P4UserMap):
|
|||||||
if self.edit_template(fileName):
|
if self.edit_template(fileName):
|
||||||
# read the edited message and submit
|
# read the edited message and submit
|
||||||
tmpFile = open(fileName, "rb")
|
tmpFile = open(fileName, "rb")
|
||||||
message = tmpFile.read()
|
message = decode_text_stream(tmpFile.read())
|
||||||
tmpFile.close()
|
tmpFile.close()
|
||||||
if self.isWindows:
|
if self.isWindows:
|
||||||
message = message.replace("\r\n", "\n")
|
message = message.replace("\r\n", "\n")
|
||||||
@ -2509,7 +2541,7 @@ class View(object):
|
|||||||
|
|
||||||
def convert_client_path(self, clientFile):
|
def convert_client_path(self, clientFile):
|
||||||
# chop off //client/ part to make it relative
|
# chop off //client/ part to make it relative
|
||||||
if not clientFile.startswith(self.client_prefix):
|
if not decode_path(clientFile).startswith(self.client_prefix):
|
||||||
die("No prefix '%s' on clientFile '%s'" %
|
die("No prefix '%s' on clientFile '%s'" %
|
||||||
(self.client_prefix, clientFile))
|
(self.client_prefix, clientFile))
|
||||||
return clientFile[len(self.client_prefix):]
|
return clientFile[len(self.client_prefix):]
|
||||||
@ -2518,7 +2550,7 @@ class View(object):
|
|||||||
""" Caching file paths by "p4 where" batch query """
|
""" Caching file paths by "p4 where" batch query """
|
||||||
|
|
||||||
# List depot file paths exclude that already cached
|
# List depot file paths exclude that already cached
|
||||||
fileArgs = [f['path'] for f in files if f['path'] not in self.client_spec_path_cache]
|
fileArgs = [f['path'] for f in files if decode_path(f['path']) not in self.client_spec_path_cache]
|
||||||
|
|
||||||
if len(fileArgs) == 0:
|
if len(fileArgs) == 0:
|
||||||
return # All files in cache
|
return # All files in cache
|
||||||
@ -2533,16 +2565,18 @@ class View(object):
|
|||||||
if "unmap" in res:
|
if "unmap" in res:
|
||||||
# it will list all of them, but only one not unmap-ped
|
# it will list all of them, but only one not unmap-ped
|
||||||
continue
|
continue
|
||||||
|
depot_path = decode_path(res['depotFile'])
|
||||||
if gitConfigBool("core.ignorecase"):
|
if gitConfigBool("core.ignorecase"):
|
||||||
res['depotFile'] = res['depotFile'].lower()
|
depot_path = depot_path.lower()
|
||||||
self.client_spec_path_cache[res['depotFile']] = self.convert_client_path(res["clientFile"])
|
self.client_spec_path_cache[depot_path] = self.convert_client_path(res["clientFile"])
|
||||||
|
|
||||||
# not found files or unmap files set to ""
|
# not found files or unmap files set to ""
|
||||||
for depotFile in fileArgs:
|
for depotFile in fileArgs:
|
||||||
|
depotFile = decode_path(depotFile)
|
||||||
if gitConfigBool("core.ignorecase"):
|
if gitConfigBool("core.ignorecase"):
|
||||||
depotFile = depotFile.lower()
|
depotFile = depotFile.lower()
|
||||||
if depotFile not in self.client_spec_path_cache:
|
if depotFile not in self.client_spec_path_cache:
|
||||||
self.client_spec_path_cache[depotFile] = ""
|
self.client_spec_path_cache[depotFile] = b''
|
||||||
|
|
||||||
def map_in_client(self, depot_path):
|
def map_in_client(self, depot_path):
|
||||||
"""Return the relative location in the client where this
|
"""Return the relative location in the client where this
|
||||||
@ -2647,6 +2681,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
def checkpoint(self):
|
def checkpoint(self):
|
||||||
self.gitStream.write("checkpoint\n\n")
|
self.gitStream.write("checkpoint\n\n")
|
||||||
self.gitStream.write("progress checkpoint\n\n")
|
self.gitStream.write("progress checkpoint\n\n")
|
||||||
|
self.gitStream.flush()
|
||||||
out = self.gitOutput.readline()
|
out = self.gitOutput.readline()
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print("checkpoint finished: " + out)
|
print("checkpoint finished: " + out)
|
||||||
@ -2660,7 +2695,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
elif path.lower() == p.lower():
|
elif path.lower() == p.lower():
|
||||||
return False
|
return False
|
||||||
for p in self.depotPaths:
|
for p in self.depotPaths:
|
||||||
if p4PathStartsWith(path, p):
|
if p4PathStartsWith(path, decode_path(p)):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -2669,7 +2704,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
fnum = 0
|
fnum = 0
|
||||||
while "depotFile%s" % fnum in commit:
|
while "depotFile%s" % fnum in commit:
|
||||||
path = commit["depotFile%s" % fnum]
|
path = commit["depotFile%s" % fnum]
|
||||||
found = self.isPathWanted(path)
|
found = self.isPathWanted(decode_path(path))
|
||||||
if not found:
|
if not found:
|
||||||
fnum = fnum + 1
|
fnum = fnum + 1
|
||||||
continue
|
continue
|
||||||
@ -2703,7 +2738,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
if self.useClientSpec:
|
if self.useClientSpec:
|
||||||
# branch detection moves files up a level (the branch name)
|
# branch detection moves files up a level (the branch name)
|
||||||
# from what client spec interpretation gives
|
# from what client spec interpretation gives
|
||||||
path = self.clientSpecDirs.map_in_client(path)
|
path = decode_path(self.clientSpecDirs.map_in_client(path))
|
||||||
if self.detectBranches:
|
if self.detectBranches:
|
||||||
for b in self.knownBranches:
|
for b in self.knownBranches:
|
||||||
if p4PathStartsWith(path, b + "/"):
|
if p4PathStartsWith(path, b + "/"):
|
||||||
@ -2737,14 +2772,15 @@ class P4Sync(Command, P4UserMap):
|
|||||||
branches = {}
|
branches = {}
|
||||||
fnum = 0
|
fnum = 0
|
||||||
while "depotFile%s" % fnum in commit:
|
while "depotFile%s" % fnum in commit:
|
||||||
path = commit["depotFile%s" % fnum]
|
raw_path = commit["depotFile%s" % fnum]
|
||||||
|
path = decode_path(raw_path)
|
||||||
found = self.isPathWanted(path)
|
found = self.isPathWanted(path)
|
||||||
if not found:
|
if not found:
|
||||||
fnum = fnum + 1
|
fnum = fnum + 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
file = {}
|
file = {}
|
||||||
file["path"] = path
|
file["path"] = raw_path
|
||||||
file["rev"] = commit["rev%s" % fnum]
|
file["rev"] = commit["rev%s" % fnum]
|
||||||
file["action"] = commit["action%s" % fnum]
|
file["action"] = commit["action%s" % fnum]
|
||||||
file["type"] = commit["type%s" % fnum]
|
file["type"] = commit["type%s" % fnum]
|
||||||
@ -2753,7 +2789,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
# start with the full relative path where this file would
|
# start with the full relative path where this file would
|
||||||
# go in a p4 client
|
# go in a p4 client
|
||||||
if self.useClientSpec:
|
if self.useClientSpec:
|
||||||
relPath = self.clientSpecDirs.map_in_client(path)
|
relPath = decode_path(self.clientSpecDirs.map_in_client(path))
|
||||||
else:
|
else:
|
||||||
relPath = self.stripRepoPath(path, self.depotPaths)
|
relPath = self.stripRepoPath(path, self.depotPaths)
|
||||||
|
|
||||||
@ -2769,7 +2805,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
return branches
|
return branches
|
||||||
|
|
||||||
def writeToGitStream(self, gitMode, relPath, contents):
|
def writeToGitStream(self, gitMode, relPath, contents):
|
||||||
self.gitStream.write('M %s inline %s\n' % (gitMode, relPath))
|
self.gitStream.write(encode_text_stream(u'M {} inline {}\n'.format(gitMode, relPath)))
|
||||||
self.gitStream.write('data %d\n' % sum(len(d) for d in contents))
|
self.gitStream.write('data %d\n' % sum(len(d) for d in contents))
|
||||||
for d in contents:
|
for d in contents:
|
||||||
self.gitStream.write(d)
|
self.gitStream.write(d)
|
||||||
@ -2791,14 +2827,15 @@ class P4Sync(Command, P4UserMap):
|
|||||||
# - helper for streamP4Files
|
# - helper for streamP4Files
|
||||||
|
|
||||||
def streamOneP4File(self, file, contents):
|
def streamOneP4File(self, file, contents):
|
||||||
relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
|
file_path = file['depotFile']
|
||||||
relPath = self.encodeWithUTF8(relPath)
|
relPath = self.stripRepoPath(decode_path(file_path), self.branchPrefixes)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
if 'fileSize' in self.stream_file:
|
if 'fileSize' in self.stream_file:
|
||||||
size = int(self.stream_file['fileSize'])
|
size = int(self.stream_file['fileSize'])
|
||||||
else:
|
else:
|
||||||
size = 0 # deleted files don't get a fileSize apparently
|
size = 0 # deleted files don't get a fileSize apparently
|
||||||
sys.stdout.write('\r%s --> %s (%i MB)\n' % (file['depotFile'], relPath, size/1024/1024))
|
sys.stdout.write('\r%s --> %s (%i MB)\n' % (file_path, relPath, size/1024/1024))
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
(type_base, type_mods) = split_p4_type(file["type"])
|
(type_base, type_mods) = split_p4_type(file["type"])
|
||||||
@ -2810,13 +2847,13 @@ class P4Sync(Command, P4UserMap):
|
|||||||
git_mode = "120000"
|
git_mode = "120000"
|
||||||
# p4 print on a symlink sometimes contains "target\n";
|
# p4 print on a symlink sometimes contains "target\n";
|
||||||
# if it does, remove the newline
|
# if it does, remove the newline
|
||||||
data = ''.join(contents)
|
data = ''.join(decode_text_stream(c) for c in contents)
|
||||||
if not data:
|
if not data:
|
||||||
# Some version of p4 allowed creating a symlink that pointed
|
# Some version of p4 allowed creating a symlink that pointed
|
||||||
# to nothing. This causes p4 errors when checking out such
|
# to nothing. This causes p4 errors when checking out such
|
||||||
# a change, and errors here too. Work around it by ignoring
|
# a change, and errors here too. Work around it by ignoring
|
||||||
# the bad symlink; hopefully a future change fixes it.
|
# the bad symlink; hopefully a future change fixes it.
|
||||||
print("\nIgnoring empty symlink in %s" % file['depotFile'])
|
print("\nIgnoring empty symlink in %s" % file_path)
|
||||||
return
|
return
|
||||||
elif data[-1] == '\n':
|
elif data[-1] == '\n':
|
||||||
contents = [data[:-1]]
|
contents = [data[:-1]]
|
||||||
@ -2835,7 +2872,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
# just the native "NT" type.
|
# just the native "NT" type.
|
||||||
#
|
#
|
||||||
try:
|
try:
|
||||||
text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (file['depotFile'], file['change'])])
|
text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (decode_path(file['depotFile']), file['change'])], raw=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if 'Translation of file content failed' in str(e):
|
if 'Translation of file content failed' in str(e):
|
||||||
type_base = 'binary'
|
type_base = 'binary'
|
||||||
@ -2843,7 +2880,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
if p4_version_string().find('/NT') >= 0:
|
if p4_version_string().find('/NT') >= 0:
|
||||||
text = text.replace('\r\n', '\n')
|
text = text.replace(b'\r\n', b'\n')
|
||||||
contents = [ text ]
|
contents = [ text ]
|
||||||
|
|
||||||
if type_base == "apple":
|
if type_base == "apple":
|
||||||
@ -2864,7 +2901,7 @@ class P4Sync(Command, P4UserMap):
|
|||||||
pattern = p4_keywords_regexp_for_type(type_base, type_mods)
|
pattern = p4_keywords_regexp_for_type(type_base, type_mods)
|
||||||
if pattern:
|
if pattern:
|
||||||
regexp = re.compile(pattern, re.VERBOSE)
|
regexp = re.compile(pattern, re.VERBOSE)
|
||||||
text = ''.join(contents)
|
text = ''.join(decode_text_stream(c) for c in contents)
|
||||||
text = regexp.sub(r'$\1$', text)
|
text = regexp.sub(r'$\1$', text)
|
||||||
contents = [ text ]
|
contents = [ text ]
|
||||||
|
|
||||||
@ -2874,12 +2911,11 @@ class P4Sync(Command, P4UserMap):
|
|||||||
self.writeToGitStream(git_mode, relPath, contents)
|
self.writeToGitStream(git_mode, relPath, contents)
|
||||||
|
|
||||||
def streamOneP4Deletion(self, file):
|
def streamOneP4Deletion(self, file):
|
||||||
relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
|
relPath = self.stripRepoPath(decode_path(file['path']), self.branchPrefixes)
|
||||||
relPath = self.encodeWithUTF8(relPath)
|
|
||||||
if verbose:
|
if verbose:
|
||||||
sys.stdout.write("delete %s\n" % relPath)
|
sys.stdout.write("delete %s\n" % relPath)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
self.gitStream.write("D %s\n" % relPath)
|
self.gitStream.write(encode_text_stream(u'D {}\n'.format(relPath)))
|
||||||
|
|
||||||
if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
|
if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
|
||||||
self.largeFileSystem.removeLargeFile(relPath)
|
self.largeFileSystem.removeLargeFile(relPath)
|
||||||
@ -2979,9 +3015,9 @@ class P4Sync(Command, P4UserMap):
|
|||||||
if 'shelved_cl' in f:
|
if 'shelved_cl' in f:
|
||||||
# Handle shelved CLs using the "p4 print file@=N" syntax to print
|
# Handle shelved CLs using the "p4 print file@=N" syntax to print
|
||||||
# the contents
|
# the contents
|
||||||
fileArg = '%s@=%d' % (f['path'], f['shelved_cl'])
|
fileArg = f['path'] + encode_text_stream('@={}'.format(f['shelved_cl']))
|
||||||
else:
|
else:
|
||||||
fileArg = '%s#%s' % (f['path'], f['rev'])
|
fileArg = f['path'] + encode_text_stream('#{}'.format(f['rev']))
|
||||||
|
|
||||||
fileArgs.append(fileArg)
|
fileArgs.append(fileArg)
|
||||||
|
|
||||||
@ -3062,8 +3098,8 @@ class P4Sync(Command, P4UserMap):
|
|||||||
if self.clientSpecDirs:
|
if self.clientSpecDirs:
|
||||||
self.clientSpecDirs.update_client_spec_path_cache(files)
|
self.clientSpecDirs.update_client_spec_path_cache(files)
|
||||||
|
|
||||||
files = [f for f in files
|
files = [f for (f, path) in ((f, decode_path(f['path'])) for f in files)
|
||||||
if self.inClientSpec(f['path']) and self.hasBranchPrefix(f['path'])]
|
if self.inClientSpec(path) and self.hasBranchPrefix(path)]
|
||||||
|
|
||||||
if gitConfigBool('git-p4.keepEmptyCommits'):
|
if gitConfigBool('git-p4.keepEmptyCommits'):
|
||||||
allow_empty = True
|
allow_empty = True
|
||||||
@ -3635,6 +3671,15 @@ class P4Sync(Command, P4UserMap):
|
|||||||
self.gitStream = self.importProcess.stdin
|
self.gitStream = self.importProcess.stdin
|
||||||
self.gitError = self.importProcess.stderr
|
self.gitError = self.importProcess.stderr
|
||||||
|
|
||||||
|
if bytes is not str:
|
||||||
|
# Wrap gitStream.write() so that it can be called using `str` arguments
|
||||||
|
def make_encoded_write(write):
|
||||||
|
def encoded_write(s):
|
||||||
|
return write(s.encode() if isinstance(s, str) else s)
|
||||||
|
return encoded_write
|
||||||
|
|
||||||
|
self.gitStream.write = make_encoded_write(self.gitStream.write)
|
||||||
|
|
||||||
def closeStreams(self):
|
def closeStreams(self):
|
||||||
if self.gitStream is None:
|
if self.gitStream is None:
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user