Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(92)

Side by Side Diff: static/upload.py

Issue 14053: Bazaar Support For upload.py SVN Base: http://rietveld.googlecode.com/svn/trunk/
Patch Set: Utilized bzr root to detect when in subdirectory Created 8 months, 3 weeks ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file | no next file » | 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 #
11 # Unless required by applicable law or agreed to in writing, software 11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, 12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and 14 # See the License for the specific language governing permissions and
15 # limitations under the License. 15 # limitations under the License.
16 16
17 """Tool for uploading diffs from a version control system to the codereview app. 17 """Tool for uploading diffs from a version control system to the codereview app.
18 18
19 Usage summary: upload.py [options] [-- diff_options] 19 Usage summary: upload.py [options] [-- diff_options]
20 20
21 Diff options are passed to the diff command of the underlying system. 21 Diff options are passed to the diff command of the underlying system.
22 22
23 Supported version control systems: 23 Supported version control systems:
24 Bazaar
24 Git 25 Git
25 Mercurial 26 Mercurial
26 Subversion 27 Subversion
27 28
28 It is important for Git/Mercurial users to specify a tree/node/branch to diff 29 It is important for Git/Mercurial users to specify a tree/node/branch to diff
29 against by using the '--rev' option. 30 against by using the '--rev' option.
30 """ 31 """
31 # This code is derived from appcfg.py in the App Engine SDK (open source), 32 # This code is derived from appcfg.py in the App Engine SDK (open source),
32 # and from ASPN recipe #146306. 33 # and from ASPN recipe #146306.
33 34
(...skipping 534 matching lines...) Expand 10 before | Expand all | Expand 10 after
568 p.wait() 569 p.wait()
569 errout = p.stderr.read() 570 errout = p.stderr.read()
570 if print_output and errout: 571 if print_output and errout:
571 print >>sys.stderr, errout 572 print >>sys.stderr, errout
572 p.stdout.close() 573 p.stdout.close()
573 p.stderr.close() 574 p.stderr.close()
574 return output, p.returncode 575 return output, p.returncode
575 576
576 577
577 def RunShell(command, silent_ok=False, universal_newlines=True, 578 def RunShell(command, silent_ok=False, universal_newlines=True,
578 print_output=False): 579 print_output=False,check_returncode=True):
579 data, retcode = RunShellWithReturnCode(command, print_output, 580 data, retcode = RunShellWithReturnCode(command, print_output,
580 universal_newlines) 581 universal_newlines)
581 if retcode: 582
583 if retcode and check_returncode:
582 ErrorExit("Got error status from %s:\n%s" % (command, data)) 584 ErrorExit("Got error status from %s:\n%s" % (command, data))
583 if not silent_ok and not data: 585 if not silent_ok and not data:
584 ErrorExit("No output from %s" % command) 586 ErrorExit("No output from %s" % command)
585 return data 587 return data
586 588
587 589
588 class VersionControlSystem(object): 590 class VersionControlSystem(object):
589 """Abstract base class providing an interface to the VCS.""" 591 """Abstract base class providing an interface to the VCS."""
590 592
591 def __init__(self, options): 593 def __init__(self, options):
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
634 is_binary: True iff the file is binary. 636 is_binary: True iff the file is binary.
635 status: The status of the file. 637 status: The status of the file.
636 """ 638 """
637 639
638 raise NotImplementedError( 640 raise NotImplementedError(
639 "abstract method -- subclass %s must override" % self.__class__) 641 "abstract method -- subclass %s must override" % self.__class__)
640 642
641 643
642 def GetBaseFiles(self, diff): 644 def GetBaseFiles(self, diff):
643 """Helper that calls GetBase file for each file in the patch. 645 """Helper that calls GetBase file for each file in the patch.
644 646
645 Returns: 647 Returns:
646 A dictionary that maps from filename to GetBaseFile's tuple. Filenames 648 A dictionary that maps from filename to GetBaseFile's tuple. Filenames
647 are retrieved based on lines that start with "Index:" or 649 are retrieved based on lines that start with "Index:" or
648 "Property changes on:". 650 "Property changes on:".
649 """ 651 """
650 files = {} 652 files = {}
651 for line in diff.splitlines(True): 653 for line in diff.splitlines(True):
652 if line.startswith('Index:') or line.startswith('Property changes on:'): 654 if line.startswith('Index:') or line.startswith('Property changes on:'):
653 unused, filename = line.split(':', 1) 655 unused, filename = line.split(':', 1)
654 # On Windows if a file has property changes its filename uses '\' 656 # On Windows if a file has property changes its filename uses '\'
(...skipping 453 matching lines...) Expand 10 before | Expand all | Expand 10 after
1108 is_binary = False 1110 is_binary = False
1109 oldrelpath = relpath = self._GetRelPath(filename) 1111 oldrelpath = relpath = self._GetRelPath(filename)
1110 # "hg status -C" returns two lines for moved/copied files, one otherwise 1112 # "hg status -C" returns two lines for moved/copied files, one otherwise
1111 out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath]) 1113 out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath])
1112 out = out.splitlines() 1114 out = out.splitlines()
1113 # HACK: strip error message about missing file/directory if it isn't in 1115 # HACK: strip error message about missing file/directory if it isn't in
1114 # the working copy 1116 # the working copy
1115 if out[0].startswith('%s: ' % relpath): 1117 if out[0].startswith('%s: ' % relpath):
1116 out = out[1:] 1118 out = out[1:]
1117 if len(out) > 1: 1119 if len(out) > 1:
1118 # Moved/copied => considered as modified, use old filename to 1120 # Moved/copied => considered as modified, use old filename to
1119 # retrieve base contents 1121 # retrieve base contents
1120 oldrelpath = out[1].strip() 1122 oldrelpath = out[1].strip()
1121 status = "M" 1123 status = "M"
1122 else: 1124 else:
1123 status, _ = out[0].split(' ', 1) 1125 status, _ = out[0].split(' ', 1)
1124 if status != "A": 1126 if status != "A":
1125 base_content = RunShell(["hg", "cat", "-r", self.base_rev, oldrelpath], 1127 base_content = RunShell(["hg", "cat", "-r", self.base_rev, oldrelpath],
1126 silent_ok=True) 1128 silent_ok=True)
1127 is_binary = "\0" in base_content # Mercurial's heuristic 1129 is_binary = "\0" in base_content # Mercurial's heuristic
1128 if status != "R": 1130 if status != "R":
1129 new_content = open(relpath, "rb").read() 1131 new_content = open(relpath, "rb").read()
1130 is_binary = is_binary or "\0" in new_content 1132 is_binary = is_binary or "\0" in new_content
1131 if is_binary and base_content: 1133 if is_binary and base_content:
1132 # Fetch again without converting newlines 1134 # Fetch again without converting newlines
1133 base_content = RunShell(["hg", "cat", "-r", self.base_rev, oldrelpath], 1135 base_content = RunShell(["hg", "cat", "-r", self.base_rev, oldrelpath],
1134 silent_ok=True, universal_newlines=False) 1136 silent_ok=True, universal_newlines=False)
1135 if not is_binary or not self.IsImage(relpath): 1137 if not is_binary or not self.IsImage(relpath):
1136 new_content = None 1138 new_content = None
1137 return base_content, new_content, is_binary, status 1139 return base_content, new_content, is_binary, status
1140
1141
1142 class BazaarVCS(VersionControlSystem):
1143 """Implementation of the VersionControlSystem interface for Bazaar."""
1144
1145 def GenerateDiff(self, args):
1146 """Return the current diff as a string.
1147
1148 Args:
1149 args: Extra arguments to pass to the diff command.
1150 """
1151 if self.options.revision:
1152 args = ["-r", self.options.revision] + args
1153 # We need check_returncode = False because bzr diff returns 1 if changes
1154 # are found.
1155
1156 data,retcode = RunShellWithReturnCode(["bzr", "diff"] + args, True)
1157 filecount = 0
1158 lines = data.splitlines()
1159 for i, line in enumerate(lines):
1160 match = re.match(r"^=== (added|removed|modified) file '(.*)'$", line)
1161 if match:
1162 # Modify line to make it look like as it comes from svn diff.
1163 lines[i] = "Index: %s" % match.group(2)
1164 filecount += 1
1165 logging.info(line)
1166 if not filecount:
1167 ErrorExit("No valid patches found in output from bzr diff")
1168 return "\n".join(lines)
1169
1170 def GetUnknownFiles(self):
1171 """Return a list of files unknown to the VCS."""
1172 args = ["-S"]
1173 if self.options.revision:
1174 args += ["-r", self.options.revision]
1175 status = RunShell(["bzr", "status"] + args, silent_ok=True)
1176 unknown_files = []
1177 for line in status.splitlines():
1178 if line and line[0] == "?":
1179 unknown_files.append(line)
1180 return unknown_files
1181
1182 def GetBaseFile(self, filename):
1183 """Get the content of the upstream version of a file.
1184
1185 Returns:
1186 A tuple (content, status) representing the file content and the status of
1187 the file.
1188 """
1189 st_args = []
1190 cat_args = []
1191 new_content = None
1192 if self.options.revision:
1193 if '..' in self.options.revision:
1194 start_rev = self.options.revision.split('..', 1)[0]
1195 cat_args += ["-r", start_rev]
1196 else:
1197 cat_args += ["-r", self.options.revision]
1198 st_args += ["-r", self.options.revision]
1199 status = RunShell(["bzr", "status", "-S"] + st_args + [filename])
1200 if status[1] == "N":
1201 content = ""
1202 else:
1203 content = RunShell(["bzr", "cat"] + cat_args + [filename])
1204 return content, new_content, False, status[0:4]
1138 1205
1139 1206
1140 # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync. 1207 # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync.
1141 def SplitPatch(data): 1208 def SplitPatch(data):
1142 """Splits a patch into separate pieces for each file. 1209 """Splits a patch into separate pieces for each file.
1143 1210
1144 Args: 1211 Args:
1145 data: A string containing the output of svn diff. 1212 data: A string containing the output of svn diff.
1146 1213
1147 Returns: 1214 Returns:
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
1219 # Mercurial has a command to get the base directory of a repository 1286 # Mercurial has a command to get the base directory of a repository
1220 # Try running it, but don't die if we don't have hg installed. 1287 # Try running it, but don't die if we don't have hg installed.
1221 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. 1288 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy.
1222 try: 1289 try:
1223 out, returncode = RunShellWithReturnCode(["hg", "root"]) 1290 out, returncode = RunShellWithReturnCode(["hg", "root"])
1224 if returncode == 0: 1291 if returncode == 0:
1225 return MercurialVCS(options, out.strip()) 1292 return MercurialVCS(options, out.strip())
1226 except OSError, (errno, message): 1293 except OSError, (errno, message):
1227 if errno != 2: # ENOENT -- they don't have hg installed. 1294 if errno != 2: # ENOENT -- they don't have hg installed.
1228 raise 1295 raise
1229 1296 try:
bialix 2009/03/24 22:49:23 I think you need either use command ["bzr", "--no-
1297 out, returncode = RunShellWithReturnCode(["bzr", "root"])
1298 if returncode == 0:
1299 os.chdir(out.strip())
1300 return BazaarVCS(options)
1301 except OSError, (errno, message):
1302 if errno != 2: # ENOENT -- they don't have bzr installed.
1303 raise
1230 # Subversion has a .svn in all working directories. 1304 # Subversion has a .svn in all working directories.
1231 if os.path.isdir('.svn'): 1305 if os.path.isdir('.svn'):
1232 logging.info("Guessed VCS = Subversion") 1306 logging.info("Guessed VCS = Subversion")
1233 return SubversionVCS(options) 1307 return SubversionVCS(options)
1234 1308
1235 # Git has a command to test if you're in a git tree. 1309 # Git has a command to test if you're in a git tree.
1236 # Try running it, but don't die if we don't have git installed. 1310 # Try running it, but don't die if we don't have git installed.
1237 try: 1311 try:
1238 out, returncode = RunShellWithReturnCode(["git", "rev-parse", 1312 out, returncode = RunShellWithReturnCode(["git", "rev-parse",
1239 "--is-inside-work-tree"]) 1313 "--is-inside-work-tree"])
1240 if returncode == 0: 1314 if returncode == 0:
1241 return GitVCS(options) 1315 return GitVCS(options)
1242 except OSError, (errno, message): 1316 except OSError, (errno, message):
1243 if errno != 2: # ENOENT -- they don't have git installed. 1317 if errno != 2: # ENOENT -- they don't have git installed.
1244 raise 1318 raise
1245 1319
1246 ErrorExit(("Could not guess version control system. " 1320 ErrorExit(("Could not guess version control system. "
1247 "Are you in a working copy directory?")) 1321 "Are you in a working copy directory?"))
1248 1322
1249 1323
1250 def RealMain(argv, data=None): 1324 def RealMain(argv, data=None):
1251 """The real main function.
1252
1253 Args:
1254 argv: Command line arguments.
1255 data: Diff contents. If None (default) the diff is generated by
1256 the VersionControlSystem implementation returned by GuessVCS().
1257
1258 Returns:
1259 A 2-tuple (issue id, patchset id).
1260 The patchset id is None if the base files are not uploaded by this
1261 script (applies only to SVN checkouts).
1262 """
1263 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" 1325 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:"
1264 "%(lineno)s %(message)s ")) 1326 "%(lineno)s %(message)s "))
1265 os.environ['LC_ALL'] = 'C' 1327 os.environ['LC_ALL'] = 'C'
1266 options, args = parser.parse_args(argv[1:]) 1328 options, args = parser.parse_args(argv[1:])
1267 global verbosity 1329 global verbosity
1268 verbosity = options.verbose 1330 verbosity = options.verbose
1269 if verbosity >= 3: 1331 if verbosity >= 3:
1270 logging.getLogger().setLevel(logging.DEBUG) 1332 logging.getLogger().setLevel(logging.DEBUG)
1271 elif verbosity >= 2: 1333 elif verbosity >= 2:
1272 logging.getLogger().setLevel(logging.INFO) 1334 logging.getLogger().setLevel(logging.INFO)
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
1338 if not options.download_base: 1400 if not options.download_base:
1339 form_fields.append(("content_upload", "1")) 1401 form_fields.append(("content_upload", "1"))
1340 if len(data) > MAX_UPLOAD_SIZE: 1402 if len(data) > MAX_UPLOAD_SIZE:
1341 print "Patch is large, so uploading file patches separately." 1403 print "Patch is large, so uploading file patches separately."
1342 uploaded_diff_file = [] 1404 uploaded_diff_file = []
1343 form_fields.append(("separate_patches", "1")) 1405 form_fields.append(("separate_patches", "1"))
1344 else: 1406 else:
1345 uploaded_diff_file = [("data", "data.diff", data)] 1407 uploaded_diff_file = [("data", "data.diff", data)]
1346 ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file) 1408 ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file)
1347 response_body = rpc_server.Send("/upload", body, content_type=ctype) 1409 response_body = rpc_server.Send("/upload", body, content_type=ctype)
1348 patchset = None
1349 if not options.download_base or not uploaded_diff_file: 1410 if not options.download_base or not uploaded_diff_file:
1350 lines = response_body.splitlines() 1411 lines = response_body.splitlines()
1351 if len(lines) >= 2: 1412 if len(lines) >= 2:
1352 msg = lines[0] 1413 msg = lines[0]
1353 patchset = lines[1].strip() 1414 patchset = lines[1].strip()
1354 patches = [x.split(" ", 1) for x in lines[2:]] 1415 patches = [x.split(" ", 1) for x in lines[2:]]
1355 else: 1416 else:
1356 msg = response_body 1417 msg = response_body
1357 else: 1418 else:
1358 msg = response_body 1419 msg = response_body
(...skipping 19 matching lines...) Expand all
1378 try: 1439 try:
1379 RealMain(sys.argv) 1440 RealMain(sys.argv)
1380 except KeyboardInterrupt: 1441 except KeyboardInterrupt:
1381 print 1442 print
1382 StatusUpdate("Interrupted.") 1443 StatusUpdate("Interrupted.")
1383 sys.exit(1) 1444 sys.exit(1)
1384 1445
1385 1446
1386 if __name__ == "__main__": 1447 if __name__ == "__main__":
1387 main() 1448 main()
OLDNEW
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld r497