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 |