зеркало из https://github.com/microsoft/git.git
git-p4: add blank lines between functions and class definitions
In the PEP8 style guidelines, top-level functions and class definitions should be separated by two blank lines. Methods should be surrounded by a single blank line. This guideline is described here in the "Blank Lines" section: https://www.python.org/dev/peps/pep-0008/#blank-lines Signed-off-by: Joel Holdsworth <jholdsworth@nvidia.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Родитель
2b9c120970
Коммит
adf159b441
107
git-p4.py
107
git-p4.py
|
@ -59,6 +59,7 @@ p4_access_checked = False
|
||||||
re_ko_keywords = re.compile(br'\$(Id|Header)(:[^$\n]+)?\$')
|
re_ko_keywords = re.compile(br'\$(Id|Header)(:[^$\n]+)?\$')
|
||||||
re_k_keywords = re.compile(br'\$(Id|Header|Author|Date|DateTime|Change|File|Revision)(:[^$\n]+)?\$')
|
re_k_keywords = re.compile(br'\$(Id|Header|Author|Date|DateTime|Change|File|Revision)(:[^$\n]+)?\$')
|
||||||
|
|
||||||
|
|
||||||
def format_size_human_readable(num):
|
def format_size_human_readable(num):
|
||||||
""" Returns a number of units (typically bytes) formatted as a human-readable
|
""" Returns a number of units (typically bytes) formatted as a human-readable
|
||||||
string.
|
string.
|
||||||
|
@ -71,6 +72,7 @@ def format_size_human_readable(num):
|
||||||
return "{:3.1f} {}B".format(num, unit)
|
return "{:3.1f} {}B".format(num, unit)
|
||||||
return "{:.1f} YiB".format(num)
|
return "{:.1f} YiB".format(num)
|
||||||
|
|
||||||
|
|
||||||
def p4_build_cmd(cmd):
|
def p4_build_cmd(cmd):
|
||||||
"""Build a suitable p4 command line.
|
"""Build a suitable p4 command line.
|
||||||
|
|
||||||
|
@ -118,6 +120,7 @@ def p4_build_cmd(cmd):
|
||||||
|
|
||||||
return real_cmd
|
return real_cmd
|
||||||
|
|
||||||
|
|
||||||
def git_dir(path):
|
def git_dir(path):
|
||||||
""" Return TRUE if the given path is a git directory (/path/to/dir/.git).
|
""" Return TRUE if the given path is a git directory (/path/to/dir/.git).
|
||||||
This won't automatically add ".git" to a directory.
|
This won't automatically add ".git" to a directory.
|
||||||
|
@ -128,6 +131,7 @@ def git_dir(path):
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def chdir(path, is_client_path=False):
|
def chdir(path, is_client_path=False):
|
||||||
"""Do chdir to the given path, and set the PWD environment
|
"""Do chdir to the given path, and set the PWD environment
|
||||||
variable for use by P4. It does not look at getcwd() output.
|
variable for use by P4. It does not look at getcwd() output.
|
||||||
|
@ -150,6 +154,7 @@ def chdir(path, is_client_path=False):
|
||||||
path = os.getcwd()
|
path = os.getcwd()
|
||||||
os.environ['PWD'] = path
|
os.environ['PWD'] = path
|
||||||
|
|
||||||
|
|
||||||
def calcDiskFree():
|
def calcDiskFree():
|
||||||
"""Return free space in bytes on the disk of the given dirname."""
|
"""Return free space in bytes on the disk of the given dirname."""
|
||||||
if platform.system() == 'Windows':
|
if platform.system() == 'Windows':
|
||||||
|
@ -160,6 +165,7 @@ def calcDiskFree():
|
||||||
st = os.statvfs(os.getcwd())
|
st = os.statvfs(os.getcwd())
|
||||||
return st.f_bavail * st.f_frsize
|
return st.f_bavail * st.f_frsize
|
||||||
|
|
||||||
|
|
||||||
def die(msg):
|
def die(msg):
|
||||||
""" Terminate execution. Make sure that any running child processes have been wait()ed for before
|
""" Terminate execution. Make sure that any running child processes have been wait()ed for before
|
||||||
calling this.
|
calling this.
|
||||||
|
@ -170,6 +176,7 @@ def die(msg):
|
||||||
sys.stderr.write(msg + "\n")
|
sys.stderr.write(msg + "\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def prompt(prompt_text):
|
def prompt(prompt_text):
|
||||||
""" Prompt the user to choose one of the choices
|
""" Prompt the user to choose one of the choices
|
||||||
|
|
||||||
|
@ -188,21 +195,25 @@ def prompt(prompt_text):
|
||||||
if response in choices:
|
if response in choices:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
# We need different encoding/decoding strategies for text data being passed
|
# We need different encoding/decoding strategies for text data being passed
|
||||||
# around in pipes depending on python version
|
# around in pipes depending on python version
|
||||||
if bytes is not str:
|
if bytes is not str:
|
||||||
# For python3, always encode and decode as appropriate
|
# For python3, always encode and decode as appropriate
|
||||||
def decode_text_stream(s):
|
def decode_text_stream(s):
|
||||||
return s.decode() if isinstance(s, bytes) else s
|
return s.decode() if isinstance(s, bytes) else s
|
||||||
|
|
||||||
def encode_text_stream(s):
|
def encode_text_stream(s):
|
||||||
return s.encode() if isinstance(s, str) else s
|
return s.encode() if isinstance(s, str) else s
|
||||||
else:
|
else:
|
||||||
# For python2.7, pass read strings as-is, but also allow writing unicode
|
# For python2.7, pass read strings as-is, but also allow writing unicode
|
||||||
def decode_text_stream(s):
|
def decode_text_stream(s):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def encode_text_stream(s):
|
def encode_text_stream(s):
|
||||||
return s.encode('utf_8') if isinstance(s, unicode) else s
|
return s.encode('utf_8') if isinstance(s, unicode) else s
|
||||||
|
|
||||||
|
|
||||||
def decode_path(path):
|
def decode_path(path):
|
||||||
"""Decode a given string (bytes or otherwise) using configured path encoding options
|
"""Decode a given string (bytes or otherwise) using configured path encoding options
|
||||||
"""
|
"""
|
||||||
|
@ -218,6 +229,7 @@ def decode_path(path):
|
||||||
print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path))
|
print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path))
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def run_git_hook(cmd, param=[]):
|
def run_git_hook(cmd, param=[]):
|
||||||
"""Execute a hook if the hook exists."""
|
"""Execute a hook if the hook exists."""
|
||||||
args = ['git', 'hook', 'run', '--ignore-missing', cmd]
|
args = ['git', 'hook', 'run', '--ignore-missing', cmd]
|
||||||
|
@ -227,6 +239,7 @@ def run_git_hook(cmd, param=[]):
|
||||||
args.append(p)
|
args.append(p)
|
||||||
return subprocess.call(args) == 0
|
return subprocess.call(args) == 0
|
||||||
|
|
||||||
|
|
||||||
def write_pipe(c, stdin, *k, **kw):
|
def write_pipe(c, stdin, *k, **kw):
|
||||||
if verbose:
|
if verbose:
|
||||||
sys.stderr.write('Writing pipe: {}\n'.format(' '.join(c)))
|
sys.stderr.write('Writing pipe: {}\n'.format(' '.join(c)))
|
||||||
|
@ -240,12 +253,14 @@ def write_pipe(c, stdin, *k, **kw):
|
||||||
|
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def p4_write_pipe(c, stdin, *k, **kw):
|
def p4_write_pipe(c, stdin, *k, **kw):
|
||||||
real_cmd = p4_build_cmd(c)
|
real_cmd = p4_build_cmd(c)
|
||||||
if bytes is not str and isinstance(stdin, str):
|
if bytes is not str and isinstance(stdin, str):
|
||||||
stdin = encode_text_stream(stdin)
|
stdin = encode_text_stream(stdin)
|
||||||
return write_pipe(real_cmd, stdin, *k, **kw)
|
return write_pipe(real_cmd, stdin, *k, **kw)
|
||||||
|
|
||||||
|
|
||||||
def read_pipe_full(c, *k, **kw):
|
def read_pipe_full(c, *k, **kw):
|
||||||
""" Read output from command. Returns a tuple
|
""" Read output from command. Returns a tuple
|
||||||
of the return status, stdout text and stderr
|
of the return status, stdout text and stderr
|
||||||
|
@ -259,6 +274,7 @@ def read_pipe_full(c, *k, **kw):
|
||||||
(out, err) = p.communicate()
|
(out, err) = p.communicate()
|
||||||
return (p.returncode, out, decode_text_stream(err))
|
return (p.returncode, out, decode_text_stream(err))
|
||||||
|
|
||||||
|
|
||||||
def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
|
def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
|
||||||
""" 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
|
||||||
|
@ -276,6 +292,7 @@ def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
|
||||||
out = decode_text_stream(out)
|
out = decode_text_stream(out)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def read_pipe_text(c, *k, **kw):
|
def read_pipe_text(c, *k, **kw):
|
||||||
""" Read output from a command with trailing whitespace stripped.
|
""" Read output from a command with trailing whitespace stripped.
|
||||||
On error, returns None.
|
On error, returns None.
|
||||||
|
@ -286,10 +303,12 @@ def read_pipe_text(c, *k, **kw):
|
||||||
else:
|
else:
|
||||||
return decode_text_stream(out).rstrip()
|
return decode_text_stream(out).rstrip()
|
||||||
|
|
||||||
|
|
||||||
def p4_read_pipe(c, ignore_error=False, raw=False, *k, **kw):
|
def p4_read_pipe(c, ignore_error=False, raw=False, *k, **kw):
|
||||||
real_cmd = p4_build_cmd(c)
|
real_cmd = p4_build_cmd(c)
|
||||||
return read_pipe(real_cmd, ignore_error, raw=raw, *k, **kw)
|
return read_pipe(real_cmd, ignore_error, raw=raw, *k, **kw)
|
||||||
|
|
||||||
|
|
||||||
def read_pipe_lines(c, raw=False, *k, **kw):
|
def read_pipe_lines(c, raw=False, *k, **kw):
|
||||||
if verbose:
|
if verbose:
|
||||||
sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
|
sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
|
||||||
|
@ -303,11 +322,13 @@ def read_pipe_lines(c, raw=False, *k, **kw):
|
||||||
die('Command failed: {}'.format(' '.join(c)))
|
die('Command failed: {}'.format(' '.join(c)))
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def p4_read_pipe_lines(c, *k, **kw):
|
def p4_read_pipe_lines(c, *k, **kw):
|
||||||
"""Specifically invoke p4 on the command supplied. """
|
"""Specifically invoke p4 on the command supplied. """
|
||||||
real_cmd = p4_build_cmd(c)
|
real_cmd = p4_build_cmd(c)
|
||||||
return read_pipe_lines(real_cmd, *k, **kw)
|
return read_pipe_lines(real_cmd, *k, **kw)
|
||||||
|
|
||||||
|
|
||||||
def p4_has_command(cmd):
|
def p4_has_command(cmd):
|
||||||
"""Ask p4 for help on this command. If it returns an error, the
|
"""Ask p4 for help on this command. If it returns an error, the
|
||||||
command does not exist in this version of p4."""
|
command does not exist in this version of p4."""
|
||||||
|
@ -317,6 +338,7 @@ def p4_has_command(cmd):
|
||||||
p.communicate()
|
p.communicate()
|
||||||
return p.returncode == 0
|
return p.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
def p4_has_move_command():
|
def p4_has_move_command():
|
||||||
"""See if the move command exists, that it supports -k, and that
|
"""See if the move command exists, that it supports -k, and that
|
||||||
it has not been administratively disabled. The arguments
|
it has not been administratively disabled. The arguments
|
||||||
|
@ -337,6 +359,7 @@ def p4_has_move_command():
|
||||||
# assume it failed because @... was invalid changelist
|
# assume it failed because @... was invalid changelist
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def system(cmd, ignore_error=False, *k, **kw):
|
def system(cmd, ignore_error=False, *k, **kw):
|
||||||
if verbose:
|
if verbose:
|
||||||
sys.stderr.write("executing {}\n".format(
|
sys.stderr.write("executing {}\n".format(
|
||||||
|
@ -347,6 +370,7 @@ def system(cmd, ignore_error=False, *k, **kw):
|
||||||
|
|
||||||
return retcode
|
return retcode
|
||||||
|
|
||||||
|
|
||||||
def p4_system(cmd, *k, **kw):
|
def p4_system(cmd, *k, **kw):
|
||||||
"""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)
|
||||||
|
@ -354,9 +378,11 @@ def p4_system(cmd, *k, **kw):
|
||||||
if retcode:
|
if retcode:
|
||||||
raise subprocess.CalledProcessError(retcode, real_cmd)
|
raise subprocess.CalledProcessError(retcode, real_cmd)
|
||||||
|
|
||||||
|
|
||||||
def die_bad_access(s):
|
def die_bad_access(s):
|
||||||
die("failure accessing depot: {0}".format(s.rstrip()))
|
die("failure accessing depot: {0}".format(s.rstrip()))
|
||||||
|
|
||||||
|
|
||||||
def p4_check_access(min_expiration=1):
|
def p4_check_access(min_expiration=1):
|
||||||
""" Check if we can access Perforce - account still logged in
|
""" Check if we can access Perforce - account still logged in
|
||||||
"""
|
"""
|
||||||
|
@ -402,7 +428,10 @@ def p4_check_access(min_expiration=1):
|
||||||
else:
|
else:
|
||||||
die_bad_access("unknown error code {0}".format(code))
|
die_bad_access("unknown error code {0}".format(code))
|
||||||
|
|
||||||
|
|
||||||
_p4_version_string = None
|
_p4_version_string = None
|
||||||
|
|
||||||
|
|
||||||
def p4_version_string():
|
def p4_version_string():
|
||||||
"""Read the version string, showing just the last line, which
|
"""Read the version string, showing just the last line, which
|
||||||
hopefully is the interesting version bit.
|
hopefully is the interesting version bit.
|
||||||
|
@ -418,12 +447,15 @@ def p4_version_string():
|
||||||
_p4_version_string = a[-1].rstrip()
|
_p4_version_string = a[-1].rstrip()
|
||||||
return _p4_version_string
|
return _p4_version_string
|
||||||
|
|
||||||
|
|
||||||
def p4_integrate(src, dest):
|
def p4_integrate(src, dest):
|
||||||
p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
|
p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
|
||||||
|
|
||||||
|
|
||||||
def p4_sync(f, *options):
|
def p4_sync(f, *options):
|
||||||
p4_system(["sync"] + list(options) + [wildcard_encode(f)])
|
p4_system(["sync"] + list(options) + [wildcard_encode(f)])
|
||||||
|
|
||||||
|
|
||||||
def p4_add(f):
|
def p4_add(f):
|
||||||
# forcibly add file names with wildcards
|
# forcibly add file names with wildcards
|
||||||
if wildcard_present(f):
|
if wildcard_present(f):
|
||||||
|
@ -431,29 +463,37 @@ def p4_add(f):
|
||||||
else:
|
else:
|
||||||
p4_system(["add", f])
|
p4_system(["add", f])
|
||||||
|
|
||||||
|
|
||||||
def p4_delete(f):
|
def p4_delete(f):
|
||||||
p4_system(["delete", wildcard_encode(f)])
|
p4_system(["delete", wildcard_encode(f)])
|
||||||
|
|
||||||
|
|
||||||
def p4_edit(f, *options):
|
def p4_edit(f, *options):
|
||||||
p4_system(["edit"] + list(options) + [wildcard_encode(f)])
|
p4_system(["edit"] + list(options) + [wildcard_encode(f)])
|
||||||
|
|
||||||
|
|
||||||
def p4_revert(f):
|
def p4_revert(f):
|
||||||
p4_system(["revert", wildcard_encode(f)])
|
p4_system(["revert", wildcard_encode(f)])
|
||||||
|
|
||||||
|
|
||||||
def p4_reopen(type, f):
|
def p4_reopen(type, f):
|
||||||
p4_system(["reopen", "-t", type, wildcard_encode(f)])
|
p4_system(["reopen", "-t", type, wildcard_encode(f)])
|
||||||
|
|
||||||
|
|
||||||
def p4_reopen_in_change(changelist, files):
|
def p4_reopen_in_change(changelist, files):
|
||||||
cmd = ["reopen", "-c", str(changelist)] + files
|
cmd = ["reopen", "-c", str(changelist)] + files
|
||||||
p4_system(cmd)
|
p4_system(cmd)
|
||||||
|
|
||||||
|
|
||||||
def p4_move(src, dest):
|
def p4_move(src, dest):
|
||||||
p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
|
p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
|
||||||
|
|
||||||
|
|
||||||
def p4_last_change():
|
def p4_last_change():
|
||||||
results = p4CmdList(["changes", "-m", "1"], skip_info=True)
|
results = p4CmdList(["changes", "-m", "1"], skip_info=True)
|
||||||
return int(results[0]['change'])
|
return int(results[0]['change'])
|
||||||
|
|
||||||
|
|
||||||
def p4_describe(change, shelved=False):
|
def p4_describe(change, shelved=False):
|
||||||
"""Make sure it returns a valid result by checking for
|
"""Make sure it returns a valid result by checking for
|
||||||
the presence of field "time". Return a dict of the
|
the presence of field "time". Return a dict of the
|
||||||
|
@ -482,6 +522,7 @@ def p4_describe(change, shelved=False):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Canonicalize the p4 type and return a tuple of the
|
# Canonicalize the p4 type and return a tuple of the
|
||||||
# base type, plus any modifiers. See "p4 help filetypes"
|
# base type, plus any modifiers. See "p4 help filetypes"
|
||||||
|
@ -517,6 +558,7 @@ def split_p4_type(p4type):
|
||||||
mods = s[1]
|
mods = s[1]
|
||||||
return (base, mods)
|
return (base, mods)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# return the raw p4 type of a file (text, text+ko, etc)
|
# return the raw p4 type of a file (text, text+ko, etc)
|
||||||
#
|
#
|
||||||
|
@ -524,6 +566,7 @@ def p4_type(f):
|
||||||
results = p4CmdList(["fstat", "-T", "headType", wildcard_encode(f)])
|
results = p4CmdList(["fstat", "-T", "headType", wildcard_encode(f)])
|
||||||
return results[0]['headType']
|
return results[0]['headType']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Given a type base and modifier, return a regexp matching
|
# Given a type base and modifier, return a regexp matching
|
||||||
# the keywords that can be expanded in the file
|
# the keywords that can be expanded in the file
|
||||||
|
@ -539,6 +582,7 @@ def p4_keywords_regexp_for_type(base, type_mods):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Given a file, return a regexp matching the possible
|
# Given a file, return a regexp matching the possible
|
||||||
# RCS keywords that will be expanded, or None for files
|
# RCS keywords that will be expanded, or None for files
|
||||||
|
@ -551,6 +595,7 @@ def p4_keywords_regexp_for_file(file):
|
||||||
(type_base, type_mods) = split_p4_type(p4_type(file))
|
(type_base, type_mods) = split_p4_type(p4_type(file))
|
||||||
return p4_keywords_regexp_for_type(type_base, type_mods)
|
return p4_keywords_regexp_for_type(type_base, type_mods)
|
||||||
|
|
||||||
|
|
||||||
def setP4ExecBit(file, mode):
|
def setP4ExecBit(file, mode):
|
||||||
# Reopens an already open file and changes the execute bit to match
|
# Reopens an already open file and changes the execute bit to match
|
||||||
# the execute bit setting in the passed in mode.
|
# the execute bit setting in the passed in mode.
|
||||||
|
@ -566,6 +611,7 @@ def setP4ExecBit(file, mode):
|
||||||
|
|
||||||
p4_reopen(p4Type, file)
|
p4_reopen(p4Type, file)
|
||||||
|
|
||||||
|
|
||||||
def getP4OpenedType(file):
|
def getP4OpenedType(file):
|
||||||
# Returns the perforce file type for the given file.
|
# Returns the perforce file type for the given file.
|
||||||
|
|
||||||
|
@ -576,6 +622,7 @@ def getP4OpenedType(file):
|
||||||
else:
|
else:
|
||||||
die("Could not determine file type for %s (result: '%s')" % (file, result))
|
die("Could not determine file type for %s (result: '%s')" % (file, result))
|
||||||
|
|
||||||
|
|
||||||
# Return the set of all p4 labels
|
# Return the set of all p4 labels
|
||||||
def getP4Labels(depotPaths):
|
def getP4Labels(depotPaths):
|
||||||
labels = set()
|
labels = set()
|
||||||
|
@ -588,6 +635,7 @@ def getP4Labels(depotPaths):
|
||||||
|
|
||||||
return labels
|
return labels
|
||||||
|
|
||||||
|
|
||||||
# Return the set of all git tags
|
# Return the set of all git tags
|
||||||
def getGitTags():
|
def getGitTags():
|
||||||
gitTags = set()
|
gitTags = set()
|
||||||
|
@ -596,8 +644,10 @@ def getGitTags():
|
||||||
gitTags.add(tag)
|
gitTags.add(tag)
|
||||||
return gitTags
|
return gitTags
|
||||||
|
|
||||||
|
|
||||||
_diff_tree_pattern = None
|
_diff_tree_pattern = None
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@ -635,41 +685,52 @@ def parseDiffTreeEntry(entry):
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def isModeExec(mode):
|
def isModeExec(mode):
|
||||||
# Returns True if the given git mode represents an executable file,
|
# Returns True if the given git mode represents an executable file,
|
||||||
# otherwise False.
|
# otherwise False.
|
||||||
return mode[-3:] == "755"
|
return mode[-3:] == "755"
|
||||||
|
|
||||||
|
|
||||||
class P4Exception(Exception):
|
class P4Exception(Exception):
|
||||||
""" Base class for exceptions from the p4 client """
|
""" Base class for exceptions from the p4 client """
|
||||||
|
|
||||||
def __init__(self, exit_code):
|
def __init__(self, exit_code):
|
||||||
self.p4ExitCode = exit_code
|
self.p4ExitCode = exit_code
|
||||||
|
|
||||||
|
|
||||||
class P4ServerException(P4Exception):
|
class P4ServerException(P4Exception):
|
||||||
""" Base class for exceptions where we get some kind of marshalled up result from the server """
|
""" Base class for exceptions where we get some kind of marshalled up result from the server """
|
||||||
|
|
||||||
def __init__(self, exit_code, p4_result):
|
def __init__(self, exit_code, p4_result):
|
||||||
super(P4ServerException, self).__init__(exit_code)
|
super(P4ServerException, self).__init__(exit_code)
|
||||||
self.p4_result = p4_result
|
self.p4_result = p4_result
|
||||||
self.code = p4_result[0]['code']
|
self.code = p4_result[0]['code']
|
||||||
self.data = p4_result[0]['data']
|
self.data = p4_result[0]['data']
|
||||||
|
|
||||||
|
|
||||||
class P4RequestSizeException(P4ServerException):
|
class P4RequestSizeException(P4ServerException):
|
||||||
""" One of the maxresults or maxscanrows errors """
|
""" One of the maxresults or maxscanrows errors """
|
||||||
|
|
||||||
def __init__(self, exit_code, p4_result, limit):
|
def __init__(self, exit_code, p4_result, limit):
|
||||||
super(P4RequestSizeException, self).__init__(exit_code, p4_result)
|
super(P4RequestSizeException, self).__init__(exit_code, p4_result)
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
|
|
||||||
|
|
||||||
class P4CommandException(P4Exception):
|
class P4CommandException(P4Exception):
|
||||||
""" Something went wrong calling p4 which means we have to give up """
|
""" Something went wrong calling p4 which means we have to give up """
|
||||||
|
|
||||||
def __init__(self, msg):
|
def __init__(self, msg):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.msg
|
return self.msg
|
||||||
|
|
||||||
|
|
||||||
def isModeExecChanged(src_mode, dst_mode):
|
def isModeExecChanged(src_mode, dst_mode):
|
||||||
return isModeExec(src_mode) != isModeExec(dst_mode)
|
return isModeExec(src_mode) != isModeExec(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, *k, **kw):
|
errors_as_exceptions=False, *k, **kw):
|
||||||
|
|
||||||
|
@ -746,6 +807,7 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def p4Cmd(cmd, *k, **kw):
|
def p4Cmd(cmd, *k, **kw):
|
||||||
list = p4CmdList(cmd, *k, **kw)
|
list = p4CmdList(cmd, *k, **kw)
|
||||||
result = {}
|
result = {}
|
||||||
|
@ -753,6 +815,7 @@ def p4Cmd(cmd, *k, **kw):
|
||||||
result.update(entry)
|
result.update(entry)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
|
||||||
def p4Where(depotPath):
|
def p4Where(depotPath):
|
||||||
if not depotPath.endswith("/"):
|
if not depotPath.endswith("/"):
|
||||||
depotPath += "/"
|
depotPath += "/"
|
||||||
|
@ -789,20 +852,25 @@ def p4Where(depotPath):
|
||||||
clientPath = clientPath[:-3]
|
clientPath = clientPath[:-3]
|
||||||
return clientPath
|
return clientPath
|
||||||
|
|
||||||
|
|
||||||
def currentGitBranch():
|
def currentGitBranch():
|
||||||
return read_pipe_text(["git", "symbolic-ref", "--short", "-q", "HEAD"])
|
return read_pipe_text(["git", "symbolic-ref", "--short", "-q", "HEAD"])
|
||||||
|
|
||||||
|
|
||||||
def isValidGitDir(path):
|
def isValidGitDir(path):
|
||||||
return git_dir(path) != None
|
return git_dir(path) != None
|
||||||
|
|
||||||
|
|
||||||
def parseRevision(ref):
|
def parseRevision(ref):
|
||||||
return read_pipe(["git", "rev-parse", ref]).strip()
|
return read_pipe(["git", "rev-parse", ref]).strip()
|
||||||
|
|
||||||
|
|
||||||
def branchExists(ref):
|
def branchExists(ref):
|
||||||
rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
|
rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
|
||||||
ignore_error=True)
|
ignore_error=True)
|
||||||
return len(rev) > 0
|
return len(rev) > 0
|
||||||
|
|
||||||
|
|
||||||
def extractLogMessageFromGitCommit(commit):
|
def extractLogMessageFromGitCommit(commit):
|
||||||
logMessage = ""
|
logMessage = ""
|
||||||
|
|
||||||
|
@ -817,6 +885,7 @@ def extractLogMessageFromGitCommit(commit):
|
||||||
logMessage += log
|
logMessage += log
|
||||||
return logMessage
|
return logMessage
|
||||||
|
|
||||||
|
|
||||||
def extractSettingsGitLog(log):
|
def extractSettingsGitLog(log):
|
||||||
values = {}
|
values = {}
|
||||||
for line in log.split("\n"):
|
for line in log.split("\n"):
|
||||||
|
@ -842,19 +911,24 @@ def extractSettingsGitLog(log):
|
||||||
values['depot-paths'] = paths.split(',')
|
values['depot-paths'] = paths.split(',')
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
def gitBranchExists(branch):
|
def gitBranchExists(branch):
|
||||||
proc = subprocess.Popen(["git", "rev-parse", branch],
|
proc = subprocess.Popen(["git", "rev-parse", branch],
|
||||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE);
|
stderr=subprocess.PIPE, stdout=subprocess.PIPE);
|
||||||
return proc.wait() == 0;
|
return proc.wait() == 0;
|
||||||
|
|
||||||
|
|
||||||
def gitUpdateRef(ref, newvalue):
|
def gitUpdateRef(ref, newvalue):
|
||||||
subprocess.check_call(["git", "update-ref", ref, newvalue])
|
subprocess.check_call(["git", "update-ref", ref, newvalue])
|
||||||
|
|
||||||
|
|
||||||
def gitDeleteRef(ref):
|
def gitDeleteRef(ref):
|
||||||
subprocess.check_call(["git", "update-ref", "-d", ref])
|
subprocess.check_call(["git", "update-ref", "-d", ref])
|
||||||
|
|
||||||
|
|
||||||
_gitConfig = {}
|
_gitConfig = {}
|
||||||
|
|
||||||
|
|
||||||
def gitConfig(key, typeSpecifier=None):
|
def gitConfig(key, typeSpecifier=None):
|
||||||
if key not in _gitConfig:
|
if key not in _gitConfig:
|
||||||
cmd = [ "git", "config" ]
|
cmd = [ "git", "config" ]
|
||||||
|
@ -865,6 +939,7 @@ def gitConfig(key, typeSpecifier=None):
|
||||||
_gitConfig[key] = s.strip()
|
_gitConfig[key] = s.strip()
|
||||||
return _gitConfig[key]
|
return _gitConfig[key]
|
||||||
|
|
||||||
|
|
||||||
def gitConfigBool(key):
|
def gitConfigBool(key):
|
||||||
"""Return a bool, using git config --bool. It is True only if the
|
"""Return a bool, using git config --bool. It is True only if the
|
||||||
variable is set to true, and False if set to false or not present
|
variable is set to true, and False if set to false or not present
|
||||||
|
@ -874,6 +949,7 @@ def gitConfigBool(key):
|
||||||
_gitConfig[key] = gitConfig(key, '--bool') == "true"
|
_gitConfig[key] = gitConfig(key, '--bool') == "true"
|
||||||
return _gitConfig[key]
|
return _gitConfig[key]
|
||||||
|
|
||||||
|
|
||||||
def gitConfigInt(key):
|
def gitConfigInt(key):
|
||||||
if key not in _gitConfig:
|
if key not in _gitConfig:
|
||||||
cmd = [ "git", "config", "--int", key ]
|
cmd = [ "git", "config", "--int", key ]
|
||||||
|
@ -885,6 +961,7 @@ def gitConfigInt(key):
|
||||||
_gitConfig[key] = None
|
_gitConfig[key] = None
|
||||||
return _gitConfig[key]
|
return _gitConfig[key]
|
||||||
|
|
||||||
|
|
||||||
def gitConfigList(key):
|
def gitConfigList(key):
|
||||||
if key not in _gitConfig:
|
if key not in _gitConfig:
|
||||||
s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
|
s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
|
||||||
|
@ -893,6 +970,7 @@ def gitConfigList(key):
|
||||||
_gitConfig[key] = []
|
_gitConfig[key] = []
|
||||||
return _gitConfig[key]
|
return _gitConfig[key]
|
||||||
|
|
||||||
|
|
||||||
def p4BranchesInGit(branchesAreInRemotes=True):
|
def p4BranchesInGit(branchesAreInRemotes=True):
|
||||||
"""Find all the branches whose names start with "p4/", looking
|
"""Find all the branches whose names start with "p4/", looking
|
||||||
in remotes or heads as specified by the argument. Return
|
in remotes or heads as specified by the argument. Return
|
||||||
|
@ -925,6 +1003,7 @@ def p4BranchesInGit(branchesAreInRemotes=True):
|
||||||
|
|
||||||
return branches
|
return branches
|
||||||
|
|
||||||
|
|
||||||
def branch_exists(branch):
|
def branch_exists(branch):
|
||||||
"""Make sure that the given ref name really exists."""
|
"""Make sure that the given ref name really exists."""
|
||||||
|
|
||||||
|
@ -937,6 +1016,7 @@ def branch_exists(branch):
|
||||||
# expect exactly one line of output: the branch name
|
# expect exactly one line of output: the branch name
|
||||||
return out.rstrip() == branch
|
return out.rstrip() == branch
|
||||||
|
|
||||||
|
|
||||||
def findUpstreamBranchPoint(head = "HEAD"):
|
def findUpstreamBranchPoint(head = "HEAD"):
|
||||||
branches = p4BranchesInGit()
|
branches = p4BranchesInGit()
|
||||||
# map from depot-path to branch name
|
# map from depot-path to branch name
|
||||||
|
@ -964,6 +1044,7 @@ def findUpstreamBranchPoint(head = "HEAD"):
|
||||||
|
|
||||||
return ["", settings]
|
return ["", settings]
|
||||||
|
|
||||||
|
|
||||||
def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
|
def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
|
||||||
if not silent:
|
if not silent:
|
||||||
print("Creating/updating branch(es) in %s based on origin branch(es)"
|
print("Creating/updating branch(es) in %s based on origin branch(es)"
|
||||||
|
@ -1011,6 +1092,7 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent
|
||||||
if update:
|
if update:
|
||||||
system(["git", "update-ref", remoteHead, originHead])
|
system(["git", "update-ref", remoteHead, originHead])
|
||||||
|
|
||||||
|
|
||||||
def originP4BranchesExist():
|
def originP4BranchesExist():
|
||||||
return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
|
return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
|
||||||
|
|
||||||
|
@ -1024,12 +1106,14 @@ def p4ParseNumericChangeRange(parts):
|
||||||
|
|
||||||
return (changeStart, changeEnd)
|
return (changeStart, changeEnd)
|
||||||
|
|
||||||
|
|
||||||
def chooseBlockSize(blockSize):
|
def chooseBlockSize(blockSize):
|
||||||
if blockSize:
|
if blockSize:
|
||||||
return blockSize
|
return blockSize
|
||||||
else:
|
else:
|
||||||
return defaultBlockSize
|
return defaultBlockSize
|
||||||
|
|
||||||
|
|
||||||
def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
|
def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
|
||||||
assert depotPaths
|
assert depotPaths
|
||||||
|
|
||||||
|
@ -1107,6 +1191,7 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
|
||||||
changes = sorted(changes)
|
changes = sorted(changes)
|
||||||
return changes
|
return changes
|
||||||
|
|
||||||
|
|
||||||
def p4PathStartsWith(path, prefix):
|
def p4PathStartsWith(path, prefix):
|
||||||
# This method tries to remedy a potential mixed-case issue:
|
# This method tries to remedy a potential mixed-case issue:
|
||||||
#
|
#
|
||||||
|
@ -1119,6 +1204,7 @@ def p4PathStartsWith(path, prefix):
|
||||||
return path.lower().startswith(prefix.lower())
|
return path.lower().startswith(prefix.lower())
|
||||||
return path.startswith(prefix)
|
return path.startswith(prefix)
|
||||||
|
|
||||||
|
|
||||||
def getClientSpec():
|
def getClientSpec():
|
||||||
"""Look at the p4 client spec, create a View() object that contains
|
"""Look at the p4 client spec, create a View() object that contains
|
||||||
all the mappings, and return it."""
|
all the mappings, and return it."""
|
||||||
|
@ -1149,6 +1235,7 @@ def getClientSpec():
|
||||||
|
|
||||||
return view
|
return view
|
||||||
|
|
||||||
|
|
||||||
def getClientRoot():
|
def getClientRoot():
|
||||||
"""Grab the client directory."""
|
"""Grab the client directory."""
|
||||||
|
|
||||||
|
@ -1162,6 +1249,7 @@ def getClientRoot():
|
||||||
|
|
||||||
return entry["Root"]
|
return entry["Root"]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# P4 wildcards are not allowed in filenames. P4 complains
|
# P4 wildcards are not allowed in filenames. P4 complains
|
||||||
# if you simply add them, but you can force it with "-f", in
|
# if you simply add them, but you can force it with "-f", in
|
||||||
|
@ -1179,6 +1267,7 @@ def wildcard_decode(path):
|
||||||
.replace("%25", "%")
|
.replace("%25", "%")
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def wildcard_encode(path):
|
def wildcard_encode(path):
|
||||||
# do % first to avoid double-encoding the %s introduced here
|
# do % first to avoid double-encoding the %s introduced here
|
||||||
path = path.replace("%", "%25") \
|
path = path.replace("%", "%25") \
|
||||||
|
@ -1187,10 +1276,12 @@ def wildcard_encode(path):
|
||||||
.replace("@", "%40")
|
.replace("@", "%40")
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def wildcard_present(path):
|
def wildcard_present(path):
|
||||||
m = re.search("[*#@%]", path)
|
m = re.search("[*#@%]", path)
|
||||||
return m is not None
|
return m is not None
|
||||||
|
|
||||||
|
|
||||||
class LargeFileSystem(object):
|
class LargeFileSystem(object):
|
||||||
"""Base class for large file system support."""
|
"""Base class for large file system support."""
|
||||||
|
|
||||||
|
@ -1272,6 +1363,7 @@ class LargeFileSystem(object):
|
||||||
sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile))
|
sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile))
|
||||||
return (git_mode, contents)
|
return (git_mode, contents)
|
||||||
|
|
||||||
|
|
||||||
class MockLFS(LargeFileSystem):
|
class MockLFS(LargeFileSystem):
|
||||||
"""Mock large file system for testing."""
|
"""Mock large file system for testing."""
|
||||||
|
|
||||||
|
@ -1295,6 +1387,7 @@ class MockLFS(LargeFileSystem):
|
||||||
os.makedirs(remotePath)
|
os.makedirs(remotePath)
|
||||||
shutil.copyfile(localLargeFile, os.path.join(remotePath, os.path.basename(localLargeFile)))
|
shutil.copyfile(localLargeFile, os.path.join(remotePath, os.path.basename(localLargeFile)))
|
||||||
|
|
||||||
|
|
||||||
class GitLFS(LargeFileSystem):
|
class GitLFS(LargeFileSystem):
|
||||||
"""Git LFS as backend for the git-p4 large file system.
|
"""Git LFS as backend for the git-p4 large file system.
|
||||||
See https://git-lfs.github.com/ for details."""
|
See https://git-lfs.github.com/ for details."""
|
||||||
|
@ -1383,6 +1476,7 @@ class GitLFS(LargeFileSystem):
|
||||||
else:
|
else:
|
||||||
return LargeFileSystem.processContent(self, git_mode, relPath, contents)
|
return LargeFileSystem.processContent(self, git_mode, relPath, contents)
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
delete_actions = ( "delete", "move/delete", "purge" )
|
delete_actions = ( "delete", "move/delete", "purge" )
|
||||||
add_actions = ( "add", "branch", "move/add" )
|
add_actions = ( "add", "branch", "move/add" )
|
||||||
|
@ -1398,6 +1492,7 @@ class Command:
|
||||||
setattr(self, attr, value)
|
setattr(self, attr, value)
|
||||||
return getattr(self, attr)
|
return getattr(self, attr)
|
||||||
|
|
||||||
|
|
||||||
class P4UserMap:
|
class P4UserMap:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.userMapFromPerforceServer = False
|
self.userMapFromPerforceServer = False
|
||||||
|
@ -1468,6 +1563,7 @@ class P4UserMap:
|
||||||
except IOError:
|
except IOError:
|
||||||
self.getUserMapFromPerforceServer()
|
self.getUserMapFromPerforceServer()
|
||||||
|
|
||||||
|
|
||||||
class P4Submit(Command, P4UserMap):
|
class P4Submit(Command, P4UserMap):
|
||||||
|
|
||||||
conflict_behavior_choices = ("ask", "skip", "quit")
|
conflict_behavior_choices = ("ask", "skip", "quit")
|
||||||
|
@ -2473,6 +2569,7 @@ class P4Submit(Command, P4UserMap):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class View(object):
|
class View(object):
|
||||||
"""Represent a p4 view ("p4 help views"), and map files in a
|
"""Represent a p4 view ("p4 help views"), and map files in a
|
||||||
repo according to the view."""
|
repo according to the view."""
|
||||||
|
@ -2580,11 +2677,13 @@ class View(object):
|
||||||
die( "Error: %s is not found in client spec path" % depot_path )
|
die( "Error: %s is not found in client spec path" % depot_path )
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def cloneExcludeCallback(option, opt_str, value, parser):
|
def cloneExcludeCallback(option, opt_str, value, parser):
|
||||||
# prepend "/" because the first "/" was consumed as part of the option itself.
|
# prepend "/" because the first "/" was consumed as part of the option itself.
|
||||||
# ("-//depot/A/..." becomes "/depot/A/..." after option parsing)
|
# ("-//depot/A/..." becomes "/depot/A/..." after option parsing)
|
||||||
parser.values.cloneExclude += ["/" + re.sub(r"\.\.\.$", "", value)]
|
parser.values.cloneExclude += ["/" + re.sub(r"\.\.\.$", "", value)]
|
||||||
|
|
||||||
|
|
||||||
class P4Sync(Command, P4UserMap):
|
class P4Sync(Command, P4UserMap):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -3942,6 +4041,7 @@ class P4Sync(Command, P4UserMap):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class P4Rebase(Command):
|
class P4Rebase(Command):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Command.__init__(self)
|
Command.__init__(self)
|
||||||
|
@ -3979,6 +4079,7 @@ class P4Rebase(Command):
|
||||||
"HEAD", "--"])
|
"HEAD", "--"])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class P4Clone(P4Sync):
|
class P4Clone(P4Sync):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
P4Sync.__init__(self)
|
P4Sync.__init__(self)
|
||||||
|
@ -4057,6 +4158,7 @@ class P4Clone(P4Sync):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class P4Unshelve(Command):
|
class P4Unshelve(Command):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Command.__init__(self)
|
Command.__init__(self)
|
||||||
|
@ -4172,6 +4274,7 @@ class P4Unshelve(Command):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class P4Branches(Command):
|
class P4Branches(Command):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Command.__init__(self)
|
Command.__init__(self)
|
||||||
|
@ -4197,6 +4300,7 @@ class P4Branches(Command):
|
||||||
print("%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"]))
|
print("%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"]))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class HelpFormatter(optparse.IndentedHelpFormatter):
|
class HelpFormatter(optparse.IndentedHelpFormatter):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
optparse.IndentedHelpFormatter.__init__(self)
|
optparse.IndentedHelpFormatter.__init__(self)
|
||||||
|
@ -4207,6 +4311,7 @@ class HelpFormatter(optparse.IndentedHelpFormatter):
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def printUsage(commands):
|
def printUsage(commands):
|
||||||
print("usage: %s <command> [options]" % sys.argv[0])
|
print("usage: %s <command> [options]" % sys.argv[0])
|
||||||
print("")
|
print("")
|
||||||
|
@ -4215,6 +4320,7 @@ def printUsage(commands):
|
||||||
print("Try %s <command> --help for command specific help." % sys.argv[0])
|
print("Try %s <command> --help for command specific help." % sys.argv[0])
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
|
||||||
commands = {
|
commands = {
|
||||||
"submit" : P4Submit,
|
"submit" : P4Submit,
|
||||||
"commit" : P4Submit,
|
"commit" : P4Submit,
|
||||||
|
@ -4225,6 +4331,7 @@ commands = {
|
||||||
"unshelve" : P4Unshelve,
|
"unshelve" : P4Unshelve,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv[1:]) == 0:
|
if len(sys.argv[1:]) == 0:
|
||||||
printUsage(commands.keys())
|
printUsage(commands.keys())
|
||||||
|
|
Загрузка…
Ссылка в новой задаче