|
|
|
|
@ -94,12 +94,6 @@ VCS_PERFORCE = "Perforce"
|
|
|
|
|
VCS_CVS = "CVS"
|
|
|
|
|
VCS_UNKNOWN = "Unknown"
|
|
|
|
|
|
|
|
|
|
# whitelist for non-binary filetypes which do not start with "text/"
|
|
|
|
|
# .mm (Objective-C) shows up as application/x-freemind on my Linux box.
|
|
|
|
|
TEXT_MIMETYPES = ['application/javascript', 'application/json',
|
|
|
|
|
'application/x-javascript', 'application/xml',
|
|
|
|
|
'application/x-freemind', 'application/x-sh']
|
|
|
|
|
|
|
|
|
|
VCS_ABBREVIATIONS = {
|
|
|
|
|
VCS_MERCURIAL.lower(): VCS_MERCURIAL,
|
|
|
|
|
"hg": VCS_MERCURIAL,
|
|
|
|
|
@ -548,6 +542,12 @@ group.add_option("--rev", action="store", dest="revision",
|
|
|
|
|
group.add_option("--send_mail", action="store_true",
|
|
|
|
|
dest="send_mail", default=False,
|
|
|
|
|
help="Send notification email to reviewers.")
|
|
|
|
|
group.add_option("-p", "--send_patch", action="store_true",
|
|
|
|
|
dest="send_patch", default=False,
|
|
|
|
|
help="Send notification email to reviewers, with a diff of "
|
|
|
|
|
"the changes included as an attachment instead of "
|
|
|
|
|
"inline. Also prepends 'PATCH:' to the email subject. "
|
|
|
|
|
"(implies --send_mail)")
|
|
|
|
|
group.add_option("--vcs", action="store", dest="vcs",
|
|
|
|
|
metavar="VCS", default=None,
|
|
|
|
|
help=("Version control system (optional, usually upload.py "
|
|
|
|
|
@ -876,15 +876,11 @@ class VersionControlSystem(object):
|
|
|
|
|
return False
|
|
|
|
|
return mimetype.startswith("image/")
|
|
|
|
|
|
|
|
|
|
def IsBinary(self, filename):
|
|
|
|
|
"""Returns true if the guessed mimetyped isnt't in text group."""
|
|
|
|
|
mimetype = mimetypes.guess_type(filename)[0]
|
|
|
|
|
if not mimetype:
|
|
|
|
|
return False # e.g. README, "real" binaries usually have an extension
|
|
|
|
|
# special case for text files which don't start with text/
|
|
|
|
|
if mimetype in TEXT_MIMETYPES:
|
|
|
|
|
return False
|
|
|
|
|
return not mimetype.startswith("text/")
|
|
|
|
|
def IsBinaryData(self, data):
|
|
|
|
|
"""Returns true if data contains a null byte."""
|
|
|
|
|
# Derived from how Mercurial's heuristic, see
|
|
|
|
|
# http://selenic.com/hg/file/848a6658069e/mercurial/util.py#l229
|
|
|
|
|
return bool(data and "\0" in data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SubversionVCS(VersionControlSystem):
|
|
|
|
|
@ -941,6 +937,12 @@ class SubversionVCS(VersionControlSystem):
|
|
|
|
|
ErrorExit("Can't find URL in output from svn info")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def _EscapeFilename(self, filename):
|
|
|
|
|
"""Escapes filename for SVN commands."""
|
|
|
|
|
if "@" in filename and not filename.endswith("@"):
|
|
|
|
|
filename = "%s@" % filename
|
|
|
|
|
return filename
|
|
|
|
|
|
|
|
|
|
def GenerateDiff(self, args):
|
|
|
|
|
cmd = ["svn", "diff"]
|
|
|
|
|
if self.options.revision:
|
|
|
|
|
@ -1008,7 +1010,8 @@ class SubversionVCS(VersionControlSystem):
|
|
|
|
|
def GetStatus(self, filename):
|
|
|
|
|
"""Returns the status of a file."""
|
|
|
|
|
if not self.options.revision:
|
|
|
|
|
status = RunShell(["svn", "status", "--ignore-externals", filename])
|
|
|
|
|
status = RunShell(["svn", "status", "--ignore-externals",
|
|
|
|
|
self._EscapeFilename(filename)])
|
|
|
|
|
if not status:
|
|
|
|
|
ErrorExit("svn status returned no output for %s" % filename)
|
|
|
|
|
status_lines = status.splitlines()
|
|
|
|
|
@ -1027,7 +1030,8 @@ class SubversionVCS(VersionControlSystem):
|
|
|
|
|
else:
|
|
|
|
|
dirname, relfilename = os.path.split(filename)
|
|
|
|
|
if dirname not in self.svnls_cache:
|
|
|
|
|
cmd = ["svn", "list", "-r", self.rev_start, dirname or "."]
|
|
|
|
|
cmd = ["svn", "list", "-r", self.rev_start,
|
|
|
|
|
self._EscapeFilename(dirname) or "."]
|
|
|
|
|
out, err, returncode = RunShellWithReturnCodeAndStderr(cmd)
|
|
|
|
|
if returncode:
|
|
|
|
|
# Directory might not yet exist at start revison
|
|
|
|
|
@ -1041,7 +1045,7 @@ class SubversionVCS(VersionControlSystem):
|
|
|
|
|
args = ["svn", "list"]
|
|
|
|
|
if self.rev_end:
|
|
|
|
|
args += ["-r", self.rev_end]
|
|
|
|
|
cmd = args + [dirname or "."]
|
|
|
|
|
cmd = args + [self._EscapeFilename(dirname) or "."]
|
|
|
|
|
out, returncode = RunShellWithReturnCode(cmd)
|
|
|
|
|
if returncode:
|
|
|
|
|
ErrorExit("Failed to run command %s" % cmd)
|
|
|
|
|
@ -1067,8 +1071,8 @@ class SubversionVCS(VersionControlSystem):
|
|
|
|
|
if status[0] == "A" and status[3] != "+":
|
|
|
|
|
# We'll need to upload the new content if we're adding a binary file
|
|
|
|
|
# since diff's output won't contain it.
|
|
|
|
|
mimetype = RunShell(["svn", "propget", "svn:mime-type", filename],
|
|
|
|
|
silent_ok=True)
|
|
|
|
|
mimetype = RunShell(["svn", "propget", "svn:mime-type",
|
|
|
|
|
self._EscapeFilename(filename)], silent_ok=True)
|
|
|
|
|
base_content = ""
|
|
|
|
|
is_binary = bool(mimetype) and not mimetype.startswith("text/")
|
|
|
|
|
if is_binary and self.IsImage(filename):
|
|
|
|
|
@ -1078,6 +1082,7 @@ class SubversionVCS(VersionControlSystem):
|
|
|
|
|
(status[0] == " " and status[1] == "M")): # Property change.
|
|
|
|
|
args = []
|
|
|
|
|
if self.options.revision:
|
|
|
|
|
# filename must not be escaped. We already add an ampersand here.
|
|
|
|
|
url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start)
|
|
|
|
|
else:
|
|
|
|
|
# Don't change filename, it's needed later.
|
|
|
|
|
@ -1092,9 +1097,12 @@ class SubversionVCS(VersionControlSystem):
|
|
|
|
|
else:
|
|
|
|
|
mimetype = mimetype.strip()
|
|
|
|
|
get_base = False
|
|
|
|
|
# this test for binary is exactly the test prescribed by the
|
|
|
|
|
# official SVN docs at
|
|
|
|
|
# http://subversion.apache.org/faq.html#binary-files
|
|
|
|
|
is_binary = (bool(mimetype) and
|
|
|
|
|
not mimetype.startswith("text/") and
|
|
|
|
|
not mimetype in TEXT_MIMETYPES)
|
|
|
|
|
mimetype not in ("image/x-xbitmap", "image/x-xpixmap"))
|
|
|
|
|
if status[0] == " ":
|
|
|
|
|
# Empty base content just to force an upload.
|
|
|
|
|
base_content = ""
|
|
|
|
|
@ -1127,7 +1135,8 @@ class SubversionVCS(VersionControlSystem):
|
|
|
|
|
silent_ok=True)
|
|
|
|
|
else:
|
|
|
|
|
base_content, ret_code = RunShellWithReturnCode(
|
|
|
|
|
["svn", "cat", filename], universal_newlines=universal_newlines)
|
|
|
|
|
["svn", "cat", self._EscapeFilename(filename)],
|
|
|
|
|
universal_newlines=universal_newlines)
|
|
|
|
|
if ret_code and status[0] == "R":
|
|
|
|
|
# It's a replaced file without local history (see issue208).
|
|
|
|
|
# The base file needs to be fetched from the server.
|
|
|
|
|
@ -1241,7 +1250,9 @@ class GitVCS(VersionControlSystem):
|
|
|
|
|
# review when a file is renamed. So first get the diff of all deleted files,
|
|
|
|
|
# then the diff of everything except deleted files with rename and copy
|
|
|
|
|
# support enabled.
|
|
|
|
|
cmd = ["git", "diff", "--no-ext-diff", "--full-index"]
|
|
|
|
|
cmd = [
|
|
|
|
|
"git", "diff", "--no-ext-diff", "--full-index", "--ignore-submodules"
|
|
|
|
|
]
|
|
|
|
|
diff = RunShell(cmd + ["--diff-filter=D"] + extra_args, env=env,
|
|
|
|
|
silent_ok=True)
|
|
|
|
|
diff += RunShell(cmd + ["-C", "--diff-filter=ACMRT"] + extra_args, env=env,
|
|
|
|
|
@ -1267,7 +1278,6 @@ class GitVCS(VersionControlSystem):
|
|
|
|
|
hash_before, hash_after = self.hashes.get(filename, (None,None))
|
|
|
|
|
base_content = None
|
|
|
|
|
new_content = None
|
|
|
|
|
is_binary = self.IsBinary(filename)
|
|
|
|
|
status = None
|
|
|
|
|
|
|
|
|
|
if filename in self.renames:
|
|
|
|
|
@ -1283,6 +1293,7 @@ class GitVCS(VersionControlSystem):
|
|
|
|
|
else:
|
|
|
|
|
status = "M"
|
|
|
|
|
|
|
|
|
|
is_binary = self.IsBinaryData(base_content)
|
|
|
|
|
is_image = self.IsImage(filename)
|
|
|
|
|
|
|
|
|
|
# Grab the before/after content if we need it.
|
|
|
|
|
@ -1314,7 +1325,6 @@ class CVSVCS(VersionControlSystem):
|
|
|
|
|
def GetBaseFile(self, filename):
|
|
|
|
|
base_content = None
|
|
|
|
|
new_content = None
|
|
|
|
|
is_binary = False
|
|
|
|
|
status = "A"
|
|
|
|
|
|
|
|
|
|
output, retcode = RunShellWithReturnCode(["cvs", "status", filename])
|
|
|
|
|
@ -1334,7 +1344,7 @@ class CVSVCS(VersionControlSystem):
|
|
|
|
|
status = "D"
|
|
|
|
|
base_content = self.GetOriginalContent_(filename)
|
|
|
|
|
|
|
|
|
|
return (base_content, new_content, is_binary, status)
|
|
|
|
|
return (base_content, new_content, self.IsBinaryData(base_content), status)
|
|
|
|
|
|
|
|
|
|
def GenerateDiff(self, extra_args):
|
|
|
|
|
cmd = ["cvs", "diff", "-u", "-N"]
|
|
|
|
|
@ -1451,10 +1461,10 @@ class MercurialVCS(VersionControlSystem):
|
|
|
|
|
if status != "A":
|
|
|
|
|
base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
|
|
|
|
|
silent_ok=True)
|
|
|
|
|
is_binary = "\0" in base_content # Mercurial's heuristic
|
|
|
|
|
is_binary = self.IsBinaryData(base_content)
|
|
|
|
|
if status != "R":
|
|
|
|
|
new_content = open(relpath, "rb").read()
|
|
|
|
|
is_binary = is_binary or "\0" in new_content
|
|
|
|
|
is_binary = is_binary or self.IsBinaryData(new_content)
|
|
|
|
|
if is_binary and base_content:
|
|
|
|
|
# Fetch again without converting newlines
|
|
|
|
|
base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
|
|
|
|
|
@ -1567,9 +1577,6 @@ class PerforceVCS(VersionControlSystem):
|
|
|
|
|
def IsPendingBinary(self, filename):
|
|
|
|
|
return self.IsBinaryHelper(filename, "describe")
|
|
|
|
|
|
|
|
|
|
def IsBinary(self, filename):
|
|
|
|
|
ErrorExit("IsBinary is not safe: call IsBaseBinary or IsPendingBinary")
|
|
|
|
|
|
|
|
|
|
def IsBinaryHelper(self, filename, command):
|
|
|
|
|
file_types = self.GetFileProperties("type", command)
|
|
|
|
|
if not filename in file_types:
|
|
|
|
|
@ -2187,6 +2194,8 @@ def RealMain(argv, data=None):
|
|
|
|
|
print "Warning: Private flag ignored when updating an existing issue."
|
|
|
|
|
else:
|
|
|
|
|
form_fields.append(("private", "1"))
|
|
|
|
|
if options.send_patch:
|
|
|
|
|
options.send_mail = True
|
|
|
|
|
# If we're uploading base files, don't send the email before the uploads, so
|
|
|
|
|
# that it contains the file status.
|
|
|
|
|
if options.send_mail and options.download_base:
|
|
|
|
|
@ -2226,7 +2235,10 @@ def RealMain(argv, data=None):
|
|
|
|
|
if not options.download_base:
|
|
|
|
|
vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files)
|
|
|
|
|
if options.send_mail:
|
|
|
|
|
rpc_server.Send("/" + issue + "/mail", payload="")
|
|
|
|
|
payload = ""
|
|
|
|
|
if options.send_patch:
|
|
|
|
|
payload=urllib.urlencode({"attach_patch": "yes"})
|
|
|
|
|
rpc_server.Send("/" + issue + "/mail", payload=payload)
|
|
|
|
|
return issue, patchset
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|