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 |