| 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 # |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 51 try: | 51 try: |
| 52 from hashlib import md5 | 52 from hashlib import md5 |
| 53 except ImportError: | 53 except ImportError: |
| 54 from md5 import md5 | 54 from md5 import md5 |
| 55 | 55 |
| 56 try: | 56 try: |
| 57 import readline | 57 import readline |
| 58 except ImportError: | 58 except ImportError: |
| 59 pass | 59 pass |
| 60 | 60 |
| 61 try: |
| 62 import keyring |
| 63 except ImportError: |
| 64 keyring = None |
| 65 |
| 61 # The logging verbosity: | 66 # The logging verbosity: |
| 62 # 0: Errors only. | 67 # 0: Errors only. |
| 63 # 1: Status messages. | 68 # 1: Status messages. |
| 64 # 2: Info logs. | 69 # 2: Info logs. |
| 65 # 3: Debug logs. | 70 # 3: Debug logs. |
| 66 verbosity = 1 | 71 verbosity = 1 |
| 67 | 72 |
| 73 # The account type used for authentication. |
| 74 # This line could be changed by the review server (see handler for |
| 75 # upload.py). |
| 76 AUTH_ACCOUNT_TYPE = "GOOGLE" |
| 77 |
| 78 # URL of the default review server. As for AUTH_ACCOUNT_TYPE, this line could be |
| 79 # changed by the review server (see handler for upload.py). |
| 80 DEFAULT_REVIEW_SERVER = "codereview.appspot.com" |
| 81 |
| 68 # Max size of patch or base file. | 82 # Max size of patch or base file. |
| 69 MAX_UPLOAD_SIZE = 900 * 1024 | 83 MAX_UPLOAD_SIZE = 900 * 1024 |
| 70 | 84 |
| 71 # Constants for version control names. Used by GuessVCSName. | 85 # Constants for version control names. Used by GuessVCSName. |
| 72 VCS_GIT = "Git" | 86 VCS_GIT = "Git" |
| 73 VCS_MERCURIAL = "Mercurial" | 87 VCS_MERCURIAL = "Mercurial" |
| 74 VCS_SUBVERSION = "Subversion" | 88 VCS_SUBVERSION = "Subversion" |
| 75 VCS_UNKNOWN = "Unknown" | 89 VCS_UNKNOWN = "Unknown" |
| 76 | 90 |
| 77 # whitelist for non-binary filetypes which do not start with "text/" | 91 # whitelist for non-binary filetypes which do not start with "text/" |
| 78 # .mm (Objective-C) shows up as application/x-freemind on my Linux box. | 92 # .mm (Objective-C) shows up as application/x-freemind on my Linux box. |
| 79 TEXT_MIMETYPES = ['application/javascript', 'application/x-javascript', | 93 TEXT_MIMETYPES = ['application/javascript', 'application/x-javascript', |
| 80 'application/xml', 'application/x-freemind'] | 94 'application/xml', 'application/x-freemind', |
| 95 'application/x-sh'] |
| 81 | 96 |
| 82 VCS_ABBREVIATIONS = { | 97 VCS_ABBREVIATIONS = { |
| 83 VCS_MERCURIAL.lower(): VCS_MERCURIAL, | 98 VCS_MERCURIAL.lower(): VCS_MERCURIAL, |
| 84 "hg": VCS_MERCURIAL, | 99 "hg": VCS_MERCURIAL, |
| 85 VCS_SUBVERSION.lower(): VCS_SUBVERSION, | 100 VCS_SUBVERSION.lower(): VCS_SUBVERSION, |
| 86 "svn": VCS_SUBVERSION, | 101 "svn": VCS_SUBVERSION, |
| 87 VCS_GIT.lower(): VCS_GIT, | 102 VCS_GIT.lower(): VCS_GIT, |
| 88 } | 103 } |
| 89 | 104 |
| 90 # The result of parsing Subversion's [auto-props] setting. | 105 # The result of parsing Subversion's [auto-props] setting. |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 146 def __init__(self, url, code, msg, headers, args): | 161 def __init__(self, url, code, msg, headers, args): |
| 147 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) | 162 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) |
| 148 self.args = args | 163 self.args = args |
| 149 self.reason = args["Error"] | 164 self.reason = args["Error"] |
| 150 | 165 |
| 151 | 166 |
| 152 class AbstractRpcServer(object): | 167 class AbstractRpcServer(object): |
| 153 """Provides a common interface for a simple RPC server.""" | 168 """Provides a common interface for a simple RPC server.""" |
| 154 | 169 |
| 155 def __init__(self, host, auth_function, host_override=None, extra_headers={}, | 170 def __init__(self, host, auth_function, host_override=None, extra_headers={}, |
| 156 save_cookies=False): | 171 save_cookies=False, account_type=AUTH_ACCOUNT_TYPE): |
| 157 """Creates a new HttpRpcServer. | 172 """Creates a new HttpRpcServer. |
| 158 | 173 |
| 159 Args: | 174 Args: |
| 160 host: The host to send requests to. | 175 host: The host to send requests to. |
| 161 auth_function: A function that takes no arguments and returns an | 176 auth_function: A function that takes no arguments and returns an |
| 162 (email, password) tuple when called. Will be called if authentication | 177 (email, password) tuple when called. Will be called if authentication |
| 163 is required. | 178 is required. |
| 164 host_override: The host header to send to the server (defaults to host). | 179 host_override: The host header to send to the server (defaults to host). |
| 165 extra_headers: A dict of extra headers to append to every request. | 180 extra_headers: A dict of extra headers to append to every request. |
| 166 save_cookies: If True, save the authentication cookies to local disk. | 181 save_cookies: If True, save the authentication cookies to local disk. |
| 167 If False, use an in-memory cookiejar instead. Subclasses must | 182 If False, use an in-memory cookiejar instead. Subclasses must |
| 168 implement this functionality. Defaults to False. | 183 implement this functionality. Defaults to False. |
| 184 account_type: Account type used for authentication. Defaults to |
| 185 AUTH_ACCOUNT_TYPE. |
| 169 """ | 186 """ |
| 170 self.host = host | 187 self.host = host |
| 171 if (not self.host.startswith("http://") and | 188 if (not self.host.startswith("http://") and |
| 172 not self.host.startswith("https://")): | 189 not self.host.startswith("https://")): |
| 173 self.host = "http://" + self.host | 190 self.host = "http://" + self.host |
| 174 self.host_override = host_override | 191 self.host_override = host_override |
| 175 self.auth_function = auth_function | 192 self.auth_function = auth_function |
| 176 self.authenticated = False | 193 self.authenticated = False |
| 177 self.extra_headers = extra_headers | 194 self.extra_headers = extra_headers |
| 178 self.save_cookies = save_cookies | 195 self.save_cookies = save_cookies |
| 196 self.account_type = account_type |
| 179 self.opener = self._GetOpener() | 197 self.opener = self._GetOpener() |
| 180 if self.host_override: | 198 if self.host_override: |
| 181 logging.info("Server: %s; Host: %s", self.host, self.host_override) | 199 logging.info("Server: %s; Host: %s", self.host, self.host_override) |
| 182 else: | 200 else: |
| 183 logging.info("Server: %s", self.host) | 201 logging.info("Server: %s", self.host) |
| 184 | 202 |
| 185 def _GetOpener(self): | 203 def _GetOpener(self): |
| 186 """Returns an OpenerDirector for making HTTP requests. | 204 """Returns an OpenerDirector for making HTTP requests. |
| 187 | 205 |
| 188 Returns: | 206 Returns: |
| (...skipping 18 matching lines...) Expand all Loading... |
| 207 email: The user's email address | 225 email: The user's email address |
| 208 password: The user's password | 226 password: The user's password |
| 209 | 227 |
| 210 Raises: | 228 Raises: |
| 211 ClientLoginError: If there was an error authenticating with ClientLogin. | 229 ClientLoginError: If there was an error authenticating with ClientLogin. |
| 212 HTTPError: If there was some other form of HTTP error. | 230 HTTPError: If there was some other form of HTTP error. |
| 213 | 231 |
| 214 Returns: | 232 Returns: |
| 215 The authentication token returned by ClientLogin. | 233 The authentication token returned by ClientLogin. |
| 216 """ | 234 """ |
| 217 account_type = "GOOGLE" | 235 account_type = self.account_type |
| 218 if self.host.endswith(".google.com"): | 236 if self.host.endswith(".google.com"): |
| 219 # Needed for use inside Google. | 237 # Needed for use inside Google. |
| 220 account_type = "HOSTED" | 238 account_type = "HOSTED" |
| 221 req = self._CreateRequest( | 239 req = self._CreateRequest( |
| 222 url="https://www.google.com/accounts/ClientLogin", | 240 url="https://www.google.com/accounts/ClientLogin", |
| 223 data=urllib.urlencode({ | 241 data=urllib.urlencode({ |
| 224 "Email": email, | 242 "Email": email, |
| 225 "Passwd": password, | 243 "Passwd": password, |
| 226 "service": "ah", | 244 "service": "ah", |
| 227 "source": "rietveld-codereview-upload", | 245 "source": "rietveld-codereview-upload", |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 287 try: | 305 try: |
| 288 auth_token = self._GetAuthToken(credentials[0], credentials[1]) | 306 auth_token = self._GetAuthToken(credentials[0], credentials[1]) |
| 289 except ClientLoginError, e: | 307 except ClientLoginError, e: |
| 290 if e.reason == "BadAuthentication": | 308 if e.reason == "BadAuthentication": |
| 291 print >>sys.stderr, "Invalid username or password." | 309 print >>sys.stderr, "Invalid username or password." |
| 292 continue | 310 continue |
| 293 if e.reason == "CaptchaRequired": | 311 if e.reason == "CaptchaRequired": |
| 294 print >>sys.stderr, ( | 312 print >>sys.stderr, ( |
| 295 "Please go to\n" | 313 "Please go to\n" |
| 296 "https://www.google.com/accounts/DisplayUnlockCaptcha\n" | 314 "https://www.google.com/accounts/DisplayUnlockCaptcha\n" |
| 297 "and verify you are a human. Then try again.") | 315 "and verify you are a human. Then try again.\n" |
| 316 "If you are using a Google Apps account the URL is:\n" |
| 317 "https://www.google.com/a/yourdomain.com/UnlockCaptcha") |
| 298 break | 318 break |
| 299 if e.reason == "NotVerified": | 319 if e.reason == "NotVerified": |
| 300 print >>sys.stderr, "Account not verified." | 320 print >>sys.stderr, "Account not verified." |
| 301 break | 321 break |
| 302 if e.reason == "TermsNotAgreed": | 322 if e.reason == "TermsNotAgreed": |
| 303 print >>sys.stderr, "User has not agreed to TOS." | 323 print >>sys.stderr, "User has not agreed to TOS." |
| 304 break | 324 break |
| 305 if e.reason == "AccountDeleted": | 325 if e.reason == "AccountDeleted": |
| 306 print >>sys.stderr, "The user account has been deleted." | 326 print >>sys.stderr, "The user account has been deleted." |
| 307 break | 327 break |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 436 group.add_option("-q", "--quiet", action="store_const", const=0, | 456 group.add_option("-q", "--quiet", action="store_const", const=0, |
| 437 dest="verbose", help="Print errors only.") | 457 dest="verbose", help="Print errors only.") |
| 438 group.add_option("-v", "--verbose", action="store_const", const=2, | 458 group.add_option("-v", "--verbose", action="store_const", const=2, |
| 439 dest="verbose", default=1, | 459 dest="verbose", default=1, |
| 440 help="Print info level logs (default).") | 460 help="Print info level logs (default).") |
| 441 group.add_option("--noisy", action="store_const", const=3, | 461 group.add_option("--noisy", action="store_const", const=3, |
| 442 dest="verbose", help="Print all logs.") | 462 dest="verbose", help="Print all logs.") |
| 443 # Review server | 463 # Review server |
| 444 group = parser.add_option_group("Review server options") | 464 group = parser.add_option_group("Review server options") |
| 445 group.add_option("-s", "--server", action="store", dest="server", | 465 group.add_option("-s", "--server", action="store", dest="server", |
| 446 default="codereview.appspot.com", | 466 default=DEFAULT_REVIEW_SERVER, |
| 447 metavar="SERVER", | 467 metavar="SERVER", |
| 448 help=("The server to upload to. The format is host[:port]. " | 468 help=("The server to upload to. The format is host[:port]. " |
| 449 "Defaults to '%default'.")) | 469 "Defaults to '%default'.")) |
| 450 group.add_option("-e", "--email", action="store", dest="email", | 470 group.add_option("-e", "--email", action="store", dest="email", |
| 451 metavar="EMAIL", default=None, | 471 metavar="EMAIL", default=None, |
| 452 help="The username to use. Will prompt if omitted.") | 472 help="The username to use. Will prompt if omitted.") |
| 453 group.add_option("-H", "--host", action="store", dest="host", | 473 group.add_option("-H", "--host", action="store", dest="host", |
| 454 metavar="HOST", default=None, | 474 metavar="HOST", default=None, |
| 455 help="Overrides the Host header sent with all RPCs.") | 475 help="Overrides the Host header sent with all RPCs.") |
| 456 group.add_option("--no_cookies", action="store_false", | 476 group.add_option("--no_cookies", action="store_false", |
| 457 dest="save_cookies", default=True, | 477 dest="save_cookies", default=True, |
| 458 help="Do not save authentication cookies to local disk.") | 478 help="Do not save authentication cookies to local disk.") |
| 479 group.add_option("--account_type", action="store", dest="account_type", |
| 480 metavar="TYPE", default=AUTH_ACCOUNT_TYPE, |
| 481 choices=["GOOGLE", "HOSTED"], |
| 482 help=("Override the default account type " |
| 483 "(defaults to '%default', " |
| 484 "valid choices are 'GOOGLE' and 'HOSTED').")) |
| 459 # Issue | 485 # Issue |
| 460 group = parser.add_option_group("Issue options") | 486 group = parser.add_option_group("Issue options") |
| 461 group.add_option("-d", "--description", action="store", dest="description", | 487 group.add_option("-d", "--description", action="store", dest="description", |
| 462 metavar="DESCRIPTION", default=None, | 488 metavar="DESCRIPTION", default=None, |
| 463 help="Optional description when creating an issue.") | 489 help="Optional description when creating an issue.") |
| 464 group.add_option("-f", "--description_file", action="store", | 490 group.add_option("-f", "--description_file", action="store", |
| 465 dest="description_file", metavar="DESCRIPTION_FILE", | 491 dest="description_file", metavar="DESCRIPTION_FILE", |
| 466 default=None, | 492 default=None, |
| 467 help="Optional path of a file that contains " | 493 help="Optional path of a file that contains " |
| 468 "the description when creating an issue.") | 494 "the description when creating an issue.") |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 501 help="Send notification email to reviewers.") | 527 help="Send notification email to reviewers.") |
| 502 group.add_option("--vcs", action="store", dest="vcs", | 528 group.add_option("--vcs", action="store", dest="vcs", |
| 503 metavar="VCS", default=None, | 529 metavar="VCS", default=None, |
| 504 help=("Version control system (optional, usually upload.py " | 530 help=("Version control system (optional, usually upload.py " |
| 505 "already guesses the right VCS).")) | 531 "already guesses the right VCS).")) |
| 506 group.add_option("--emulate_svn_auto_props", action="store_true", | 532 group.add_option("--emulate_svn_auto_props", action="store_true", |
| 507 dest="emulate_svn_auto_props", default=False, | 533 dest="emulate_svn_auto_props", default=False, |
| 508 help=("Emulate Subversion's auto properties feature.")) | 534 help=("Emulate Subversion's auto properties feature.")) |
| 509 | 535 |
| 510 | 536 |
| 511 def GetRpcServer(server, email=None, host_override=None, save_cookies=True): | 537 def GetRpcServer(server, email=None, host_override=None, save_cookies=True, |
| 538 account_type=AUTH_ACCOUNT_TYPE): |
| 512 """Returns an instance of an AbstractRpcServer. | 539 """Returns an instance of an AbstractRpcServer. |
| 513 | 540 |
| 514 Args: | 541 Args: |
| 515 server: String containing the review server URL. | 542 server: String containing the review server URL. |
| 516 email: String containing user's email address. | 543 email: String containing user's email address. |
| 517 host_override: If not None, string containing an alternate hostname to use | 544 host_override: If not None, string containing an alternate hostname to use |
| 518 in the host header. | 545 in the host header. |
| 519 save_cookies: Whether authentication cookies should be saved to disk. | 546 save_cookies: Whether authentication cookies should be saved to disk. |
| 547 account_type: Account type for authentication, either 'GOOGLE' |
| 548 or 'HOSTED'. Defaults to AUTH_ACCOUNT_TYPE. |
| 520 | 549 |
| 521 Returns: | 550 Returns: |
| 522 A new AbstractRpcServer, on which RPC calls can be made. | 551 A new AbstractRpcServer, on which RPC calls can be made. |
| 523 """ | 552 """ |
| 524 | 553 |
| 525 rpc_server_class = HttpRpcServer | 554 rpc_server_class = HttpRpcServer |
| 526 | 555 |
| 527 # If this is the dev_appserver, use fake authentication. | 556 # If this is the dev_appserver, use fake authentication. |
| 528 host = (host_override or server).lower() | 557 host = (host_override or server).lower() |
| 529 if host == "localhost" or host.startswith("localhost:"): | 558 if host == "localhost" or host.startswith("localhost:"): |
| 530 if email is None: | 559 if email is None: |
| 531 email = "test@example.com" | 560 email = "test@example.com" |
| 532 logging.info("Using debug user %s. Override with --email" % email) | 561 logging.info("Using debug user %s. Override with --email" % email) |
| 533 server = rpc_server_class( | 562 server = rpc_server_class( |
| 534 server, | 563 server, |
| 535 lambda: (email, "password"), | 564 lambda: (email, "password"), |
| 536 host_override=host_override, | 565 host_override=host_override, |
| 537 extra_headers={"Cookie": | 566 extra_headers={"Cookie": |
| 538 'dev_appserver_login="%s:False"' % email}, | 567 'dev_appserver_login="%s:False"' % email}, |
| 539 save_cookies=save_cookies) | 568 save_cookies=save_cookies, |
| 569 account_type=account_type) |
| 540 # Don't try to talk to ClientLogin. | 570 # Don't try to talk to ClientLogin. |
| 541 server.authenticated = True | 571 server.authenticated = True |
| 542 return server | 572 return server |
| 543 | 573 |
| 544 def GetUserCredentials(): | 574 def GetUserCredentials(): |
| 545 """Prompts the user for a username and password.""" | 575 """Prompts the user for a username and password.""" |
| 546 # Create a local alias to the email variable to avoid Python's crazy | 576 # Create a local alias to the email variable to avoid Python's crazy |
| 547 # scoping rules. | 577 # scoping rules. |
| 548 local_email = email | 578 local_email = email |
| 549 if local_email is None: | 579 if local_email is None: |
| 550 local_email = GetEmail("Email (login for uploading to %s)" % server) | 580 local_email = GetEmail("Email (login for uploading to %s)" % server) |
| 551 password = getpass.getpass("Password for %s: " % local_email) | 581 password = None |
| 582 if keyring: |
| 583 password = keyring.get_password(host, local_email) |
| 584 if password is not None: |
| 585 print "Using password from system keyring." |
| 586 else: |
| 587 password = getpass.getpass("Password for %s: " % local_email) |
| 588 if keyring: |
| 589 answer = raw_input("Store password in system keyring?(y/N) ").strip() |
| 590 if answer == "y": |
| 591 keyring.set_password(host, local_email, password) |
| 552 return (local_email, password) | 592 return (local_email, password) |
| 553 | 593 |
| 554 return rpc_server_class(server, | 594 return rpc_server_class(server, |
| 555 GetUserCredentials, | 595 GetUserCredentials, |
| 556 host_override=host_override, | 596 host_override=host_override, |
| 557 save_cookies=save_cookies) | 597 save_cookies=save_cookies) |
| 558 | 598 |
| 559 | 599 |
| 560 def EncodeMultipartFormData(fields, files): | 600 def EncodeMultipartFormData(fields, files): |
| 561 """Encode form fields for multipart/form-data. | 601 """Encode form fields for multipart/form-data. |
| 562 | 602 |
| 563 Args: | 603 Args: |
| 564 fields: A sequence of (name, value) elements for regular form fields. | 604 fields: A sequence of (name, value) elements for regular form fields. |
| 565 files: A sequence of (name, filename, value) elements for data to be | 605 files: A sequence of (name, filename, value) elements for data to be |
| 566 uploaded as files. | 606 uploaded as files. |
| 567 Returns: | 607 Returns: |
| 568 (content_type, body) ready for httplib.HTTP instance. | 608 (content_type, body) ready for httplib.HTTP instance. |
| 569 | 609 |
| 570 Source: | 610 Source: |
| 571 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 | 611 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 |
| 572 """ | 612 """ |
| 573 BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-' | 613 BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-' |
| 574 CRLF = '\r\n' | 614 CRLF = '\r\n' |
| 575 lines = [] | 615 lines = [] |
| 576 for (key, value) in fields: | 616 for (key, value) in fields: |
| 577 lines.append('--' + BOUNDARY) | 617 lines.append('--' + BOUNDARY) |
| 578 lines.append('Content-Disposition: form-data; name="%s"' % key) | 618 lines.append('Content-Disposition: form-data; name="%s"' % key) |
| 579 lines.append('') | 619 lines.append('') |
| 620 if isinstance(value, unicode): |
| 621 value = value.encode('utf-8') |
| 580 lines.append(value) | 622 lines.append(value) |
| 581 for (key, filename, value) in files: | 623 for (key, filename, value) in files: |
| 582 lines.append('--' + BOUNDARY) | 624 lines.append('--' + BOUNDARY) |
| 583 lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % | 625 lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % |
| 584 (key, filename)) | 626 (key, filename)) |
| 585 lines.append('Content-Type: %s' % GetContentType(filename)) | 627 lines.append('Content-Type: %s' % GetContentType(filename)) |
| 586 lines.append('') | 628 lines.append('') |
| 629 if isinstance(value, unicode): |
| 630 value = value.encode('utf-8') |
| 587 lines.append(value) | 631 lines.append(value) |
| 588 lines.append('--' + BOUNDARY + '--') | 632 lines.append('--' + BOUNDARY + '--') |
| 589 lines.append('') | 633 lines.append('') |
| 590 body = CRLF.join(lines) | 634 body = CRLF.join(lines) |
| 591 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY | 635 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY |
| 592 return content_type, body | 636 return content_type, body |
| 593 | 637 |
| 594 | 638 |
| 595 def GetContentType(filename): | 639 def GetContentType(filename): |
| 596 """Helper to guess the content-type from the filename.""" | 640 """Helper to guess the content-type from the filename.""" |
| (...skipping 437 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1034 else: | 1078 else: |
| 1035 universal_newlines = True | 1079 universal_newlines = True |
| 1036 if self.rev_start: | 1080 if self.rev_start: |
| 1037 # "svn cat -r REV delete_file.txt" doesn't work. cat requires | 1081 # "svn cat -r REV delete_file.txt" doesn't work. cat requires |
| 1038 # the full URL with "@REV" appended instead of using "-r" option. | 1082 # the full URL with "@REV" appended instead of using "-r" option. |
| 1039 url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) | 1083 url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) |
| 1040 base_content = RunShell(["svn", "cat", url], | 1084 base_content = RunShell(["svn", "cat", url], |
| 1041 universal_newlines=universal_newlines, | 1085 universal_newlines=universal_newlines, |
| 1042 silent_ok=True) | 1086 silent_ok=True) |
| 1043 else: | 1087 else: |
| 1044 base_content = RunShell(["svn", "cat", filename], | 1088 base_content, ret_code = RunShellWithReturnCode( |
| 1045 universal_newlines=universal_newlines, | 1089 ["svn", "cat", filename], universal_newlines=universal_newlines) |
| 1046 silent_ok=True) | 1090 if ret_code and status[0] == "R": |
| 1091 # It's a replaced file without local history (see issue208). |
| 1092 # The base file needs to be fetched from the server. |
| 1093 url = "%s/%s" % (self.svn_base, filename) |
| 1094 base_content = RunShell(["svn", "cat", url], |
| 1095 universal_newlines=universal_newlines, |
| 1096 silent_ok=True) |
| 1097 elif ret_code: |
| 1098 ErrorExit("Got error status from 'svn cat %s'", filename) |
| 1047 if not is_binary: | 1099 if not is_binary: |
| 1048 args = [] | 1100 args = [] |
| 1049 if self.rev_start: | 1101 if self.rev_start: |
| 1050 url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) | 1102 url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) |
| 1051 else: | 1103 else: |
| 1052 url = filename | 1104 url = filename |
| 1053 args += ["-r", "BASE"] | 1105 args += ["-r", "BASE"] |
| 1054 cmd = ["svn"] + args + ["propget", "svn:keywords", url] | 1106 cmd = ["svn"] + args + ["propget", "svn:keywords", url] |
| 1055 keywords, returncode = RunShellWithReturnCode(cmd) | 1107 keywords, returncode = RunShellWithReturnCode(cmd) |
| 1056 if keywords and not returncode: | 1108 if keywords and not returncode: |
| (...skipping 561 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1618 if options.issue: | 1670 if options.issue: |
| 1619 prompt = "Message describing this patch set: " | 1671 prompt = "Message describing this patch set: " |
| 1620 else: | 1672 else: |
| 1621 prompt = "New issue subject: " | 1673 prompt = "New issue subject: " |
| 1622 message = options.message or raw_input(prompt).strip() | 1674 message = options.message or raw_input(prompt).strip() |
| 1623 if not message: | 1675 if not message: |
| 1624 ErrorExit("A non-empty message is required") | 1676 ErrorExit("A non-empty message is required") |
| 1625 rpc_server = GetRpcServer(options.server, | 1677 rpc_server = GetRpcServer(options.server, |
| 1626 options.email, | 1678 options.email, |
| 1627 options.host, | 1679 options.host, |
| 1628 options.save_cookies) | 1680 options.save_cookies, |
| 1681 options.account_type) |
| 1629 form_fields = [("subject", message)] | 1682 form_fields = [("subject", message)] |
| 1630 if base: | 1683 if base: |
| 1631 form_fields.append(("base", base)) | 1684 form_fields.append(("base", base)) |
| 1632 if options.issue: | 1685 if options.issue: |
| 1633 form_fields.append(("issue", str(options.issue))) | 1686 form_fields.append(("issue", str(options.issue))) |
| 1634 if options.email: | 1687 if options.email: |
| 1635 form_fields.append(("user", options.email)) | 1688 form_fields.append(("user", options.email)) |
| 1636 if options.reviewers: | 1689 if options.reviewers: |
| 1637 for reviewer in options.reviewers.split(','): | 1690 for reviewer in options.reviewers.split(','): |
| 1638 CheckReviewer(reviewer) | 1691 CheckReviewer(reviewer) |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1712 try: | 1765 try: |
| 1713 RealMain(sys.argv) | 1766 RealMain(sys.argv) |
| 1714 except KeyboardInterrupt: | 1767 except KeyboardInterrupt: |
| 1715 print | 1768 print |
| 1716 StatusUpdate("Interrupted.") | 1769 StatusUpdate("Interrupted.") |
| 1717 sys.exit(1) | 1770 sys.exit(1) |
| 1718 | 1771 |
| 1719 | 1772 |
| 1720 if __name__ == "__main__": | 1773 if __name__ == "__main__": |
| 1721 main() | 1774 main() |
| OLD | NEW |