Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(144)

Side by Side Diff: third_party/upload.py

Issue 7709021: Update upload.py from ed59464f8468. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2007 Google Inc. 3 # Copyright 2007 Google Inc.
4 # 4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License. 6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at 7 # You may obtain a copy of the License at
8 # 8 #
9 # http://www.apache.org/licenses/LICENSE-2.0 9 # http://www.apache.org/licenses/LICENSE-2.0
10 # 10 #
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
87 MAX_UPLOAD_SIZE = 900 * 1024 87 MAX_UPLOAD_SIZE = 900 * 1024
88 88
89 # Constants for version control names. Used by GuessVCSName. 89 # Constants for version control names. Used by GuessVCSName.
90 VCS_GIT = "Git" 90 VCS_GIT = "Git"
91 VCS_MERCURIAL = "Mercurial" 91 VCS_MERCURIAL = "Mercurial"
92 VCS_SUBVERSION = "Subversion" 92 VCS_SUBVERSION = "Subversion"
93 VCS_PERFORCE = "Perforce" 93 VCS_PERFORCE = "Perforce"
94 VCS_CVS = "CVS" 94 VCS_CVS = "CVS"
95 VCS_UNKNOWN = "Unknown" 95 VCS_UNKNOWN = "Unknown"
96 96
97 # whitelist for non-binary filetypes which do not start with "text/"
98 # .mm (Objective-C) shows up as application/x-freemind on my Linux box.
99 TEXT_MIMETYPES = ['application/javascript', 'application/json',
100 'application/x-javascript', 'application/xml',
101 'application/x-freemind', 'application/x-sh']
102
103 VCS_ABBREVIATIONS = { 97 VCS_ABBREVIATIONS = {
104 VCS_MERCURIAL.lower(): VCS_MERCURIAL, 98 VCS_MERCURIAL.lower(): VCS_MERCURIAL,
105 "hg": VCS_MERCURIAL, 99 "hg": VCS_MERCURIAL,
106 VCS_SUBVERSION.lower(): VCS_SUBVERSION, 100 VCS_SUBVERSION.lower(): VCS_SUBVERSION,
107 "svn": VCS_SUBVERSION, 101 "svn": VCS_SUBVERSION,
108 VCS_PERFORCE.lower(): VCS_PERFORCE, 102 VCS_PERFORCE.lower(): VCS_PERFORCE,
109 "p4": VCS_PERFORCE, 103 "p4": VCS_PERFORCE,
110 VCS_GIT.lower(): VCS_GIT, 104 VCS_GIT.lower(): VCS_GIT,
111 VCS_CVS.lower(): VCS_CVS, 105 VCS_CVS.lower(): VCS_CVS,
112 } 106 }
(...skipping 428 matching lines...) Expand 10 before | Expand all | Expand 10 after
541 dest="download_base", default=False, 535 dest="download_base", default=False,
542 help="Base files will be downloaded by the server " 536 help="Base files will be downloaded by the server "
543 "(side-by-side diffs may not work on files with CRs).") 537 "(side-by-side diffs may not work on files with CRs).")
544 group.add_option("--rev", action="store", dest="revision", 538 group.add_option("--rev", action="store", dest="revision",
545 metavar="REV", default=None, 539 metavar="REV", default=None,
546 help="Base revision/branch/tree to diff against. Use " 540 help="Base revision/branch/tree to diff against. Use "
547 "rev1:rev2 range to review already committed changeset.") 541 "rev1:rev2 range to review already committed changeset.")
548 group.add_option("--send_mail", action="store_true", 542 group.add_option("--send_mail", action="store_true",
549 dest="send_mail", default=False, 543 dest="send_mail", default=False,
550 help="Send notification email to reviewers.") 544 help="Send notification email to reviewers.")
545 group.add_option("-p", "--send_patch", action="store_true",
546 dest="send_patch", default=False,
547 help="Send notification email to reviewers, with a diff of "
548 "the changes included as an attachment instead of "
549 "inline. Also prepends 'PATCH:' to the email subject. "
550 "(implies --send_mail)")
551 group.add_option("--vcs", action="store", dest="vcs", 551 group.add_option("--vcs", action="store", dest="vcs",
552 metavar="VCS", default=None, 552 metavar="VCS", default=None,
553 help=("Version control system (optional, usually upload.py " 553 help=("Version control system (optional, usually upload.py "
554 "already guesses the right VCS).")) 554 "already guesses the right VCS)."))
555 group.add_option("--emulate_svn_auto_props", action="store_true", 555 group.add_option("--emulate_svn_auto_props", action="store_true",
556 dest="emulate_svn_auto_props", default=False, 556 dest="emulate_svn_auto_props", default=False,
557 help=("Emulate Subversion's auto properties feature.")) 557 help=("Emulate Subversion's auto properties feature."))
558 # Perforce-specific 558 # Perforce-specific
559 group = parser.add_option_group("Perforce-specific options " 559 group = parser.add_option_group("Perforce-specific options "
560 "(overrides P4 environment variables)") 560 "(overrides P4 environment variables)")
(...skipping 308 matching lines...) Expand 10 before | Expand all | Expand 10 after
869 if new_content != None: 869 if new_content != None:
870 UploadFile(filename, file_id, new_content, is_binary, status, False) 870 UploadFile(filename, file_id, new_content, is_binary, status, False)
871 871
872 def IsImage(self, filename): 872 def IsImage(self, filename):
873 """Returns true if the filename has an image extension.""" 873 """Returns true if the filename has an image extension."""
874 mimetype = mimetypes.guess_type(filename)[0] 874 mimetype = mimetypes.guess_type(filename)[0]
875 if not mimetype: 875 if not mimetype:
876 return False 876 return False
877 return mimetype.startswith("image/") 877 return mimetype.startswith("image/")
878 878
879 def IsBinary(self, filename): 879 def IsBinaryData(self, data):
880 """Returns true if the guessed mimetyped isnt't in text group.""" 880 """Returns true if data contains a null byte."""
881 mimetype = mimetypes.guess_type(filename)[0] 881 # Derived from how Mercurial's heuristic, see
882 if not mimetype: 882 # http://selenic.com/hg/file/848a6658069e/mercurial/util.py#l229
883 return False # e.g. README, "real" binaries usually have an extension 883 return bool(data and "\0" in data)
884 # special case for text files which don't start with text/
885 if mimetype in TEXT_MIMETYPES:
886 return False
887 return not mimetype.startswith("text/")
888 884
889 885
890 class SubversionVCS(VersionControlSystem): 886 class SubversionVCS(VersionControlSystem):
891 """Implementation of the VersionControlSystem interface for Subversion.""" 887 """Implementation of the VersionControlSystem interface for Subversion."""
892 888
893 def __init__(self, options): 889 def __init__(self, options):
894 super(SubversionVCS, self).__init__(options) 890 super(SubversionVCS, self).__init__(options)
895 if self.options.revision: 891 if self.options.revision:
896 match = re.match(r"(\d+)(:(\d+))?", self.options.revision) 892 match = re.match(r"(\d+)(:(\d+))?", self.options.revision)
897 if not match: 893 if not match:
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
934 guess = "Google Code " 930 guess = "Google Code "
935 path = path + "/" 931 path = path + "/"
936 base = urlparse.urlunparse((scheme, netloc, path, params, 932 base = urlparse.urlunparse((scheme, netloc, path, params,
937 query, fragment)) 933 query, fragment))
938 logging.info("Guessed %sbase = %s", guess, base) 934 logging.info("Guessed %sbase = %s", guess, base)
939 return base 935 return base
940 if required: 936 if required:
941 ErrorExit("Can't find URL in output from svn info") 937 ErrorExit("Can't find URL in output from svn info")
942 return None 938 return None
943 939
940 def _EscapeFilename(self, filename):
941 """Escapes filename for SVN commands."""
942 if "@" in filename and not filename.endswith("@"):
943 filename = "%s@" % filename
944 return filename
945
944 def GenerateDiff(self, args): 946 def GenerateDiff(self, args):
945 cmd = ["svn", "diff"] 947 cmd = ["svn", "diff"]
946 if self.options.revision: 948 if self.options.revision:
947 cmd += ["-r", self.options.revision] 949 cmd += ["-r", self.options.revision]
948 cmd.extend(args) 950 cmd.extend(args)
949 data = RunShell(cmd) 951 data = RunShell(cmd)
950 count = 0 952 count = 0
951 for line in data.splitlines(): 953 for line in data.splitlines():
952 if line.startswith("Index:") or line.startswith("Property changes on:"): 954 if line.startswith("Index:") or line.startswith("Property changes on:"):
953 count += 1 955 count += 1
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
1001 result = "" 1003 result = ""
1002 try: 1004 try:
1003 result = file.read() 1005 result = file.read()
1004 finally: 1006 finally:
1005 file.close() 1007 file.close()
1006 return result 1008 return result
1007 1009
1008 def GetStatus(self, filename): 1010 def GetStatus(self, filename):
1009 """Returns the status of a file.""" 1011 """Returns the status of a file."""
1010 if not self.options.revision: 1012 if not self.options.revision:
1011 status = RunShell(["svn", "status", "--ignore-externals", filename]) 1013 status = RunShell(["svn", "status", "--ignore-externals",
1014 self._EscapeFilename(filename)])
1012 if not status: 1015 if not status:
1013 ErrorExit("svn status returned no output for %s" % filename) 1016 ErrorExit("svn status returned no output for %s" % filename)
1014 status_lines = status.splitlines() 1017 status_lines = status.splitlines()
1015 # If file is in a cl, the output will begin with 1018 # If file is in a cl, the output will begin with
1016 # "\n--- Changelist 'cl_name':\n". See 1019 # "\n--- Changelist 'cl_name':\n". See
1017 # http://svn.collab.net/repos/svn/trunk/notes/changelist-design.txt 1020 # http://svn.collab.net/repos/svn/trunk/notes/changelist-design.txt
1018 if (len(status_lines) == 3 and 1021 if (len(status_lines) == 3 and
1019 not status_lines[0] and 1022 not status_lines[0] and
1020 status_lines[1].startswith("--- Changelist")): 1023 status_lines[1].startswith("--- Changelist")):
1021 status = status_lines[2] 1024 status = status_lines[2]
1022 else: 1025 else:
1023 status = status_lines[0] 1026 status = status_lines[0]
1024 # If we have a revision to diff against we need to run "svn list" 1027 # If we have a revision to diff against we need to run "svn list"
1025 # for the old and the new revision and compare the results to get 1028 # for the old and the new revision and compare the results to get
1026 # the correct status for a file. 1029 # the correct status for a file.
1027 else: 1030 else:
1028 dirname, relfilename = os.path.split(filename) 1031 dirname, relfilename = os.path.split(filename)
1029 if dirname not in self.svnls_cache: 1032 if dirname not in self.svnls_cache:
1030 cmd = ["svn", "list", "-r", self.rev_start, dirname or "."] 1033 cmd = ["svn", "list", "-r", self.rev_start,
1034 self._EscapeFilename(dirname) or "."]
1031 out, err, returncode = RunShellWithReturnCodeAndStderr(cmd) 1035 out, err, returncode = RunShellWithReturnCodeAndStderr(cmd)
1032 if returncode: 1036 if returncode:
1033 # Directory might not yet exist at start revison 1037 # Directory might not yet exist at start revison
1034 # svn: Unable to find repository location for 'abc' in revision nnn 1038 # svn: Unable to find repository location for 'abc' in revision nnn
1035 if re.match('^svn: Unable to find repository location for .+ in revisi on \d+', err): 1039 if re.match('^svn: Unable to find repository location for .+ in revisi on \d+', err):
1036 old_files = () 1040 old_files = ()
1037 else: 1041 else:
1038 ErrorExit("Failed to get status for %s:\n%s" % (filename, err)) 1042 ErrorExit("Failed to get status for %s:\n%s" % (filename, err))
1039 else: 1043 else:
1040 old_files = out.splitlines() 1044 old_files = out.splitlines()
1041 args = ["svn", "list"] 1045 args = ["svn", "list"]
1042 if self.rev_end: 1046 if self.rev_end:
1043 args += ["-r", self.rev_end] 1047 args += ["-r", self.rev_end]
1044 cmd = args + [dirname or "."] 1048 cmd = args + [self._EscapeFilename(dirname) or "."]
1045 out, returncode = RunShellWithReturnCode(cmd) 1049 out, returncode = RunShellWithReturnCode(cmd)
1046 if returncode: 1050 if returncode:
1047 ErrorExit("Failed to run command %s" % cmd) 1051 ErrorExit("Failed to run command %s" % cmd)
1048 self.svnls_cache[dirname] = (old_files, out.splitlines()) 1052 self.svnls_cache[dirname] = (old_files, out.splitlines())
1049 old_files, new_files = self.svnls_cache[dirname] 1053 old_files, new_files = self.svnls_cache[dirname]
1050 if relfilename in old_files and relfilename not in new_files: 1054 if relfilename in old_files and relfilename not in new_files:
1051 status = "D " 1055 status = "D "
1052 elif relfilename in old_files and relfilename in new_files: 1056 elif relfilename in old_files and relfilename in new_files:
1053 status = "M " 1057 status = "M "
1054 else: 1058 else:
1055 status = "A " 1059 status = "A "
1056 return status 1060 return status
1057 1061
1058 def GetBaseFile(self, filename): 1062 def GetBaseFile(self, filename):
1059 status = self.GetStatus(filename) 1063 status = self.GetStatus(filename)
1060 base_content = None 1064 base_content = None
1061 new_content = None 1065 new_content = None
1062 1066
1063 # If a file is copied its status will be "A +", which signifies 1067 # If a file is copied its status will be "A +", which signifies
1064 # "addition-with-history". See "svn st" for more information. We need to 1068 # "addition-with-history". See "svn st" for more information. We need to
1065 # upload the original file or else diff parsing will fail if the file was 1069 # upload the original file or else diff parsing will fail if the file was
1066 # edited. 1070 # edited.
1067 if status[0] == "A" and status[3] != "+": 1071 if status[0] == "A" and status[3] != "+":
1068 # We'll need to upload the new content if we're adding a binary file 1072 # We'll need to upload the new content if we're adding a binary file
1069 # since diff's output won't contain it. 1073 # since diff's output won't contain it.
1070 mimetype = RunShell(["svn", "propget", "svn:mime-type", filename], 1074 mimetype = RunShell(["svn", "propget", "svn:mime-type",
1071 silent_ok=True) 1075 self._EscapeFilename(filename)], silent_ok=True)
1072 base_content = "" 1076 base_content = ""
1073 is_binary = bool(mimetype) and not mimetype.startswith("text/") 1077 is_binary = bool(mimetype) and not mimetype.startswith("text/")
1074 if is_binary and self.IsImage(filename): 1078 if is_binary and self.IsImage(filename):
1075 new_content = self.ReadFile(filename) 1079 new_content = self.ReadFile(filename)
1076 elif (status[0] in ("M", "D", "R") or 1080 elif (status[0] in ("M", "D", "R") or
1077 (status[0] == "A" and status[3] == "+") or # Copied file. 1081 (status[0] == "A" and status[3] == "+") or # Copied file.
1078 (status[0] == " " and status[1] == "M")): # Property change. 1082 (status[0] == " " and status[1] == "M")): # Property change.
1079 args = [] 1083 args = []
1080 if self.options.revision: 1084 if self.options.revision:
1085 # filename must not be escaped. We already add an ampersand here.
1081 url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) 1086 url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start)
1082 else: 1087 else:
1083 # Don't change filename, it's needed later. 1088 # Don't change filename, it's needed later.
1084 url = filename 1089 url = filename
1085 args += ["-r", "BASE"] 1090 args += ["-r", "BASE"]
1086 cmd = ["svn"] + args + ["propget", "svn:mime-type", url] 1091 cmd = ["svn"] + args + ["propget", "svn:mime-type", url]
1087 mimetype, returncode = RunShellWithReturnCode(cmd) 1092 mimetype, returncode = RunShellWithReturnCode(cmd)
1088 if returncode: 1093 if returncode:
1089 # File does not exist in the requested revision. 1094 # File does not exist in the requested revision.
1090 # Reset mimetype, it contains an error message. 1095 # Reset mimetype, it contains an error message.
1091 mimetype = "" 1096 mimetype = ""
1092 else: 1097 else:
1093 mimetype = mimetype.strip() 1098 mimetype = mimetype.strip()
1094 get_base = False 1099 get_base = False
1100 # this test for binary is exactly the test prescribed by the
1101 # official SVN docs at
1102 # http://subversion.apache.org/faq.html#binary-files
1095 is_binary = (bool(mimetype) and 1103 is_binary = (bool(mimetype) and
1096 not mimetype.startswith("text/") and 1104 not mimetype.startswith("text/") and
1097 not mimetype in TEXT_MIMETYPES) 1105 mimetype not in ("image/x-xbitmap", "image/x-xpixmap"))
1098 if status[0] == " ": 1106 if status[0] == " ":
1099 # Empty base content just to force an upload. 1107 # Empty base content just to force an upload.
1100 base_content = "" 1108 base_content = ""
1101 elif is_binary: 1109 elif is_binary:
1102 if self.IsImage(filename): 1110 if self.IsImage(filename):
1103 get_base = True 1111 get_base = True
1104 if status[0] == "M": 1112 if status[0] == "M":
1105 if not self.rev_end: 1113 if not self.rev_end:
1106 new_content = self.ReadFile(filename) 1114 new_content = self.ReadFile(filename)
1107 else: 1115 else:
(...skipping 12 matching lines...) Expand all
1120 universal_newlines = True 1128 universal_newlines = True
1121 if self.rev_start: 1129 if self.rev_start:
1122 # "svn cat -r REV delete_file.txt" doesn't work. cat requires 1130 # "svn cat -r REV delete_file.txt" doesn't work. cat requires
1123 # the full URL with "@REV" appended instead of using "-r" option. 1131 # the full URL with "@REV" appended instead of using "-r" option.
1124 url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) 1132 url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start)
1125 base_content = RunShell(["svn", "cat", url], 1133 base_content = RunShell(["svn", "cat", url],
1126 universal_newlines=universal_newlines, 1134 universal_newlines=universal_newlines,
1127 silent_ok=True) 1135 silent_ok=True)
1128 else: 1136 else:
1129 base_content, ret_code = RunShellWithReturnCode( 1137 base_content, ret_code = RunShellWithReturnCode(
1130 ["svn", "cat", filename], universal_newlines=universal_newlines) 1138 ["svn", "cat", self._EscapeFilename(filename)],
1139 universal_newlines=universal_newlines)
1131 if ret_code and status[0] == "R": 1140 if ret_code and status[0] == "R":
1132 # It's a replaced file without local history (see issue208). 1141 # It's a replaced file without local history (see issue208).
1133 # The base file needs to be fetched from the server. 1142 # The base file needs to be fetched from the server.
1134 url = "%s/%s" % (self.svn_base, filename) 1143 url = "%s/%s" % (self.svn_base, filename)
1135 base_content = RunShell(["svn", "cat", url], 1144 base_content = RunShell(["svn", "cat", url],
1136 universal_newlines=universal_newlines, 1145 universal_newlines=universal_newlines,
1137 silent_ok=True) 1146 silent_ok=True)
1138 elif ret_code: 1147 elif ret_code:
1139 ErrorExit("Got error status from 'svn cat %s'" % filename) 1148 ErrorExit("Got error status from 'svn cat %s'" % filename)
1140 if not is_binary: 1149 if not is_binary:
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
1234 # --no-ext-diff is broken in some versions of Git, so try to work around 1243 # --no-ext-diff is broken in some versions of Git, so try to work around
1235 # this by overriding the environment (but there is still a problem if the 1244 # this by overriding the environment (but there is still a problem if the
1236 # git config key "diff.external" is used). 1245 # git config key "diff.external" is used).
1237 env = os.environ.copy() 1246 env = os.environ.copy()
1238 if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF'] 1247 if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF']
1239 # -M/-C will not print the diff for the deleted file when a file is renamed. 1248 # -M/-C will not print the diff for the deleted file when a file is renamed.
1240 # This is confusing because the original file will not be shown on the 1249 # This is confusing because the original file will not be shown on the
1241 # review when a file is renamed. So first get the diff of all deleted files, 1250 # review when a file is renamed. So first get the diff of all deleted files,
1242 # then the diff of everything except deleted files with rename and copy 1251 # then the diff of everything except deleted files with rename and copy
1243 # support enabled. 1252 # support enabled.
1244 cmd = ["git", "diff", "--no-ext-diff", "--full-index"] 1253 cmd = [
1254 "git", "diff", "--no-ext-diff", "--full-index", "--ignore-submodules"
1255 ]
1245 diff = RunShell(cmd + ["--diff-filter=D"] + extra_args, env=env, 1256 diff = RunShell(cmd + ["--diff-filter=D"] + extra_args, env=env,
1246 silent_ok=True) 1257 silent_ok=True)
1247 diff += RunShell(cmd + ["-C", "--diff-filter=ACMRT"] + extra_args, env=env, 1258 diff += RunShell(cmd + ["-C", "--diff-filter=ACMRT"] + extra_args, env=env,
1248 silent_ok=True) 1259 silent_ok=True)
1249 if not diff: 1260 if not diff:
1250 ErrorExit("No output from %s" % (cmd + extra_args)) 1261 ErrorExit("No output from %s" % (cmd + extra_args))
1251 return diff 1262 return diff
1252 1263
1253 def GetUnknownFiles(self): 1264 def GetUnknownFiles(self):
1254 status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], 1265 status = RunShell(["git", "ls-files", "--exclude-standard", "--others"],
1255 silent_ok=True) 1266 silent_ok=True)
1256 return status.splitlines() 1267 return status.splitlines()
1257 1268
1258 def GetFileContent(self, file_hash, is_binary): 1269 def GetFileContent(self, file_hash, is_binary):
1259 """Returns the content of a file identified by its git hash.""" 1270 """Returns the content of a file identified by its git hash."""
1260 data, retcode = RunShellWithReturnCode(["git", "show", file_hash], 1271 data, retcode = RunShellWithReturnCode(["git", "show", file_hash],
1261 universal_newlines=not is_binary) 1272 universal_newlines=not is_binary)
1262 if retcode: 1273 if retcode:
1263 ErrorExit("Got error status from 'git show %s'" % file_hash) 1274 ErrorExit("Got error status from 'git show %s'" % file_hash)
1264 return data 1275 return data
1265 1276
1266 def GetBaseFile(self, filename): 1277 def GetBaseFile(self, filename):
1267 hash_before, hash_after = self.hashes.get(filename, (None,None)) 1278 hash_before, hash_after = self.hashes.get(filename, (None,None))
1268 base_content = None 1279 base_content = None
1269 new_content = None 1280 new_content = None
1270 is_binary = self.IsBinary(filename)
1271 status = None 1281 status = None
1272 1282
1273 if filename in self.renames: 1283 if filename in self.renames:
1274 status = "A +" # Match svn attribute name for renames. 1284 status = "A +" # Match svn attribute name for renames.
1275 if filename not in self.hashes: 1285 if filename not in self.hashes:
1276 # If a rename doesn't change the content, we never get a hash. 1286 # If a rename doesn't change the content, we never get a hash.
1277 base_content = RunShell(["git", "show", "HEAD:" + filename]) 1287 base_content = RunShell(["git", "show", "HEAD:" + filename])
1278 elif not hash_before: 1288 elif not hash_before:
1279 status = "A" 1289 status = "A"
1280 base_content = "" 1290 base_content = ""
1281 elif not hash_after: 1291 elif not hash_after:
1282 status = "D" 1292 status = "D"
1283 else: 1293 else:
1284 status = "M" 1294 status = "M"
1285 1295
1296 is_binary = self.IsBinaryData(base_content)
1286 is_image = self.IsImage(filename) 1297 is_image = self.IsImage(filename)
1287 1298
1288 # Grab the before/after content if we need it. 1299 # Grab the before/after content if we need it.
1289 # We should include file contents if it's text or it's an image. 1300 # We should include file contents if it's text or it's an image.
1290 if not is_binary or is_image: 1301 if not is_binary or is_image:
1291 # Grab the base content if we don't have it already. 1302 # Grab the base content if we don't have it already.
1292 if base_content is None and hash_before: 1303 if base_content is None and hash_before:
1293 base_content = self.GetFileContent(hash_before, is_binary) 1304 base_content = self.GetFileContent(hash_before, is_binary)
1294 # Only include the "after" file if it's an image; otherwise it 1305 # Only include the "after" file if it's an image; otherwise it
1295 # it is reconstructed from the diff. 1306 # it is reconstructed from the diff.
(...skipping 11 matching lines...) Expand all
1307 1318
1308 def GetOriginalContent_(self, filename): 1319 def GetOriginalContent_(self, filename):
1309 RunShell(["cvs", "up", filename], silent_ok=True) 1320 RunShell(["cvs", "up", filename], silent_ok=True)
1310 # TODO need detect file content encoding 1321 # TODO need detect file content encoding
1311 content = open(filename).read() 1322 content = open(filename).read()
1312 return content.replace("\r\n", "\n") 1323 return content.replace("\r\n", "\n")
1313 1324
1314 def GetBaseFile(self, filename): 1325 def GetBaseFile(self, filename):
1315 base_content = None 1326 base_content = None
1316 new_content = None 1327 new_content = None
1317 is_binary = False
1318 status = "A" 1328 status = "A"
1319 1329
1320 output, retcode = RunShellWithReturnCode(["cvs", "status", filename]) 1330 output, retcode = RunShellWithReturnCode(["cvs", "status", filename])
1321 if retcode: 1331 if retcode:
1322 ErrorExit("Got error status from 'cvs status %s'" % filename) 1332 ErrorExit("Got error status from 'cvs status %s'" % filename)
1323 1333
1324 if output.find("Status: Locally Modified") != -1: 1334 if output.find("Status: Locally Modified") != -1:
1325 status = "M" 1335 status = "M"
1326 temp_filename = "%s.tmp123" % filename 1336 temp_filename = "%s.tmp123" % filename
1327 os.rename(filename, temp_filename) 1337 os.rename(filename, temp_filename)
1328 base_content = self.GetOriginalContent_(filename) 1338 base_content = self.GetOriginalContent_(filename)
1329 os.rename(temp_filename, filename) 1339 os.rename(temp_filename, filename)
1330 elif output.find("Status: Locally Added"): 1340 elif output.find("Status: Locally Added"):
1331 status = "A" 1341 status = "A"
1332 base_content = "" 1342 base_content = ""
1333 elif output.find("Status: Needs Checkout"): 1343 elif output.find("Status: Needs Checkout"):
1334 status = "D" 1344 status = "D"
1335 base_content = self.GetOriginalContent_(filename) 1345 base_content = self.GetOriginalContent_(filename)
1336 1346
1337 return (base_content, new_content, is_binary, status) 1347 return (base_content, new_content, self.IsBinaryData(base_content), status)
1338 1348
1339 def GenerateDiff(self, extra_args): 1349 def GenerateDiff(self, extra_args):
1340 cmd = ["cvs", "diff", "-u", "-N"] 1350 cmd = ["cvs", "diff", "-u", "-N"]
1341 if self.options.revision: 1351 if self.options.revision:
1342 cmd += ["-r", self.options.revision] 1352 cmd += ["-r", self.options.revision]
1343 1353
1344 cmd.extend(extra_args) 1354 cmd.extend(extra_args)
1345 data, retcode = RunShellWithReturnCode(cmd) 1355 data, retcode = RunShellWithReturnCode(cmd)
1346 count = 0 1356 count = 0
1347 if retcode in [0, 1]: 1357 if retcode in [0, 1]:
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
1444 # retrieve base contents 1454 # retrieve base contents
1445 oldrelpath = out[1].strip() 1455 oldrelpath = out[1].strip()
1446 status = "M" 1456 status = "M"
1447 if ":" in self.base_rev: 1457 if ":" in self.base_rev:
1448 base_rev = self.base_rev.split(":", 1)[0] 1458 base_rev = self.base_rev.split(":", 1)[0]
1449 else: 1459 else:
1450 base_rev = self.base_rev 1460 base_rev = self.base_rev
1451 if status != "A": 1461 if status != "A":
1452 base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], 1462 base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
1453 silent_ok=True) 1463 silent_ok=True)
1454 is_binary = "\0" in base_content # Mercurial's heuristic 1464 is_binary = self.IsBinaryData(base_content)
1455 if status != "R": 1465 if status != "R":
1456 new_content = open(relpath, "rb").read() 1466 new_content = open(relpath, "rb").read()
1457 is_binary = is_binary or "\0" in new_content 1467 is_binary = is_binary or self.IsBinaryData(new_content)
1458 if is_binary and base_content: 1468 if is_binary and base_content:
1459 # Fetch again without converting newlines 1469 # Fetch again without converting newlines
1460 base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], 1470 base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
1461 silent_ok=True, universal_newlines=False) 1471 silent_ok=True, universal_newlines=False)
1462 if not is_binary or not self.IsImage(relpath): 1472 if not is_binary or not self.IsImage(relpath):
1463 new_content = None 1473 new_content = None
1464 return base_content, new_content, is_binary, status 1474 return base_content, new_content, is_binary, status
1465 1475
1466 1476
1467 class PerforceVCS(VersionControlSystem): 1477 class PerforceVCS(VersionControlSystem):
1468 """Implementation of the VersionControlSystem interface for Perforce.""" 1478 """Implementation of the VersionControlSystem interface for Perforce."""
1469 1479
1470 def __init__(self, options): 1480 def __init__(self, options):
1471 1481
1472 def ConfirmLogin(): 1482 def ConfirmLogin():
1473 # Make sure we have a valid perforce session 1483 # Make sure we have a valid perforce session
1474 while True: 1484 while True:
1475 data, retcode = self.RunPerforceCommandWithReturnCode( 1485 data, retcode = self.RunPerforceCommandWithReturnCode(
1476 ["login", "-s"], marshal_output=True) 1486 ["login", "-s"], marshal_output=True)
1477 if not data: 1487 if not data:
1478 ErrorExit("Error checking perforce login") 1488 ErrorExit("Error checking perforce login")
1479 if not retcode and (not "code" in data or data["code"] != "error"): 1489 if not retcode and (not "code" in data or data["code"] != "error"):
1480 break 1490 break
1481 print "Enter perforce password: " 1491 print "Enter perforce password: "
1482 self.RunPerforceCommandWithReturnCode(["login"]) 1492 self.RunPerforceCommandWithReturnCode(["login"])
1483 1493
1484 super(PerforceVCS, self).__init__(options) 1494 super(PerforceVCS, self).__init__(options)
1485 1495
1486 self.p4_changelist = options.p4_changelist 1496 self.p4_changelist = options.p4_changelist
1487 if not self.p4_changelist: 1497 if not self.p4_changelist:
1488 ErrorExit("A changelist id is required") 1498 ErrorExit("A changelist id is required")
1489 if (options.revision): 1499 if (options.revision):
1490 ErrorExit("--rev is not supported for perforce") 1500 ErrorExit("--rev is not supported for perforce")
1491 1501
1492 self.p4_port = options.p4_port 1502 self.p4_port = options.p4_port
1493 self.p4_client = options.p4_client 1503 self.p4_client = options.p4_client
1494 self.p4_user = options.p4_user 1504 self.p4_user = options.p4_user
1495 1505
1496 ConfirmLogin() 1506 ConfirmLogin()
1497 1507
1498 if not options.message: 1508 if not options.message:
1499 description = self.RunPerforceCommand(["describe", self.p4_changelist], 1509 description = self.RunPerforceCommand(["describe", self.p4_changelist],
1500 marshal_output=True) 1510 marshal_output=True)
1501 if description and "desc" in description: 1511 if description and "desc" in description:
1502 # Rietveld doesn't support multi-line descriptions 1512 # Rietveld doesn't support multi-line descriptions
1503 raw_message = description["desc"].strip() 1513 raw_message = description["desc"].strip()
1504 lines = raw_message.splitlines() 1514 lines = raw_message.splitlines()
1505 if len(lines): 1515 if len(lines):
1506 options.message = lines[0] 1516 options.message = lines[0]
1507 1517
1508 def RunPerforceCommandWithReturnCode(self, extra_args, marshal_output=False, 1518 def RunPerforceCommandWithReturnCode(self, extra_args, marshal_output=False,
1509 universal_newlines=True): 1519 universal_newlines=True):
1510 args = ["p4"] 1520 args = ["p4"]
1511 if marshal_output: 1521 if marshal_output:
1512 # -G makes perforce format its output as marshalled python objects 1522 # -G makes perforce format its output as marshalled python objects
1513 args.extend(["-G"]) 1523 args.extend(["-G"])
1514 if self.p4_port: 1524 if self.p4_port:
1515 args.extend(["-p", self.p4_port]) 1525 args.extend(["-p", self.p4_port])
1516 if self.p4_client: 1526 if self.p4_client:
1517 args.extend(["-c", self.p4_client]) 1527 args.extend(["-c", self.p4_client])
1518 if self.p4_user: 1528 if self.p4_user:
1519 args.extend(["-u", self.p4_user]) 1529 args.extend(["-u", self.p4_user])
1520 args.extend(extra_args) 1530 args.extend(extra_args)
1521 1531
1522 data, retcode = RunShellWithReturnCode( 1532 data, retcode = RunShellWithReturnCode(
1523 args, print_output=False, universal_newlines=universal_newlines) 1533 args, print_output=False, universal_newlines=universal_newlines)
1524 if marshal_output and data: 1534 if marshal_output and data:
1525 data = marshal.loads(data) 1535 data = marshal.loads(data)
1526 return data, retcode 1536 return data, retcode
1527 1537
1528 def RunPerforceCommand(self, extra_args, marshal_output=False, 1538 def RunPerforceCommand(self, extra_args, marshal_output=False,
1529 universal_newlines=True): 1539 universal_newlines=True):
1530 # This might be a good place to cache call results, since things like 1540 # This might be a good place to cache call results, since things like
1531 # describe or fstat might get called repeatedly. 1541 # describe or fstat might get called repeatedly.
1532 data, retcode = self.RunPerforceCommandWithReturnCode( 1542 data, retcode = self.RunPerforceCommandWithReturnCode(
1533 extra_args, marshal_output, universal_newlines) 1543 extra_args, marshal_output, universal_newlines)
1534 if retcode: 1544 if retcode:
1535 ErrorExit("Got error status from %s:\n%s" % (extra_args, data)) 1545 ErrorExit("Got error status from %s:\n%s" % (extra_args, data))
1536 return data 1546 return data
1537 1547
1538 def GetFileProperties(self, property_key_prefix = "", command = "describe"): 1548 def GetFileProperties(self, property_key_prefix = "", command = "describe"):
1539 description = self.RunPerforceCommand(["describe", self.p4_changelist], 1549 description = self.RunPerforceCommand(["describe", self.p4_changelist],
1540 marshal_output=True) 1550 marshal_output=True)
1541 1551
1542 changed_files = {} 1552 changed_files = {}
1543 file_index = 0 1553 file_index = 0
1544 # Try depotFile0, depotFile1, ... until we don't find a match 1554 # Try depotFile0, depotFile1, ... until we don't find a match
1545 while True: 1555 while True:
1546 file_key = "depotFile%d" % file_index 1556 file_key = "depotFile%d" % file_index
1547 if file_key in description: 1557 if file_key in description:
1548 filename = description[file_key] 1558 filename = description[file_key]
1549 change_type = description[property_key_prefix + str(file_index)] 1559 change_type = description[property_key_prefix + str(file_index)]
1550 changed_files[filename] = change_type 1560 changed_files[filename] = change_type
1551 file_index += 1 1561 file_index += 1
1552 else: 1562 else:
1553 break 1563 break
1554 return changed_files 1564 return changed_files
1555 1565
1556 def GetChangedFiles(self): 1566 def GetChangedFiles(self):
1557 return self.GetFileProperties("action") 1567 return self.GetFileProperties("action")
1558 1568
1559 def GetUnknownFiles(self): 1569 def GetUnknownFiles(self):
1560 # Perforce doesn't detect new files, they have to be explicitly added 1570 # Perforce doesn't detect new files, they have to be explicitly added
1561 return [] 1571 return []
1562 1572
1563 def IsBaseBinary(self, filename): 1573 def IsBaseBinary(self, filename):
1564 base_filename = self.GetBaseFilename(filename) 1574 base_filename = self.GetBaseFilename(filename)
1565 return self.IsBinaryHelper(base_filename, "files") 1575 return self.IsBinaryHelper(base_filename, "files")
1566 1576
1567 def IsPendingBinary(self, filename): 1577 def IsPendingBinary(self, filename):
1568 return self.IsBinaryHelper(filename, "describe") 1578 return self.IsBinaryHelper(filename, "describe")
1569 1579
1570 def IsBinary(self, filename):
1571 ErrorExit("IsBinary is not safe: call IsBaseBinary or IsPendingBinary")
1572
1573 def IsBinaryHelper(self, filename, command): 1580 def IsBinaryHelper(self, filename, command):
1574 file_types = self.GetFileProperties("type", command) 1581 file_types = self.GetFileProperties("type", command)
1575 if not filename in file_types: 1582 if not filename in file_types:
1576 ErrorExit("Trying to check binary status of unknown file %s." % filename) 1583 ErrorExit("Trying to check binary status of unknown file %s." % filename)
1577 # This treats symlinks, macintosh resource files, temporary objects, and 1584 # This treats symlinks, macintosh resource files, temporary objects, and
1578 # unicode as binary. See the Perforce docs for more details: 1585 # unicode as binary. See the Perforce docs for more details:
1579 # http://www.perforce.com/perforce/doc.current/manuals/cmdref/o.ftypes.html 1586 # http://www.perforce.com/perforce/doc.current/manuals/cmdref/o.ftypes.html
1580 return not file_types[filename].endswith("text") 1587 return not file_types[filename].endswith("text")
1581 1588
1582 def GetFileContent(self, filename, revision, is_binary): 1589 def GetFileContent(self, filename, revision, is_binary):
1583 file_arg = filename 1590 file_arg = filename
1584 if revision: 1591 if revision:
1585 file_arg += "#" + revision 1592 file_arg += "#" + revision
1586 # -q suppresses the initial line that displays the filename and revision 1593 # -q suppresses the initial line that displays the filename and revision
1587 return self.RunPerforceCommand(["print", "-q", file_arg], 1594 return self.RunPerforceCommand(["print", "-q", file_arg],
1588 universal_newlines=not is_binary) 1595 universal_newlines=not is_binary)
1589 1596
1590 def GetBaseFilename(self, filename): 1597 def GetBaseFilename(self, filename):
1591 actionsWithDifferentBases = [ 1598 actionsWithDifferentBases = [
1592 "move/add", # p4 move 1599 "move/add", # p4 move
1593 "branch", # p4 integrate (to a new file), similar to hg "add" 1600 "branch", # p4 integrate (to a new file), similar to hg "add"
1594 "add", # p4 integrate (to a new file), after modifying the new file 1601 "add", # p4 integrate (to a new file), after modifying the new file
1595 ] 1602 ]
1596 1603
1597 # We only see a different base for "add" if this is a downgraded branch 1604 # We only see a different base for "add" if this is a downgraded branch
1598 # after a file was branched (integrated), then edited. 1605 # after a file was branched (integrated), then edited.
1599 if self.GetAction(filename) in actionsWithDifferentBases: 1606 if self.GetAction(filename) in actionsWithDifferentBases:
1600 # -Or shows information about pending integrations/moves 1607 # -Or shows information about pending integrations/moves
1601 fstat_result = self.RunPerforceCommand(["fstat", "-Or", filename], 1608 fstat_result = self.RunPerforceCommand(["fstat", "-Or", filename],
1602 marshal_output=True) 1609 marshal_output=True)
1603 1610
1604 baseFileKey = "resolveFromFile0" # I think it's safe to use only file0 1611 baseFileKey = "resolveFromFile0" # I think it's safe to use only file0
1605 if baseFileKey in fstat_result: 1612 if baseFileKey in fstat_result:
1606 return fstat_result[baseFileKey] 1613 return fstat_result[baseFileKey]
1607 1614
1608 return filename 1615 return filename
1609 1616
1610 def GetBaseRevision(self, filename): 1617 def GetBaseRevision(self, filename):
1611 base_filename = self.GetBaseFilename(filename) 1618 base_filename = self.GetBaseFilename(filename)
1612 1619
1613 have_result = self.RunPerforceCommand(["have", base_filename], 1620 have_result = self.RunPerforceCommand(["have", base_filename],
1614 marshal_output=True) 1621 marshal_output=True)
1615 if "haveRev" in have_result: 1622 if "haveRev" in have_result:
1616 return have_result["haveRev"] 1623 return have_result["haveRev"]
1617 1624
1618 def GetLocalFilename(self, filename): 1625 def GetLocalFilename(self, filename):
1619 where = self.RunPerforceCommand(["where", filename], marshal_output=True) 1626 where = self.RunPerforceCommand(["where", filename], marshal_output=True)
1620 if "path" in where: 1627 if "path" in where:
1621 return where["path"] 1628 return where["path"]
1622 1629
1623 def GenerateDiff(self, args): 1630 def GenerateDiff(self, args):
1624 class DiffData: 1631 class DiffData:
1625 def __init__(self, perforceVCS, filename, action): 1632 def __init__(self, perforceVCS, filename, action):
1626 self.perforceVCS = perforceVCS 1633 self.perforceVCS = perforceVCS
1627 self.filename = filename 1634 self.filename = filename
1628 self.action = action 1635 self.action = action
1629 self.base_filename = perforceVCS.GetBaseFilename(filename) 1636 self.base_filename = perforceVCS.GetBaseFilename(filename)
1630 1637
1631 self.file_body = None 1638 self.file_body = None
1632 self.base_rev = None 1639 self.base_rev = None
1633 self.prefix = None 1640 self.prefix = None
1634 self.working_copy = True 1641 self.working_copy = True
1635 self.change_summary = None 1642 self.change_summary = None
1636 1643
1637 def GenerateDiffHeader(diffData): 1644 def GenerateDiffHeader(diffData):
1638 header = [] 1645 header = []
1639 header.append("Index: %s" % diffData.filename) 1646 header.append("Index: %s" % diffData.filename)
1640 header.append("=" * 67) 1647 header.append("=" * 67)
1641 1648
1642 if diffData.base_filename != diffData.filename: 1649 if diffData.base_filename != diffData.filename:
1643 if diffData.action.startswith("move"): 1650 if diffData.action.startswith("move"):
1644 verb = "rename" 1651 verb = "rename"
1645 else: 1652 else:
1646 verb = "copy" 1653 verb = "copy"
1647 header.append("%s from %s" % (verb, diffData.base_filename)) 1654 header.append("%s from %s" % (verb, diffData.base_filename))
1648 header.append("%s to %s" % (verb, diffData.filename)) 1655 header.append("%s to %s" % (verb, diffData.filename))
1649 1656
1650 suffix = "\t(revision %s)" % diffData.base_rev 1657 suffix = "\t(revision %s)" % diffData.base_rev
1651 header.append("--- " + diffData.base_filename + suffix) 1658 header.append("--- " + diffData.base_filename + suffix)
1652 if diffData.working_copy: 1659 if diffData.working_copy:
1653 suffix = "\t(working copy)" 1660 suffix = "\t(working copy)"
1654 header.append("+++ " + diffData.filename + suffix) 1661 header.append("+++ " + diffData.filename + suffix)
1655 if diffData.change_summary: 1662 if diffData.change_summary:
1656 header.append(diffData.change_summary) 1663 header.append(diffData.change_summary)
1657 return header 1664 return header
1658 1665
1659 def GenerateMergeDiff(diffData, args): 1666 def GenerateMergeDiff(diffData, args):
1660 # -du generates a unified diff, which is nearly svn format 1667 # -du generates a unified diff, which is nearly svn format
1661 diffData.file_body = self.RunPerforceCommand( 1668 diffData.file_body = self.RunPerforceCommand(
1662 ["diff", "-du", diffData.filename] + args) 1669 ["diff", "-du", diffData.filename] + args)
1663 diffData.base_rev = self.GetBaseRevision(diffData.filename) 1670 diffData.base_rev = self.GetBaseRevision(diffData.filename)
1664 diffData.prefix = "" 1671 diffData.prefix = ""
1665 1672
1666 # We have to replace p4's file status output (the lines starting 1673 # We have to replace p4's file status output (the lines starting
1667 # with +++ or ---) to match svn's diff format 1674 # with +++ or ---) to match svn's diff format
1668 lines = diffData.file_body.splitlines() 1675 lines = diffData.file_body.splitlines()
1669 first_good_line = 0 1676 first_good_line = 0
1670 while (first_good_line < len(lines) and 1677 while (first_good_line < len(lines) and
1671 not lines[first_good_line].startswith("@@")): 1678 not lines[first_good_line].startswith("@@")):
1672 first_good_line += 1 1679 first_good_line += 1
1673 diffData.file_body = "\n".join(lines[first_good_line:]) 1680 diffData.file_body = "\n".join(lines[first_good_line:])
1674 return diffData 1681 return diffData
1675 1682
1676 def GenerateAddDiff(diffData): 1683 def GenerateAddDiff(diffData):
1677 fstat = self.RunPerforceCommand(["fstat", diffData.filename], 1684 fstat = self.RunPerforceCommand(["fstat", diffData.filename],
1678 marshal_output=True) 1685 marshal_output=True)
1679 if "headRev" in fstat: 1686 if "headRev" in fstat:
1680 diffData.base_rev = fstat["headRev"] # Re-adding a deleted file 1687 diffData.base_rev = fstat["headRev"] # Re-adding a deleted file
1681 else: 1688 else:
1682 diffData.base_rev = "0" # Brand new file 1689 diffData.base_rev = "0" # Brand new file
1683 diffData.working_copy = False 1690 diffData.working_copy = False
1684 rel_path = self.GetLocalFilename(diffData.filename) 1691 rel_path = self.GetLocalFilename(diffData.filename)
1685 diffData.file_body = open(rel_path, 'r').read() 1692 diffData.file_body = open(rel_path, 'r').read()
1686 # Replicate svn's list of changed lines 1693 # Replicate svn's list of changed lines
1687 line_count = len(diffData.file_body.splitlines()) 1694 line_count = len(diffData.file_body.splitlines())
1688 diffData.change_summary = "@@ -0,0 +1" 1695 diffData.change_summary = "@@ -0,0 +1"
1689 if line_count > 1: 1696 if line_count > 1:
1690 diffData.change_summary += ",%d" % line_count 1697 diffData.change_summary += ",%d" % line_count
1691 diffData.change_summary += " @@" 1698 diffData.change_summary += " @@"
1692 diffData.prefix = "+" 1699 diffData.prefix = "+"
1693 return diffData 1700 return diffData
1694 1701
1695 def GenerateDeleteDiff(diffData): 1702 def GenerateDeleteDiff(diffData):
1696 diffData.base_rev = self.GetBaseRevision(diffData.filename) 1703 diffData.base_rev = self.GetBaseRevision(diffData.filename)
1697 is_base_binary = self.IsBaseBinary(diffData.filename) 1704 is_base_binary = self.IsBaseBinary(diffData.filename)
1698 # For deletes, base_filename == filename 1705 # For deletes, base_filename == filename
1699 diffData.file_body = self.GetFileContent(diffData.base_filename, 1706 diffData.file_body = self.GetFileContent(diffData.base_filename,
1700 None, 1707 None,
1701 is_base_binary) 1708 is_base_binary)
1702 # Replicate svn's list of changed lines 1709 # Replicate svn's list of changed lines
1703 line_count = len(diffData.file_body.splitlines()) 1710 line_count = len(diffData.file_body.splitlines())
1704 diffData.change_summary = "@@ -1" 1711 diffData.change_summary = "@@ -1"
1705 if line_count > 1: 1712 if line_count > 1:
1706 diffData.change_summary += ",%d" % line_count 1713 diffData.change_summary += ",%d" % line_count
1707 diffData.change_summary += " +0,0 @@" 1714 diffData.change_summary += " +0,0 @@"
1708 diffData.prefix = "-" 1715 diffData.prefix = "-"
1709 return diffData 1716 return diffData
1710 1717
1711 changed_files = self.GetChangedFiles() 1718 changed_files = self.GetChangedFiles()
1712 1719
1713 svndiff = [] 1720 svndiff = []
1714 filecount = 0 1721 filecount = 0
1715 for (filename, action) in changed_files.items(): 1722 for (filename, action) in changed_files.items():
1716 svn_status = self.PerforceActionToSvnStatus(action) 1723 svn_status = self.PerforceActionToSvnStatus(action)
1717 if svn_status == "SKIP": 1724 if svn_status == "SKIP":
1718 continue 1725 continue
1719 1726
1720 diffData = DiffData(self, filename, action) 1727 diffData = DiffData(self, filename, action)
1721 # Is it possible to diff a branched file? Stackoverflow says no: 1728 # Is it possible to diff a branched file? Stackoverflow says no:
1722 # http://stackoverflow.com/questions/1771314/in-perforce-command-line-how- to-diff-a-file-reopened-for-add 1729 # http://stackoverflow.com/questions/1771314/in-perforce-command-line-how- to-diff-a-file-reopened-for-add
1723 if svn_status == "M": 1730 if svn_status == "M":
1724 diffData = GenerateMergeDiff(diffData, args) 1731 diffData = GenerateMergeDiff(diffData, args)
1725 elif svn_status == "A": 1732 elif svn_status == "A":
1726 diffData = GenerateAddDiff(diffData) 1733 diffData = GenerateAddDiff(diffData)
1727 elif svn_status == "D": 1734 elif svn_status == "D":
1728 diffData = GenerateDeleteDiff(diffData) 1735 diffData = GenerateDeleteDiff(diffData)
1729 else: 1736 else:
1730 ErrorExit("Unknown file action %s (svn action %s)." % \ 1737 ErrorExit("Unknown file action %s (svn action %s)." % \
1731 (action, svn_status)) 1738 (action, svn_status))
1732 1739
1733 svndiff += GenerateDiffHeader(diffData) 1740 svndiff += GenerateDiffHeader(diffData)
1734 1741
1735 for line in diffData.file_body.splitlines(): 1742 for line in diffData.file_body.splitlines():
1736 svndiff.append(diffData.prefix + line) 1743 svndiff.append(diffData.prefix + line)
1737 filecount += 1 1744 filecount += 1
1738 if not filecount: 1745 if not filecount:
1739 ErrorExit("No valid patches found in output from p4 diff") 1746 ErrorExit("No valid patches found in output from p4 diff")
1740 return "\n".join(svndiff) + "\n" 1747 return "\n".join(svndiff) + "\n"
1741 1748
1742 def PerforceActionToSvnStatus(self, status): 1749 def PerforceActionToSvnStatus(self, status):
1743 # Mirroring the list at http://permalink.gmane.org/gmane.comp.version-contro l.mercurial.devel/28717 1750 # Mirroring the list at http://permalink.gmane.org/gmane.comp.version-contro l.mercurial.devel/28717
1744 # Is there something more official? 1751 # Is there something more official?
1745 return { 1752 return {
1746 "add" : "A", 1753 "add" : "A",
1747 "branch" : "A", 1754 "branch" : "A",
1748 "delete" : "D", 1755 "delete" : "D",
1749 "edit" : "M", # Also includes changing file types. 1756 "edit" : "M", # Also includes changing file types.
1750 "integrate" : "M", 1757 "integrate" : "M",
1751 "move/add" : "M", 1758 "move/add" : "M",
1752 "move/delete": "SKIP", 1759 "move/delete": "SKIP",
1753 "purge" : "D", # How does a file's status become "purge"? 1760 "purge" : "D", # How does a file's status become "purge"?
1754 }[status] 1761 }[status]
1755 1762
1756 def GetAction(self, filename): 1763 def GetAction(self, filename):
1757 changed_files = self.GetChangedFiles() 1764 changed_files = self.GetChangedFiles()
1758 if not filename in changed_files: 1765 if not filename in changed_files:
1759 ErrorExit("Trying to get base version of unknown file %s." % filename) 1766 ErrorExit("Trying to get base version of unknown file %s." % filename)
1760 1767
1761 return changed_files[filename] 1768 return changed_files[filename]
1762 1769
1763 def GetBaseFile(self, filename): 1770 def GetBaseFile(self, filename):
1764 base_filename = self.GetBaseFilename(filename) 1771 base_filename = self.GetBaseFilename(filename)
1765 base_content = "" 1772 base_content = ""
1766 new_content = None 1773 new_content = None
1767 1774
1768 status = self.PerforceActionToSvnStatus(self.GetAction(filename)) 1775 status = self.PerforceActionToSvnStatus(self.GetAction(filename))
1769 1776
1770 if status != "A": 1777 if status != "A":
1771 revision = self.GetBaseRevision(base_filename) 1778 revision = self.GetBaseRevision(base_filename)
1772 if not revision: 1779 if not revision:
1773 ErrorExit("Couldn't find base revision for file %s" % filename) 1780 ErrorExit("Couldn't find base revision for file %s" % filename)
1774 is_base_binary = self.IsBaseBinary(base_filename) 1781 is_base_binary = self.IsBaseBinary(base_filename)
1775 base_content = self.GetFileContent(base_filename, 1782 base_content = self.GetFileContent(base_filename,
1776 revision, 1783 revision,
1777 is_base_binary) 1784 is_base_binary)
1778 1785
1779 is_binary = self.IsPendingBinary(filename) 1786 is_binary = self.IsPendingBinary(filename)
1780 if status != "D" and status != "SKIP": 1787 if status != "D" and status != "SKIP":
1781 relpath = self.GetLocalFilename(filename) 1788 relpath = self.GetLocalFilename(filename)
1782 if is_binary and self.IsImage(relpath): 1789 if is_binary and self.IsImage(relpath):
1783 new_content = open(relpath, "rb").read() 1790 new_content = open(relpath, "rb").read()
1784 1791
1785 return base_content, new_content, is_binary, status 1792 return base_content, new_content, is_binary, status
1786 1793
1787 # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync. 1794 # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync.
1788 def SplitPatch(data): 1795 def SplitPatch(data):
1789 """Splits a patch into separate pieces for each file. 1796 """Splits a patch into separate pieces for each file.
1790 1797
1791 Args: 1798 Args:
1792 data: A string containing the output of svn diff. 1799 data: A string containing the output of svn diff.
1793 1800
1794 Returns: 1801 Returns:
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
1864 and is one of VCS_GIT, VCS_MERCURIAL, VCS_SUBVERSION, VCS_PERFORCE, 1871 and is one of VCS_GIT, VCS_MERCURIAL, VCS_SUBVERSION, VCS_PERFORCE,
1865 VCS_CVS, or VCS_UNKNOWN. 1872 VCS_CVS, or VCS_UNKNOWN.
1866 Since local perforce repositories can't be easily detected, this method 1873 Since local perforce repositories can't be easily detected, this method
1867 will only guess VCS_PERFORCE if any perforce options have been specified. 1874 will only guess VCS_PERFORCE if any perforce options have been specified.
1868 output is a string containing any interesting output from the vcs 1875 output is a string containing any interesting output from the vcs
1869 detection routine, or None if there is nothing interesting. 1876 detection routine, or None if there is nothing interesting.
1870 """ 1877 """
1871 for attribute, value in options.__dict__.iteritems(): 1878 for attribute, value in options.__dict__.iteritems():
1872 if attribute.startswith("p4") and value != None: 1879 if attribute.startswith("p4") and value != None:
1873 return (VCS_PERFORCE, None) 1880 return (VCS_PERFORCE, None)
1874 1881
1875 def RunDetectCommand(vcs_type, command): 1882 def RunDetectCommand(vcs_type, command):
1876 """Helper to detect VCS by executing command. 1883 """Helper to detect VCS by executing command.
1877 1884
1878 Returns: 1885 Returns:
1879 A pair (vcs, output) or None. Throws exception on error. 1886 A pair (vcs, output) or None. Throws exception on error.
1880 """ 1887 """
1881 try: 1888 try:
1882 out, returncode = RunShellWithReturnCode(command) 1889 out, returncode = RunShellWithReturnCode(command)
1883 if returncode == 0: 1890 if returncode == 0:
1884 return (vcs_type, out.strip()) 1891 return (vcs_type, out.strip())
1885 except OSError, (errcode, message): 1892 except OSError, (errcode, message):
1886 if errcode != errno.ENOENT: # command not found code 1893 if errcode != errno.ENOENT: # command not found code
1887 raise 1894 raise
1888 1895
1889 # Mercurial has a command to get the base directory of a repository 1896 # Mercurial has a command to get the base directory of a repository
1890 # Try running it, but don't die if we don't have hg installed. 1897 # Try running it, but don't die if we don't have hg installed.
1891 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. 1898 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy.
1892 res = RunDetectCommand(VCS_MERCURIAL, ["hg", "root"]) 1899 res = RunDetectCommand(VCS_MERCURIAL, ["hg", "root"])
1893 if res != None: 1900 if res != None:
1894 return res 1901 return res
1895 1902
1896 # Subversion has a .svn in all working directories. 1903 # Subversion has a .svn in all working directories.
1897 if os.path.isdir('.svn'): 1904 if os.path.isdir('.svn'):
1898 logging.info("Guessed VCS = Subversion") 1905 logging.info("Guessed VCS = Subversion")
(...skipping 281 matching lines...) Expand 10 before | Expand all | Expand 10 after
2180 checksum = md5(info[0]).hexdigest() 2187 checksum = md5(info[0]).hexdigest()
2181 if base_hashes: 2188 if base_hashes:
2182 base_hashes += "|" 2189 base_hashes += "|"
2183 base_hashes += checksum + ":" + file 2190 base_hashes += checksum + ":" + file
2184 form_fields.append(("base_hashes", base_hashes)) 2191 form_fields.append(("base_hashes", base_hashes))
2185 if options.private: 2192 if options.private:
2186 if options.issue: 2193 if options.issue:
2187 print "Warning: Private flag ignored when updating an existing issue." 2194 print "Warning: Private flag ignored when updating an existing issue."
2188 else: 2195 else:
2189 form_fields.append(("private", "1")) 2196 form_fields.append(("private", "1"))
2197 if options.send_patch:
2198 options.send_mail = True
2190 # If we're uploading base files, don't send the email before the uploads, so 2199 # If we're uploading base files, don't send the email before the uploads, so
2191 # that it contains the file status. 2200 # that it contains the file status.
2192 if options.send_mail and options.download_base: 2201 if options.send_mail and options.download_base:
2193 form_fields.append(("send_mail", "1")) 2202 form_fields.append(("send_mail", "1"))
2194 if not options.download_base: 2203 if not options.download_base:
2195 form_fields.append(("content_upload", "1")) 2204 form_fields.append(("content_upload", "1"))
2196 if len(data) > MAX_UPLOAD_SIZE: 2205 if len(data) > MAX_UPLOAD_SIZE:
2197 print "Patch is large, so uploading file patches separately." 2206 print "Patch is large, so uploading file patches separately."
2198 uploaded_diff_file = [] 2207 uploaded_diff_file = []
2199 form_fields.append(("separate_patches", "1")) 2208 form_fields.append(("separate_patches", "1"))
(...skipping 19 matching lines...) Expand all
2219 issue = msg[msg.rfind("/")+1:] 2228 issue = msg[msg.rfind("/")+1:]
2220 2229
2221 if not uploaded_diff_file: 2230 if not uploaded_diff_file:
2222 result = UploadSeparatePatches(issue, rpc_server, patchset, data, options) 2231 result = UploadSeparatePatches(issue, rpc_server, patchset, data, options)
2223 if not options.download_base: 2232 if not options.download_base:
2224 patches = result 2233 patches = result
2225 2234
2226 if not options.download_base: 2235 if not options.download_base:
2227 vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files) 2236 vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files)
2228 if options.send_mail: 2237 if options.send_mail:
2229 rpc_server.Send("/" + issue + "/mail", payload="") 2238 payload = ""
2239 if options.send_patch:
2240 payload=urllib.urlencode({"attach_patch": "yes"})
2241 rpc_server.Send("/" + issue + "/mail", payload=payload)
2230 return issue, patchset 2242 return issue, patchset
2231 2243
2232 2244
2233 def main(): 2245 def main():
2234 try: 2246 try:
2235 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" 2247 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:"
2236 "%(lineno)s %(message)s ")) 2248 "%(lineno)s %(message)s "))
2237 os.environ['LC_ALL'] = 'C' 2249 os.environ['LC_ALL'] = 'C'
2238 RealMain(sys.argv) 2250 RealMain(sys.argv)
2239 except KeyboardInterrupt: 2251 except KeyboardInterrupt:
2240 print 2252 print
2241 StatusUpdate("Interrupted.") 2253 StatusUpdate("Interrupted.")
2242 sys.exit(1) 2254 sys.exit(1)
2243 2255
2244 2256
2245 if __name__ == "__main__": 2257 if __name__ == "__main__":
2246 main() 2258 main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698