| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # coding: utf-8 | 2 # coding: utf-8 |
| 3 # | 3 # |
| 4 # Copyright 2007 Google Inc. | 4 # Copyright 2007 Google Inc. |
| 5 # | 5 # |
| 6 # Licensed under the Apache License, Version 2.0 (the "License"); | 6 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 # you may not use this file except in compliance with the License. | 7 # you may not use this file except in compliance with the License. |
| 8 # You may obtain a copy of the License at | 8 # You may obtain a copy of the License at |
| 9 # | 9 # |
| 10 # http://www.apache.org/licenses/LICENSE-2.0 | 10 # http://www.apache.org/licenses/LICENSE-2.0 |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 96 | 96 |
| 97 # Constants for version control names. Used by GuessVCSName. | 97 # Constants for version control names. Used by GuessVCSName. |
| 98 VCS_GIT = "Git" | 98 VCS_GIT = "Git" |
| 99 VCS_MERCURIAL = "Mercurial" | 99 VCS_MERCURIAL = "Mercurial" |
| 100 VCS_SUBVERSION = "Subversion" | 100 VCS_SUBVERSION = "Subversion" |
| 101 VCS_PERFORCE = "Perforce" | 101 VCS_PERFORCE = "Perforce" |
| 102 VCS_CVS = "CVS" | 102 VCS_CVS = "CVS" |
| 103 VCS_UNKNOWN = "Unknown" | 103 VCS_UNKNOWN = "Unknown" |
| 104 | 104 |
| 105 VCS = [ | 105 VCS = [ |
| 106 { | 106 {'name': VCS_MERCURIAL, |
| 107 'name': VCS_MERCURIAL, | 107 'aliases': ['hg', 'mercurial']}, |
| 108 'aliases': ['hg', 'mercurial'], | 108 {'name': VCS_SUBVERSION, |
| 109 }, { | 109 'aliases': ['svn', 'subversion'],}, |
| 110 'name': VCS_SUBVERSION, | 110 {'name': VCS_PERFORCE, |
| 111 'aliases': ['svn', 'subversion'], | 111 'aliases': ['p4', 'perforce']}, |
| 112 }, { | 112 {'name': VCS_GIT, |
| 113 'name': VCS_PERFORCE, | 113 'aliases': ['git']}, |
| 114 'aliases': ['p4', 'perforce'], | 114 {'name': VCS_CVS, |
| 115 }, { | 115 'aliases': ['cvs']}, |
| 116 'name': VCS_GIT, | 116 ] |
| 117 'aliases': ['git'], | 117 |
| 118 }, { | |
| 119 'name': VCS_CVS, | |
| 120 'aliases': ['cvs'], | |
| 121 }] | |
| 122 | 118 |
| 123 VCS_SHORT_NAMES = [] # hg, svn, ... | 119 VCS_SHORT_NAMES = [] # hg, svn, ... |
| 124 VCS_ABBREVIATIONS = {} # alias: name, ... | 120 VCS_ABBREVIATIONS = {} # alias: name, ... |
| 125 for vcs in VCS: | 121 for vcs in VCS: |
| 126 VCS_SHORT_NAMES.append(min(vcs['aliases'], key=len)) | 122 VCS_SHORT_NAMES.append(min(vcs['aliases'], key=len)) |
| 127 VCS_ABBREVIATIONS.update((alias, vcs['name']) for alias in vcs['aliases']) | 123 VCS_ABBREVIATIONS.update((alias, vcs['name']) for alias in vcs['aliases']) |
| 128 | 124 |
| 129 | 125 |
| 130 # OAuth 2.0-Related Constants | 126 # OAuth 2.0-Related Constants |
| 131 LOCALHOST_IP = '127.0.0.1' | 127 LOCALHOST_IP = '127.0.0.1' |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 211 | 207 |
| 212 Args: | 208 Args: |
| 213 msg: The string to print. | 209 msg: The string to print. |
| 214 """ | 210 """ |
| 215 if verbosity > 0: | 211 if verbosity > 0: |
| 216 print msg | 212 print msg |
| 217 | 213 |
| 218 | 214 |
| 219 def ErrorExit(msg): | 215 def ErrorExit(msg): |
| 220 """Print an error message to stderr and exit.""" | 216 """Print an error message to stderr and exit.""" |
| 221 print >>sys.stderr, msg | 217 print >> sys.stderr, msg |
| 222 sys.exit(1) | 218 sys.exit(1) |
| 223 | 219 |
| 224 | 220 |
| 225 class ClientLoginError(urllib2.HTTPError): | 221 class ClientLoginError(urllib2.HTTPError): |
| 226 """Raised to indicate there was an error authenticating with ClientLogin.""" | 222 """Raised to indicate there was an error authenticating with ClientLogin.""" |
| 227 | 223 |
| 228 def __init__(self, url, code, msg, headers, args): | 224 def __init__(self, url, code, msg, headers, args): |
| 229 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) | 225 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) |
| 230 self.args = args | 226 self.args = args |
| 231 self._reason = args["Error"] | 227 self._reason = args["Error"] |
| (...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 374 | 370 |
| 375 If we attempt to access the upload API without first obtaining an | 371 If we attempt to access the upload API without first obtaining an |
| 376 authentication cookie, it returns a 401 response (or a 302) and | 372 authentication cookie, it returns a 401 response (or a 302) and |
| 377 directs us to authenticate ourselves with ClientLogin. | 373 directs us to authenticate ourselves with ClientLogin. |
| 378 """ | 374 """ |
| 379 for i in range(3): | 375 for i in range(3): |
| 380 credentials = self.auth_function() | 376 credentials = self.auth_function() |
| 381 try: | 377 try: |
| 382 auth_token = self._GetAuthToken(credentials[0], credentials[1]) | 378 auth_token = self._GetAuthToken(credentials[0], credentials[1]) |
| 383 except ClientLoginError, e: | 379 except ClientLoginError, e: |
| 384 print >>sys.stderr, '' | 380 print >> sys.stderr, '' |
| 385 if e.reason == "BadAuthentication": | 381 if e.reason == "BadAuthentication": |
| 386 if e.info == "InvalidSecondFactor": | 382 if e.info == "InvalidSecondFactor": |
| 387 print >>sys.stderr, ( | 383 print >> sys.stderr, ( |
| 388 "Use an application-specific password instead " | 384 "Use an application-specific password instead " |
| 389 "of your regular account password.\n" | 385 "of your regular account password.\n" |
| 390 "See http://www.google.com/" | 386 "See http://www.google.com/" |
| 391 "support/accounts/bin/answer.py?answer=185833") | 387 "support/accounts/bin/answer.py?answer=185833") |
| 392 else: | 388 else: |
| 393 print >>sys.stderr, "Invalid username or password." | 389 print >> sys.stderr, "Invalid username or password." |
| 394 elif e.reason == "CaptchaRequired": | 390 elif e.reason == "CaptchaRequired": |
| 395 print >>sys.stderr, ( | 391 print >> sys.stderr, ( |
| 396 "Please go to\n" | 392 "Please go to\n" |
| 397 "https://www.google.com/accounts/DisplayUnlockCaptcha\n" | 393 "https://www.google.com/accounts/DisplayUnlockCaptcha\n" |
| 398 "and verify you are a human. Then try again.\n" | 394 "and verify you are a human. Then try again.\n" |
| 399 "If you are using a Google Apps account the URL is:\n" | 395 "If you are using a Google Apps account the URL is:\n" |
| 400 "https://www.google.com/a/yourdomain.com/UnlockCaptcha") | 396 "https://www.google.com/a/yourdomain.com/UnlockCaptcha") |
| 401 elif e.reason == "NotVerified": | 397 elif e.reason == "NotVerified": |
| 402 print >>sys.stderr, "Account not verified." | 398 print >> sys.stderr, "Account not verified." |
| 403 elif e.reason == "TermsNotAgreed": | 399 elif e.reason == "TermsNotAgreed": |
| 404 print >>sys.stderr, "User has not agreed to TOS." | 400 print >> sys.stderr, "User has not agreed to TOS." |
| 405 elif e.reason == "AccountDeleted": | 401 elif e.reason == "AccountDeleted": |
| 406 print >>sys.stderr, "The user account has been deleted." | 402 print >> sys.stderr, "The user account has been deleted." |
| 407 elif e.reason == "AccountDisabled": | 403 elif e.reason == "AccountDisabled": |
| 408 print >>sys.stderr, "The user account has been disabled." | 404 print >> sys.stderr, "The user account has been disabled." |
| 409 break | 405 break |
| 410 elif e.reason == "ServiceDisabled": | 406 elif e.reason == "ServiceDisabled": |
| 411 print >>sys.stderr, ("The user's access to the service has been " | 407 print >> sys.stderr, ("The user's access to the service has been " |
| 412 "disabled.") | 408 "disabled.") |
| 413 elif e.reason == "ServiceUnavailable": | 409 elif e.reason == "ServiceUnavailable": |
| 414 print >>sys.stderr, "The service is not available; try again later." | 410 print >> sys.stderr, "The service is not available; try again later." |
| 415 else: | 411 else: |
| 416 # Unknown error. | 412 # Unknown error. |
| 417 raise | 413 raise |
| 418 print >>sys.stderr, '' | 414 print >> sys.stderr, '' |
| 419 continue | 415 continue |
| 420 self._GetAuthCookie(auth_token) | 416 self._GetAuthCookie(auth_token) |
| 421 return | 417 return |
| 422 | 418 |
| 423 def Send(self, request_path, payload=None, | 419 def Send(self, request_path, payload=None, |
| 424 content_type="application/octet-stream", | 420 content_type="application/octet-stream", |
| 425 timeout=None, | 421 timeout=None, |
| 426 extra_headers=None, | 422 extra_headers=None, |
| 427 **kwargs): | 423 **kwargs): |
| 428 """Sends an RPC and returns the response. | 424 """Sends an RPC and returns the response. |
| (...skipping 26 matching lines...) Expand all Loading... |
| 455 args = dict(kwargs) | 451 args = dict(kwargs) |
| 456 url = "%s%s" % (self.host, request_path) | 452 url = "%s%s" % (self.host, request_path) |
| 457 if args: | 453 if args: |
| 458 url += "?" + urllib.urlencode(args) | 454 url += "?" + urllib.urlencode(args) |
| 459 req = self._CreateRequest(url=url, data=payload) | 455 req = self._CreateRequest(url=url, data=payload) |
| 460 req.add_header("Content-Type", content_type) | 456 req.add_header("Content-Type", content_type) |
| 461 if extra_headers: | 457 if extra_headers: |
| 462 for header, value in extra_headers.items(): | 458 for header, value in extra_headers.items(): |
| 463 req.add_header(header, value) | 459 req.add_header(header, value) |
| 464 try: | 460 try: |
| 465 f = self.opener.open(req) | 461 f = self.opener.open(req, timeout=70) |
| 466 response = f.read() | 462 response = f.read() |
| 467 f.close() | 463 f.close() |
| 468 return response | 464 return response |
| 469 except urllib2.HTTPError, e: | 465 except urllib2.HTTPError, e: |
| 470 if tries > 3: | 466 if tries > 3: |
| 471 raise | 467 raise |
| 472 elif e.code == 401 or e.code == 302: | 468 elif e.code == 401 or e.code == 302: |
| 473 if not self.auth_function: | 469 if not self.auth_function: |
| 474 raise | 470 raise |
| 475 self._Authenticate() | 471 self._Authenticate() |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 536 # Always chmod the cookie file | 532 # Always chmod the cookie file |
| 537 os.chmod(self.cookie_file, 0600) | 533 os.chmod(self.cookie_file, 0600) |
| 538 else: | 534 else: |
| 539 # Don't save cookies across runs of update.py. | 535 # Don't save cookies across runs of update.py. |
| 540 self.cookie_jar = cookielib.CookieJar() | 536 self.cookie_jar = cookielib.CookieJar() |
| 541 opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar)) | 537 opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar)) |
| 542 return opener | 538 return opener |
| 543 | 539 |
| 544 | 540 |
| 545 class CondensedHelpFormatter(optparse.IndentedHelpFormatter): | 541 class CondensedHelpFormatter(optparse.IndentedHelpFormatter): |
| 546 """Frees more horizontal space by removing indentation from group | 542 """Frees more horizontal space by removing indentation from group |
| 547 options and collapsing arguments between short and long, e.g. | 543 options and collapsing arguments between short and long, e.g. |
| 548 '-o ARG, --opt=ARG' to -o --opt ARG""" | 544 '-o ARG, --opt=ARG' to -o --opt ARG""" |
| 549 | 545 |
| 550 def format_heading(self, heading): | 546 def format_heading(self, heading): |
| 551 return "%s:\n" % heading | 547 return "%s:\n" % heading |
| 552 | 548 |
| 553 def format_option(self, option): | 549 def format_option(self, option): |
| 554 self.dedent() | 550 self.dedent() |
| 555 res = optparse.HelpFormatter.format_option(self, option) | 551 res = optparse.HelpFormatter.format_option(self, option) |
| 556 self.indent() | 552 self.indent() |
| 557 return res | 553 return res |
| 558 | 554 |
| 559 def format_option_strings(self, option): | 555 def format_option_strings(self, option): |
| 560 self.set_long_opt_delimiter(" ") | 556 self.set_long_opt_delimiter(" ") |
| 561 optstr = optparse.HelpFormatter.format_option_strings(self, option) | 557 optstr = optparse.HelpFormatter.format_option_strings(self, option) |
| 562 optlist = optstr.split(", ") | 558 optlist = optstr.split(", ") |
| 563 if len(optlist) > 1: | 559 if len(optlist) > 1: |
| 564 if option.takes_value(): | 560 if option.takes_value(): |
| 565 # strip METAVAR from all but the last option | 561 # strip METAVAR from all but the last option |
| 566 optlist = [x.split()[0] for x in optlist[:-1]] + optlist[-1:] | 562 optlist = [x.split()[0] for x in optlist[:-1]] + optlist[-1:] |
| 567 optstr = " ".join(optlist) | 563 optstr = " ".join(optlist) |
| 568 return optstr | 564 return optstr |
| 569 | 565 |
| 570 | 566 |
| 571 parser = optparse.OptionParser( | 567 parser = optparse.OptionParser( |
| 572 usage=("%prog [options] [-- diff_options] [path...]\n" | 568 usage=("%prog [options] [-- diff_options] [path...]\n" |
| 573 "See also: http://code.google.com/p/rietveld/wiki/UploadPyUsage"), | 569 "See also: http://code.google.com/p/rietveld/wiki/UploadPyUsage"), |
| 574 add_help_option=False, | 570 add_help_option=False, |
| 575 formatter=CondensedHelpFormatter() | 571 formatter=CondensedHelpFormatter() |
| 576 ) | 572 ) |
| 577 parser.add_option("-h", "--help", action="store_true", | 573 parser.add_option("-h", "--help", action="store_true", |
| 578 help="Show this help message and exit.") | 574 help="Show this help message and exit.") |
| 579 parser.add_option("-y", "--assume_yes", action="store_true", | 575 parser.add_option("-y", "--assume_yes", action="store_true", |
| 580 dest="assume_yes", default=False, | 576 dest="assume_yes", default=False, |
| 581 help="Assume that the answer to yes/no questions is 'yes'.") | 577 help="Assume that the answer to yes/no questions is 'yes'.") |
| 582 # Logging | 578 # Logging |
| 583 group = parser.add_option_group("Logging options") | 579 group = parser.add_option_group("Logging options") |
| 584 group.add_option("-q", "--quiet", action="store_const", const=0, | 580 group.add_option("-q", "--quiet", action="store_const", const=0, |
| 585 dest="verbose", help="Print errors only.") | 581 dest="verbose", help="Print errors only.") |
| 586 group.add_option("-v", "--verbose", action="store_const", const=2, | 582 group.add_option("-v", "--verbose", action="store_const", const=2, |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 674 metavar="VCS", default=None, | 670 metavar="VCS", default=None, |
| 675 help=("Explicitly specify version control system (%s)" | 671 help=("Explicitly specify version control system (%s)" |
| 676 % ", ".join(VCS_SHORT_NAMES))) | 672 % ", ".join(VCS_SHORT_NAMES))) |
| 677 group.add_option("--emulate_svn_auto_props", action="store_true", | 673 group.add_option("--emulate_svn_auto_props", action="store_true", |
| 678 dest="emulate_svn_auto_props", default=False, | 674 dest="emulate_svn_auto_props", default=False, |
| 679 help=("Emulate Subversion's auto properties feature.")) | 675 help=("Emulate Subversion's auto properties feature.")) |
| 680 # Git-specific | 676 # Git-specific |
| 681 group = parser.add_option_group("Git-specific options") | 677 group = parser.add_option_group("Git-specific options") |
| 682 group.add_option("--git_similarity", action="store", dest="git_similarity", | 678 group.add_option("--git_similarity", action="store", dest="git_similarity", |
| 683 metavar="SIM", type="int", default=50, | 679 metavar="SIM", type="int", default=50, |
| 684 help=("Set the minimum similarity index for detecting renames " | 680 help=("Set the minimum similarity percentage for detecting " |
| 685 "and copies. See `git diff -C`. (default 50).")) | 681 "renames and copies. See `git diff -C`. (default 50).")) |
| 682 group.add_option("--git_only_search_patch", action="store_false", default=True, |
| 683 dest='git_find_copies_harder', |
| 684 help="Removes --find-copies-harder when seaching for copies") |
| 686 group.add_option("--git_no_find_copies", action="store_false", default=True, | 685 group.add_option("--git_no_find_copies", action="store_false", default=True, |
| 687 dest="git_find_copies", | 686 dest="git_find_copies", |
| 688 help=("Prevents git from looking for copies (default off).")) | 687 help=("Prevents git from looking for copies (default off).")) |
| 689 # Perforce-specific | 688 # Perforce-specific |
| 690 group = parser.add_option_group("Perforce-specific options " | 689 group = parser.add_option_group("Perforce-specific options " |
| 691 "(overrides P4 environment variables)") | 690 "(overrides P4 environment variables)") |
| 692 group.add_option("--p4_port", action="store", dest="p4_port", | 691 group.add_option("--p4_port", action="store", dest="p4_port", |
| 693 metavar="P4_PORT", default=None, | 692 metavar="P4_PORT", default=None, |
| 694 help=("Perforce server and port (optional)")) | 693 help=("Perforce server and port (optional)")) |
| 695 group.add_option("--p4_changelist", action="store", dest="p4_changelist", | 694 group.add_option("--p4_changelist", action="store", dest="p4_changelist", |
| (...skipping 307 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1003 """Helper to guess the content-type from the filename.""" | 1002 """Helper to guess the content-type from the filename.""" |
| 1004 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' | 1003 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' |
| 1005 | 1004 |
| 1006 | 1005 |
| 1007 # Use a shell for subcommands on Windows to get a PATH search. | 1006 # Use a shell for subcommands on Windows to get a PATH search. |
| 1008 use_shell = sys.platform.startswith("win") | 1007 use_shell = sys.platform.startswith("win") |
| 1009 | 1008 |
| 1010 def RunShellWithReturnCodeAndStderr(command, print_output=False, | 1009 def RunShellWithReturnCodeAndStderr(command, print_output=False, |
| 1011 universal_newlines=True, | 1010 universal_newlines=True, |
| 1012 env=os.environ): | 1011 env=os.environ): |
| 1013 """Executes a command and returns the output from stdout, stderr and the retur
n code. | 1012 """Run a command and return output from stdout, stderr and the return code. |
| 1014 | 1013 |
| 1015 Args: | 1014 Args: |
| 1016 command: Command to execute. | 1015 command: Command to execute. |
| 1017 print_output: If True, the output is printed to stdout. | 1016 print_output: If True, the output is printed to stdout. |
| 1018 If False, both stdout and stderr are ignored. | 1017 If False, both stdout and stderr are ignored. |
| 1019 universal_newlines: Use universal_newlines flag (default: True). | 1018 universal_newlines: Use universal_newlines flag (default: True). |
| 1020 | 1019 |
| 1021 Returns: | 1020 Returns: |
| 1022 Tuple (stdout, stderr, return code) | 1021 Tuple (stdout, stderr, return code) |
| 1023 """ | 1022 """ |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1034 if not line: | 1033 if not line: |
| 1035 break | 1034 break |
| 1036 print line.strip("\n") | 1035 print line.strip("\n") |
| 1037 output_array.append(line) | 1036 output_array.append(line) |
| 1038 output = "".join(output_array) | 1037 output = "".join(output_array) |
| 1039 else: | 1038 else: |
| 1040 output = p.stdout.read() | 1039 output = p.stdout.read() |
| 1041 p.wait() | 1040 p.wait() |
| 1042 errout = p.stderr.read() | 1041 errout = p.stderr.read() |
| 1043 if print_output and errout: | 1042 if print_output and errout: |
| 1044 print >>sys.stderr, errout | 1043 print >> sys.stderr, errout |
| 1045 p.stdout.close() | 1044 p.stdout.close() |
| 1046 p.stderr.close() | 1045 p.stderr.close() |
| 1047 return output, errout, p.returncode | 1046 return output, errout, p.returncode |
| 1048 | 1047 |
| 1049 def RunShellWithReturnCode(command, print_output=False, | 1048 def RunShellWithReturnCode(command, print_output=False, |
| 1050 universal_newlines=True, | 1049 universal_newlines=True, |
| 1051 env=os.environ): | 1050 env=os.environ): |
| 1052 """Executes a command and returns the output from stdout and the return code."
"" | 1051 """Run a command and return output from stdout and the return code.""" |
| 1053 out, err, retcode = RunShellWithReturnCodeAndStderr(command, print_output, | 1052 out, err, retcode = RunShellWithReturnCodeAndStderr(command, print_output, |
| 1054 universal_newlines, env) | 1053 universal_newlines, env) |
| 1055 return out, retcode | 1054 return out, retcode |
| 1056 | 1055 |
| 1057 def RunShell(command, silent_ok=False, universal_newlines=True, | 1056 def RunShell(command, silent_ok=False, universal_newlines=True, |
| 1058 print_output=False, env=os.environ): | 1057 print_output=False, env=os.environ): |
| 1059 data, retcode = RunShellWithReturnCode(command, print_output, | 1058 data, retcode = RunShellWithReturnCode(command, print_output, |
| 1060 universal_newlines, env) | 1059 universal_newlines, env) |
| 1061 if retcode: | 1060 if retcode: |
| 1062 ErrorExit("Got error status from %s:\n%s" % (command, data)) | 1061 ErrorExit("Got error status from %s:\n%s" % (command, data)) |
| (...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1217 | 1216 |
| 1218 for t in threads: | 1217 for t in threads: |
| 1219 print t.get(timeout=60) | 1218 print t.get(timeout=60) |
| 1220 | 1219 |
| 1221 | 1220 |
| 1222 def IsImage(self, filename): | 1221 def IsImage(self, filename): |
| 1223 """Returns true if the filename has an image extension.""" | 1222 """Returns true if the filename has an image extension.""" |
| 1224 mimetype = mimetypes.guess_type(filename)[0] | 1223 mimetype = mimetypes.guess_type(filename)[0] |
| 1225 if not mimetype: | 1224 if not mimetype: |
| 1226 return False | 1225 return False |
| 1227 return mimetype.startswith("image/") and not mimetype.startswith("image/svg"
) | 1226 return (mimetype.startswith("image/") and |
| 1227 not mimetype.startswith("image/svg")) |
| 1228 | 1228 |
| 1229 def IsBinaryData(self, data): | 1229 def IsBinaryData(self, data): |
| 1230 """Returns true if data contains a null byte.""" | 1230 """Returns true if data contains a null byte.""" |
| 1231 # Derived from how Mercurial's heuristic, see | 1231 # Derived from how Mercurial's heuristic, see |
| 1232 # http://selenic.com/hg/file/848a6658069e/mercurial/util.py#l229 | 1232 # http://selenic.com/hg/file/848a6658069e/mercurial/util.py#l229 |
| 1233 return bool(data and "\0" in data) | 1233 return bool(data and "\0" in data) |
| 1234 | 1234 |
| 1235 | 1235 |
| 1236 class SubversionVCS(VersionControlSystem): | 1236 class SubversionVCS(VersionControlSystem): |
| 1237 """Implementation of the VersionControlSystem interface for Subversion.""" | 1237 """Implementation of the VersionControlSystem interface for Subversion.""" |
| (...skipping 25 matching lines...) Expand all Loading... |
| 1263 | 1263 |
| 1264 def _GuessBase(self, required): | 1264 def _GuessBase(self, required): |
| 1265 """Returns base URL for current diff. | 1265 """Returns base URL for current diff. |
| 1266 | 1266 |
| 1267 Args: | 1267 Args: |
| 1268 required: If true, exits if the url can't be guessed, otherwise None is | 1268 required: If true, exits if the url can't be guessed, otherwise None is |
| 1269 returned. | 1269 returned. |
| 1270 """ | 1270 """ |
| 1271 url = self._GetInfo("URL") | 1271 url = self._GetInfo("URL") |
| 1272 if url: | 1272 if url: |
| 1273 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) | 1273 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) |
| 1274 guess = "" | 1274 guess = "" |
| 1275 # TODO(anatoli) - repository specific hacks should be handled by server | 1275 # TODO(anatoli) - repository specific hacks should be handled by server |
| 1276 if netloc == "svn.python.org" and scheme == "svn+ssh": | 1276 if netloc == "svn.python.org" and scheme == "svn+ssh": |
| 1277 path = "projects" + path | 1277 path = "projects" + path |
| 1278 scheme = "http" | 1278 scheme = "http" |
| 1279 guess = "Python " | 1279 guess = "Python " |
| 1280 elif netloc.endswith(".googlecode.com"): | 1280 elif netloc.endswith(".googlecode.com"): |
| 1281 scheme = "http" | 1281 scheme = "http" |
| 1282 guess = "Google Code " | 1282 guess = "Google Code " |
| 1283 path = path + "/" | 1283 path = path + "/" |
| 1284 base = urlparse.urlunparse((scheme, netloc, path, params, | 1284 base = urlparse.urlunparse((scheme, netloc, path, params, |
| 1285 query, fragment)) | 1285 query, fragment)) |
| 1286 LOGGER.info("Guessed %sbase = %s", guess, base) | 1286 LOGGER.info("Guessed %sbase = %s", guess, base) |
| 1287 return base | 1287 return base |
| 1288 if required: | 1288 if required: |
| 1289 ErrorExit("Can't find URL in output from svn info") | 1289 ErrorExit("Can't find URL in output from svn info") |
| 1290 return None | 1290 return None |
| 1291 | 1291 |
| 1292 def _GetInfo(self, key): | 1292 def _GetInfo(self, key): |
| 1293 """Parses 'svn info' for current dir. Returns value for key or None""" | 1293 """Parses 'svn info' for current dir. Returns value for key or None""" |
| 1294 for line in RunShell(["svn", "info"]).splitlines(): | 1294 for line in RunShell(["svn", "info"]).splitlines(): |
| 1295 if line.startswith(key + ": "): | 1295 if line.startswith(key + ": "): |
| 1296 return line.split(":", 1)[1].strip() | 1296 return line.split(":", 1)[1].strip() |
| 1297 | 1297 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1332 'Id': ['Id'], | 1332 'Id': ['Id'], |
| 1333 | 1333 |
| 1334 # Aliases | 1334 # Aliases |
| 1335 'LastChangedDate': ['LastChangedDate', 'Date'], | 1335 'LastChangedDate': ['LastChangedDate', 'Date'], |
| 1336 'LastChangedRevision': ['LastChangedRevision', 'Rev', 'Revision'], | 1336 'LastChangedRevision': ['LastChangedRevision', 'Rev', 'Revision'], |
| 1337 'LastChangedBy': ['LastChangedBy', 'Author'], | 1337 'LastChangedBy': ['LastChangedBy', 'Author'], |
| 1338 'URL': ['URL', 'HeadURL'], | 1338 'URL': ['URL', 'HeadURL'], |
| 1339 } | 1339 } |
| 1340 | 1340 |
| 1341 def repl(m): | 1341 def repl(m): |
| 1342 if m.group(2): | 1342 if m.group(2): |
| 1343 return "$%s::%s$" % (m.group(1), " " * len(m.group(3))) | 1343 return "$%s::%s$" % (m.group(1), " " * len(m.group(3))) |
| 1344 return "$%s$" % m.group(1) | 1344 return "$%s$" % m.group(1) |
| 1345 |
| 1345 keywords = [keyword | 1346 keywords = [keyword |
| 1346 for name in keyword_str.split(" ") | 1347 for name in keyword_str.split(" ") |
| 1347 for keyword in svn_keywords.get(name, [])] | 1348 for keyword in svn_keywords.get(name, [])] |
| 1348 return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content) | 1349 return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content) |
| 1349 | 1350 |
| 1350 def GetUnknownFiles(self): | 1351 def GetUnknownFiles(self): |
| 1351 status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True) | 1352 status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True) |
| 1352 unknown_files = [] | 1353 unknown_files = [] |
| 1353 for line in status.split("\n"): | 1354 for line in status.split("\n"): |
| 1354 if line and line[0] == "?": | 1355 if line and line[0] == "?": |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1387 # the correct status for a file. | 1388 # the correct status for a file. |
| 1388 else: | 1389 else: |
| 1389 dirname, relfilename = os.path.split(filename) | 1390 dirname, relfilename = os.path.split(filename) |
| 1390 if dirname not in self.svnls_cache: | 1391 if dirname not in self.svnls_cache: |
| 1391 cmd = ["svn", "list", "-r", self.rev_start, | 1392 cmd = ["svn", "list", "-r", self.rev_start, |
| 1392 self._EscapeFilename(dirname) or "."] | 1393 self._EscapeFilename(dirname) or "."] |
| 1393 out, err, returncode = RunShellWithReturnCodeAndStderr(cmd) | 1394 out, err, returncode = RunShellWithReturnCodeAndStderr(cmd) |
| 1394 if returncode: | 1395 if returncode: |
| 1395 # Directory might not yet exist at start revison | 1396 # Directory might not yet exist at start revison |
| 1396 # svn: Unable to find repository location for 'abc' in revision nnn | 1397 # svn: Unable to find repository location for 'abc' in revision nnn |
| 1397 if re.match('^svn: Unable to find repository location for .+ in revisi
on \d+', err): | 1398 if re.match('^svn: Unable to find repository location ' |
| 1399 'for .+ in revision \d+', err): |
| 1398 old_files = () | 1400 old_files = () |
| 1399 else: | 1401 else: |
| 1400 ErrorExit("Failed to get status for %s:\n%s" % (filename, err)) | 1402 ErrorExit("Failed to get status for %s:\n%s" % (filename, err)) |
| 1401 else: | 1403 else: |
| 1402 old_files = out.splitlines() | 1404 old_files = out.splitlines() |
| 1403 args = ["svn", "list"] | 1405 args = ["svn", "list"] |
| 1404 if self.rev_end: | 1406 if self.rev_end: |
| 1405 args += ["-r", self.rev_end] | 1407 args += ["-r", self.rev_end] |
| 1406 cmd = args + [self._EscapeFilename(dirname) or "."] | 1408 cmd = args + [self._EscapeFilename(dirname) or "."] |
| 1407 out, returncode = RunShellWithReturnCode(cmd) | 1409 out, returncode = RunShellWithReturnCode(cmd) |
| (...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1609 # git config key "diff.external" is used). | 1611 # git config key "diff.external" is used). |
| 1610 env = os.environ.copy() | 1612 env = os.environ.copy() |
| 1611 if "GIT_EXTERNAL_DIFF" in env: | 1613 if "GIT_EXTERNAL_DIFF" in env: |
| 1612 del env["GIT_EXTERNAL_DIFF"] | 1614 del env["GIT_EXTERNAL_DIFF"] |
| 1613 # -M/-C will not print the diff for the deleted file when a file is renamed. | 1615 # -M/-C will not print the diff for the deleted file when a file is renamed. |
| 1614 # This is confusing because the original file will not be shown on the | 1616 # This is confusing because the original file will not be shown on the |
| 1615 # review when a file is renamed. So, get a diff with ONLY deletes, then | 1617 # review when a file is renamed. So, get a diff with ONLY deletes, then |
| 1616 # append a diff (with rename detection), without deletes. | 1618 # append a diff (with rename detection), without deletes. |
| 1617 cmd = [ | 1619 cmd = [ |
| 1618 "git", "diff", "--no-color", "--no-ext-diff", "--full-index", | 1620 "git", "diff", "--no-color", "--no-ext-diff", "--full-index", |
| 1619 "--ignore-submodules", | 1621 "--ignore-submodules", "--src-prefix=a/", "--dst-prefix=b/", |
| 1620 ] | 1622 ] |
| 1621 diff = RunShell( | 1623 diff = RunShell( |
| 1622 cmd + ["--no-renames", "--diff-filter=D"] + extra_args, | 1624 cmd + ["--no-renames", "--diff-filter=D"] + extra_args, |
| 1623 env=env, silent_ok=True) | 1625 env=env, silent_ok=True) |
| 1626 assert 0 <= self.options.git_similarity <= 100 |
| 1624 if self.options.git_find_copies: | 1627 if self.options.git_find_copies: |
| 1625 similarity_options = ["--find-copies-harder", "-l100000", | 1628 similarity_options = ["-l100000", "-C%d%%" % self.options.git_similarity] |
| 1626 "-C%s" % self.options.git_similarity ] | 1629 if self.options.git_find_copies_harder: |
| 1630 similarity_options.append("--find-copies-harder") |
| 1627 else: | 1631 else: |
| 1628 similarity_options = ["-M%s" % self.options.git_similarity ] | 1632 similarity_options = ["-M%d%%" % self.options.git_similarity ] |
| 1629 diff += RunShell( | 1633 diff += RunShell( |
| 1630 cmd + ["--diff-filter=AMCRT"] + similarity_options + extra_args, | 1634 cmd + ["--diff-filter=AMCRT"] + similarity_options + extra_args, |
| 1631 env=env, silent_ok=True) | 1635 env=env, silent_ok=True) |
| 1632 | 1636 |
| 1633 # The CL could be only file deletion or not. So accept silent diff for both | 1637 # The CL could be only file deletion or not. So accept silent diff for both |
| 1634 # commands then check for an empty diff manually. | 1638 # commands then check for an empty diff manually. |
| 1635 if not diff: | 1639 if not diff: |
| 1636 ErrorExit("No output from %s" % (cmd + extra_args)) | 1640 ErrorExit("No output from %s" % (cmd + extra_args)) |
| 1637 return diff | 1641 return diff |
| 1638 | 1642 |
| (...skipping 440 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2079 diffData.base_rev = fstat["headRev"] # Re-adding a deleted file | 2083 diffData.base_rev = fstat["headRev"] # Re-adding a deleted file |
| 2080 else: | 2084 else: |
| 2081 diffData.base_rev = "0" # Brand new file | 2085 diffData.base_rev = "0" # Brand new file |
| 2082 diffData.working_copy = False | 2086 diffData.working_copy = False |
| 2083 rel_path = self.GetLocalFilename(diffData.filename) | 2087 rel_path = self.GetLocalFilename(diffData.filename) |
| 2084 diffData.file_body = open(rel_path, 'r').read() | 2088 diffData.file_body = open(rel_path, 'r').read() |
| 2085 # Replicate svn's list of changed lines | 2089 # Replicate svn's list of changed lines |
| 2086 line_count = len(diffData.file_body.splitlines()) | 2090 line_count = len(diffData.file_body.splitlines()) |
| 2087 diffData.change_summary = "@@ -0,0 +1" | 2091 diffData.change_summary = "@@ -0,0 +1" |
| 2088 if line_count > 1: | 2092 if line_count > 1: |
| 2089 diffData.change_summary += ",%d" % line_count | 2093 diffData.change_summary += ",%d" % line_count |
| 2090 diffData.change_summary += " @@" | 2094 diffData.change_summary += " @@" |
| 2091 diffData.prefix = "+" | 2095 diffData.prefix = "+" |
| 2092 return diffData | 2096 return diffData |
| 2093 | 2097 |
| 2094 def GenerateDeleteDiff(diffData): | 2098 def GenerateDeleteDiff(diffData): |
| 2095 diffData.base_rev = self.GetBaseRevision(diffData.filename) | 2099 diffData.base_rev = self.GetBaseRevision(diffData.filename) |
| 2096 is_base_binary = self.IsBaseBinary(diffData.filename) | 2100 is_base_binary = self.IsBaseBinary(diffData.filename) |
| 2097 # For deletes, base_filename == filename | 2101 # For deletes, base_filename == filename |
| 2098 diffData.file_body = self.GetFileContent(diffData.base_filename, | 2102 diffData.file_body = self.GetFileContent(diffData.base_filename, |
| 2099 None, | 2103 None, |
| (...skipping 606 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2706 os.environ['LC_ALL'] = 'C' | 2710 os.environ['LC_ALL'] = 'C' |
| 2707 RealMain(sys.argv) | 2711 RealMain(sys.argv) |
| 2708 except KeyboardInterrupt: | 2712 except KeyboardInterrupt: |
| 2709 print | 2713 print |
| 2710 StatusUpdate("Interrupted.") | 2714 StatusUpdate("Interrupted.") |
| 2711 sys.exit(1) | 2715 sys.exit(1) |
| 2712 | 2716 |
| 2713 | 2717 |
| 2714 if __name__ == "__main__": | 2718 if __name__ == "__main__": |
| 2715 main() | 2719 main() |
| OLD | NEW |