| OLD | NEW |
| 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 subversion diffs to the codereview app. | 17 """Tool for uploading subversion diffs to the codereview app. |
| 18 | 18 |
| 19 Usage summary: upload.py [options] [-- svn_diff_options] | 19 Usage summary: upload.py [options] [-- svn_diff_options] |
| 20 """ | 20 """ |
| 21 # This code is derived from appcfg.py in the App Engine SDK (open source), | 21 # This code is derived from appcfg.py in the App Engine SDK (open source), |
| 22 # and from ASPN recipe #146306. | 22 # and from ASPN recipe #146306. |
| 23 | 23 |
| 24 import cookielib | 24 import cookielib |
| 25 import getpass | 25 import getpass |
| 26 import logging | 26 import logging |
| 27 import mimetypes | 27 import mimetypes |
| 28 import optparse | 28 import optparse |
| 29 import os | 29 import os |
| 30 import socket | 30 import socket |
| 31 import sys | 31 import sys |
| 32 import urllib | 32 import urllib |
| 33 import urllib2 | 33 import urllib2 |
| 34 import urlparse | 34 import urlparse |
| 35 | 35 |
| 36 | 36 |
| 37 # The logging verbosity: | 37 # The logging verbosity: |
| 38 # 0: Errors only. | 38 # 0: Errors only. |
| 39 # 1: Status messages. | 39 # 1: Status messages. |
| 40 # 2: Info logs. | 40 # 2: Info logs. |
| 41 # 3: Debug logs. | 41 # 3: Debug logs. |
| 42 verbosity = 1 | 42 verbosity = 1 |
| 43 | 43 |
| 44 | 44 |
| 45 def StatusUpdate(msg): | 45 def StatusUpdate(msg): |
| 46 """Print a status message to stdout. | 46 """Print a status message to stdout. |
| 47 | 47 |
| 48 If 'verbosity' is greater than 0, print the message. | 48 If 'verbosity' is greater than 0, print the message. |
| 49 | 49 |
| 50 Args: | 50 Args: |
| (...skipping 382 matching lines...) Show 10 above Show 10 below |
| 433 lines.append(value) | 433 lines.append(value) |
| 434 lines.append('--' + BOUNDARY + '--') | 434 lines.append('--' + BOUNDARY + '--') |
| 435 lines.append('') | 435 lines.append('') |
| 436 body = CRLF.join(lines) | 436 body = CRLF.join(lines) |
| 437 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY | 437 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY |
| 438 return content_type, body | 438 return content_type, body |
| 439 | 439 |
| 440 | 440 |
| 441 def GetContentType(filename): | 441 def GetContentType(filename): |
| 442 """Helper to guess the content-type from the filename.""" | 442 """Helper to guess the content-type from the filename.""" |
| 443 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' | 443 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' |
| 444 | 444 |
| 445 | 445 |
| 446 def RunShell(command, args=(), silent_ok=False): | 446 def RunShell(command, args=(), silent_ok=False): |
| 447 logging.info("Running %s", command) | 447 logging.info("Running %s", command) |
| 448 stream = os.popen("%s %s" % (command, " ".join(args)), "r") | 448 stream = os.popen("%s %s" % (command, " ".join(args)), "r") |
| 449 data = stream.read() | 449 data = stream.read() |
| 450 if stream.close(): | 450 if stream.close(): |
| 451 ErrorExit("Got error status from %s" % command) | 451 ErrorExit("Got error status from %s" % command) |
| 452 if not silent_ok and not data: | 452 if not silent_ok and not data: |
| 453 ErrorExit("No output from %s" % command) | 453 ErrorExit("No output from %s" % command) |
| 454 return data | 454 return data |
| 455 | 455 |
| 456 | 456 |
| 457 def GuessBase(): | 457 def GuessBase(): |
| 458 info = RunShell("svn info") | 458 info = RunShell("svn info") |
| 459 for line in info.splitlines(): | 459 for line in info.splitlines(): |
| 460 words = line.split() | 460 words = line.split() |
| 461 if len(words) == 2 and words[0] == "URL:": | 461 if len(words) == 2 and words[0] == "URL:": |
| 462 url = words[1] | 462 url = words[1] |
| 463 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) | 463 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) |
| 464 if netloc.endswith("svn.python.org"): | 464 if netloc.endswith("svn.python.org"): |
| 465 if netloc == "svn.python.org": | 465 if netloc == "svn.python.org": |
| 466 if path.startswith("/projects/"): | 466 if path.startswith("/projects/"): |
| 467 path = path[9:] | 467 path = path[9:] |
| 468 elif netloc != "pythondev@svn.python.org": | 468 elif netloc != "pythondev@svn.python.org": |
| 469 ErrorExit("Unrecognized Python URL: %s" % url) | 469 ErrorExit("Unrecognized Python URL: %s" % url) |
| 470 base = "http://svn.python.org/view/*checkout*%s/" % path | 470 base = "http://svn.python.org/view/*checkout*%s/" % path |
| 471 logging.info("Guessed Python base = %s", base) | 471 logging.info("Guessed Python base = %s", base) |
| 472 elif netloc.endswith("svn.collab.net"): | 472 elif netloc.endswith("svn.collab.net"): |
| 473 if path.startswith("/repos/"): | 473 if path.startswith("/repos/"): |
| 474 path = path[6:] | 474 path = path[6:] |
| 475 base = "http://svn.collab.net/viewvc/*checkout*%s/" % path | 475 base = "http://svn.collab.net/viewvc/*checkout*%s/" % path |
| 476 logging.info("Guessed CollabNet base = %s", base) | 476 logging.info("Guessed CollabNet base = %s", base) |
| 477 elif netloc.endswith(".googlecode.com"): | 477 elif netloc.endswith(".googlecode.com"): |
| 478 base = url + "/" | 478 base = url + "/" |
| 479 if base.startswith("https"): | 479 if base.startswith("https"): |
| 480 base = "http" + base[5:] | 480 base = "http" + base[5:] |
| 481 logging.info("Guessed Google Code base = %s", base) | 481 logging.info("Guessed Google Code base = %s", base) |
| 482 else: | 482 else: |
| 483 ErrorExit("Unrecognized svn project root: %s" % url) | 483 base = url + "/" |
| 484 #print netloc |
| 485 #ErrorExit("Unrecognized svn project root: %s" % url) |
| 484 return base | 486 return base |
| 485 ErrorExit("Can't find URL in output from svn info") | 487 ErrorExit("Can't find URL in output from svn info") |
| 486 | 488 |
| 487 | 489 |
| 488 def RealMain(argv): | 490 def RealMain(argv): |
| 489 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" | 491 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" |
| 490 "%(lineno)s %(message)s ")) | 492 "%(lineno)s %(message)s ")) |
| 491 options, args = parser.parse_args(sys.argv[1:]) | 493 options, args = parser.parse_args(sys.argv[1:]) |
| 492 global verbosity | 494 global verbosity |
| 493 verbosity = options.verbose | 495 verbosity = options.verbose |
| 494 if verbosity >= 3: | 496 if verbosity >= 3: |
| 495 logging.getLogger().setLevel(logging.DEBUG) | 497 logging.getLogger().setLevel(logging.DEBUG) |
| 496 elif verbosity >= 2: | 498 elif verbosity >= 2: |
| 497 logging.getLogger().setLevel(logging.INFO) | 499 logging.getLogger().setLevel(logging.INFO) |
| 498 base = GuessBase() | 500 #base = GuessBase() |
| 499 CheckForUnknownFiles() | 501 base = "http://svn.scipy.org/svn/numpy/trunk" |
| 500 data = RunShell("svn diff", args) | 502 #CheckForUnknownFiles() |
| 503 data = RunShell("hg diff", args) |
| 504 #print data |
| 501 count = 0 | 505 count = 0 |
| 502 for line in data.splitlines(): | 506 for line in data.splitlines(): |
| 503 if line.startswith("Index:"): | 507 if line.startswith("diff"): |
| 504 count += 1 | 508 count += 1 |
| 505 logging.info(line) | 509 logging.info(line) |
| 506 if not count: | 510 if not count: |
| 507 ErrorExit("No valid patches found in output from svn diff") | 511 ErrorExit("No valid patches found in output from svn diff") |
| 508 if options.issue: | 512 if options.issue: |
| 509 prompt = "Message describing this patch set: " | 513 prompt = "Message describing this patch set: " |
| 510 else: | 514 else: |
| 511 prompt = "New issue subject: " | 515 prompt = "New issue subject: " |
| 512 message = options.message or raw_input(prompt).strip() | 516 message = options.message or raw_input(prompt).strip() |
| 513 if not message: | 517 if not message: |
| 514 ErrorExit("A non-empty message is required") | 518 ErrorExit("A non-empty message is required") |
| 515 rpc_server = GetRpcServer(options) | 519 rpc_server = GetRpcServer(options) |
| 516 form_fields = [("base", base), ("subject", message)] | 520 form_fields = [("base", base), ("subject", message)] |
| 517 if options.issue: | 521 if options.issue: |
| 518 form_fields.append(("issue", str(options.issue))) | 522 form_fields.append(("issue", str(options.issue))) |
| 519 if options.email: | 523 if options.email: |
| 520 form_fields.append(("user", options.email)) | 524 form_fields.append(("user", options.email)) |
| 521 ctype, body = EncodeMultipartFormData(form_fields, | 525 ctype, body = EncodeMultipartFormData(form_fields, |
| 522 [("data", "data.diff", data)]) | 526 [("data", "data.diff", data)]) |
| 523 response_body = rpc_server.Send("/upload", body, content_type=ctype) | 527 response_body = rpc_server.Send("/upload", body, content_type=ctype) |
| 524 StatusUpdate(response_body) | 528 StatusUpdate(response_body) |
| 525 sys.exit(not response_body.startswith("Issue created.")) | 529 sys.exit(not response_body.startswith("Issue created.")) |
| 526 | 530 |
| 527 def CheckForUnknownFiles(): | 531 def CheckForUnknownFiles(): |
| 528 status = RunShell("svn status --ignore-externals", silent_ok=True) | 532 status = RunShell("svn status --ignore-externals", silent_ok=True) |
| 529 unknown_files = [] | 533 unknown_files = [] |
| 530 for line in status.split("\n"): | 534 for line in status.split("\n"): |
| 531 if line and line[0] == "?": | 535 if line and line[0] == "?": |
| 532 unknown_files.append(line) | 536 unknown_files.append(line) |
| 533 if unknown_files: | 537 if unknown_files: |
| 534 print "The following files are not added to version control:" | 538 print "The following files are not added to version control:" |
| 535 for line in unknown_files: | 539 for line in unknown_files: |
| 536 print line | 540 print line |
| 537 prompt = "Are you sure to continue?(y/N) " | 541 prompt = "Are you sure to continue?(y/N) " |
| 538 answer = raw_input(prompt).strip() | 542 answer = raw_input(prompt).strip() |
| 539 if answer != "y": | 543 if answer != "y": |
| 540 ErrorExit("User aborted") | 544 ErrorExit("User aborted") |
| 541 | 545 |
| 542 def main(): | 546 def main(): |
| 543 try: | 547 try: |
| 544 RealMain(sys.argv) | 548 RealMain(sys.argv) |
| 545 except KeyboardInterrupt: | 549 except KeyboardInterrupt: |
| 546 print | 550 print |
| 547 StatusUpdate("Interrupted.") | 551 StatusUpdate("Interrupted.") |
| 548 sys.exit(1) | 552 sys.exit(1) |
| 549 | 553 |
| 550 | 554 |
| 551 if __name__ == "__main__": | 555 if __name__ == "__main__": |
| 552 main() | 556 main() |
| OLD | NEW |