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 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
163 """Raised to indicate there was an error authenticating with ClientLogin.""" | 163 """Raised to indicate there was an error authenticating with ClientLogin.""" |
164 | 164 |
165 def __init__(self, url, code, msg, headers, args): | 165 def __init__(self, url, code, msg, headers, args): |
166 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) | 166 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) |
167 self.args = args | 167 self.args = args |
168 self._reason = args["Error"] | 168 self._reason = args["Error"] |
169 self.info = args.get("Info", None) | 169 self.info = args.get("Info", None) |
170 | 170 |
171 @property | 171 @property |
172 def reason(self): | 172 def reason(self): |
| 173 # reason is a property on python 2.7 but a member variable on <=2.6. |
| 174 # self.args is modified so it cannot be used as-is so save the value in |
| 175 # self._reason. |
173 return self._reason | 176 return self._reason |
174 | 177 |
175 | 178 |
176 class AbstractRpcServer(object): | 179 class AbstractRpcServer(object): |
177 """Provides a common interface for a simple RPC server.""" | 180 """Provides a common interface for a simple RPC server.""" |
178 | 181 |
179 def __init__(self, host, auth_function, host_override=None, extra_headers={}, | 182 def __init__(self, host, auth_function, host_override=None, extra_headers={}, |
180 save_cookies=False, account_type=AUTH_ACCOUNT_TYPE): | 183 save_cookies=False, account_type=AUTH_ACCOUNT_TYPE): |
181 """Creates a new HttpRpcServer. | 184 """Creates a new AbstractRpcServer. |
182 | 185 |
183 Args: | 186 Args: |
184 host: The host to send requests to. | 187 host: The host to send requests to. |
185 auth_function: A function that takes no arguments and returns an | 188 auth_function: A function that takes no arguments and returns an |
186 (email, password) tuple when called. Will be called if authentication | 189 (email, password) tuple when called. Will be called if authentication |
187 is required. | 190 is required. |
188 host_override: The host header to send to the server (defaults to host). | 191 host_override: The host header to send to the server (defaults to host). |
189 extra_headers: A dict of extra headers to append to every request. | 192 extra_headers: A dict of extra headers to append to every request. |
190 save_cookies: If True, save the authentication cookies to local disk. | 193 save_cookies: If True, save the authentication cookies to local disk. |
191 If False, use an in-memory cookiejar instead. Subclasses must | 194 If False, use an in-memory cookiejar instead. Subclasses must |
(...skipping 400 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
592 group.add_option("--p4_changelist", action="store", dest="p4_changelist", | 595 group.add_option("--p4_changelist", action="store", dest="p4_changelist", |
593 metavar="P4_CHANGELIST", default=None, | 596 metavar="P4_CHANGELIST", default=None, |
594 help=("Perforce changelist id")) | 597 help=("Perforce changelist id")) |
595 group.add_option("--p4_client", action="store", dest="p4_client", | 598 group.add_option("--p4_client", action="store", dest="p4_client", |
596 metavar="P4_CLIENT", default=None, | 599 metavar="P4_CLIENT", default=None, |
597 help=("Perforce client/workspace")) | 600 help=("Perforce client/workspace")) |
598 group.add_option("--p4_user", action="store", dest="p4_user", | 601 group.add_option("--p4_user", action="store", dest="p4_user", |
599 metavar="P4_USER", default=None, | 602 metavar="P4_USER", default=None, |
600 help=("Perforce user")) | 603 help=("Perforce user")) |
601 | 604 |
| 605 |
| 606 class KeyringCreds(object): |
| 607 def __init__(self, server, host, email): |
| 608 self.server = server |
| 609 self.host = host |
| 610 self.email = email |
| 611 self.accounts_seen = set() |
| 612 |
| 613 def GetUserCredentials(self): |
| 614 """Prompts the user for a username and password. |
| 615 |
| 616 Only use keyring on the initial call. If the keyring contains the wrong |
| 617 password, we want to give the user a chance to enter another one. |
| 618 """ |
| 619 # Create a local alias to the email variable to avoid Python's crazy |
| 620 # scoping rules. |
| 621 global keyring |
| 622 email = self.email |
| 623 if email is None: |
| 624 email = GetEmail("Email (login for uploading to %s)" % self.server) |
| 625 password = None |
| 626 if keyring and not email in self.accounts_seen: |
| 627 try: |
| 628 password = keyring.get_password(self.host, email) |
| 629 except: |
| 630 # Sadly, we have to trap all errors here as |
| 631 # gnomekeyring.IOError inherits from object. :/ |
| 632 print "Failed to get password from keyring" |
| 633 keyring = None |
| 634 if password is not None: |
| 635 print "Using password from system keyring." |
| 636 self.accounts_seen.add(email) |
| 637 else: |
| 638 password = getpass.getpass("Password for %s: " % email) |
| 639 if keyring: |
| 640 answer = raw_input("Store password in system keyring?(y/N) ").strip() |
| 641 if answer == "y": |
| 642 keyring.set_password(host, email, password) |
| 643 self.accounts_seen.add(email) |
| 644 return (email, password) |
| 645 |
| 646 |
602 def GetRpcServer(server, email=None, host_override=None, save_cookies=True, | 647 def GetRpcServer(server, email=None, host_override=None, save_cookies=True, |
603 account_type=AUTH_ACCOUNT_TYPE): | 648 account_type=AUTH_ACCOUNT_TYPE): |
604 """Returns an instance of an AbstractRpcServer. | 649 """Returns an instance of an AbstractRpcServer. |
605 | 650 |
606 Args: | 651 Args: |
607 server: String containing the review server URL. | 652 server: String containing the review server URL. |
608 email: String containing user's email address. | 653 email: String containing user's email address. |
609 host_override: If not None, string containing an alternate hostname to use | 654 host_override: If not None, string containing an alternate hostname to use |
610 in the host header. | 655 in the host header. |
611 save_cookies: Whether authentication cookies should be saved to disk. | 656 save_cookies: Whether authentication cookies should be saved to disk. |
612 account_type: Account type for authentication, either 'GOOGLE' | 657 account_type: Account type for authentication, either 'GOOGLE' |
613 or 'HOSTED'. Defaults to AUTH_ACCOUNT_TYPE. | 658 or 'HOSTED'. Defaults to AUTH_ACCOUNT_TYPE. |
614 | 659 |
615 Returns: | 660 Returns: |
616 A new AbstractRpcServer, on which RPC calls can be made. | 661 A new HttpRpcServer, on which RPC calls can be made. |
617 """ | 662 """ |
618 | 663 |
619 rpc_server_class = HttpRpcServer | |
620 | |
621 # If this is the dev_appserver, use fake authentication. | 664 # If this is the dev_appserver, use fake authentication. |
622 host = (host_override or server).lower() | 665 host = (host_override or server).lower() |
623 if re.match(r'(http://)?localhost([:/]|$)', host): | 666 if re.match(r'(http://)?localhost([:/]|$)', host): |
624 if email is None: | 667 if email is None: |
625 email = "test@example.com" | 668 email = "test@example.com" |
626 logging.info("Using debug user %s. Override with --email" % email) | 669 logging.info("Using debug user %s. Override with --email" % email) |
627 server = rpc_server_class( | 670 server = HttpRpcServer( |
628 server, | 671 server, |
629 lambda: (email, "password"), | 672 lambda: (email, "password"), |
630 host_override=host_override, | 673 host_override=host_override, |
631 extra_headers={"Cookie": | 674 extra_headers={"Cookie": |
632 'dev_appserver_login="%s:False"' % email}, | 675 'dev_appserver_login="%s:False"' % email}, |
633 save_cookies=save_cookies, | 676 save_cookies=save_cookies, |
634 account_type=account_type) | 677 account_type=account_type) |
635 # Don't try to talk to ClientLogin. | 678 # Don't try to talk to ClientLogin. |
636 server.authenticated = True | 679 server.authenticated = True |
637 return server | 680 return server |
638 | 681 |
639 def GetUserCredentials(): | 682 return HttpRpcServer(server, |
640 """Prompts the user for a username and password.""" | 683 KeyringCreds(server, host, email).GetUserCredentials, |
641 # Create a local alias to the email variable to avoid Python's crazy | 684 host_override=host_override, |
642 # scoping rules. | 685 save_cookies=save_cookies) |
643 global keyring | |
644 local_email = email | |
645 if local_email is None: | |
646 local_email = GetEmail("Email (login for uploading to %s)" % server) | |
647 password = None | |
648 if keyring: | |
649 try: | |
650 password = keyring.get_password(host, local_email) | |
651 except: | |
652 # Sadly, we have to trap all errors here as | |
653 # gnomekeyring.IOError inherits from object. :/ | |
654 print "Failed to get password from keyring" | |
655 keyring = None | |
656 if password is not None: | |
657 print "Using password from system keyring." | |
658 else: | |
659 password = getpass.getpass("Password for %s: " % local_email) | |
660 if keyring: | |
661 answer = raw_input("Store password in system keyring?(y/N) ").strip() | |
662 if answer == "y": | |
663 keyring.set_password(host, local_email, password) | |
664 return (local_email, password) | |
665 | |
666 return rpc_server_class(server, | |
667 GetUserCredentials, | |
668 host_override=host_override, | |
669 save_cookies=save_cookies) | |
670 | 686 |
671 | 687 |
672 def EncodeMultipartFormData(fields, files): | 688 def EncodeMultipartFormData(fields, files): |
673 """Encode form fields for multipart/form-data. | 689 """Encode form fields for multipart/form-data. |
674 | 690 |
675 Args: | 691 Args: |
676 fields: A sequence of (name, value) elements for regular form fields. | 692 fields: A sequence of (name, value) elements for regular form fields. |
677 files: A sequence of (name, filename, value) elements for data to be | 693 files: A sequence of (name, filename, value) elements for data to be |
678 uploaded as files. | 694 uploaded as files. |
679 Returns: | 695 Returns: |
(...skipping 612 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1292 if self.options.revision: | 1308 if self.options.revision: |
1293 if ":" in self.options.revision: | 1309 if ":" in self.options.revision: |
1294 extra_args = self.options.revision.split(":", 1) + extra_args | 1310 extra_args = self.options.revision.split(":", 1) + extra_args |
1295 else: | 1311 else: |
1296 extra_args = [self.options.revision] + extra_args | 1312 extra_args = [self.options.revision] + extra_args |
1297 | 1313 |
1298 # --no-ext-diff is broken in some versions of Git, so try to work around | 1314 # --no-ext-diff is broken in some versions of Git, so try to work around |
1299 # this by overriding the environment (but there is still a problem if the | 1315 # this by overriding the environment (but there is still a problem if the |
1300 # git config key "diff.external" is used). | 1316 # git config key "diff.external" is used). |
1301 env = os.environ.copy() | 1317 env = os.environ.copy() |
1302 if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF'] | 1318 if "GIT_EXTERNAL_DIFF" in env: |
| 1319 del env["GIT_EXTERNAL_DIFF"] |
1303 # -M/-C will not print the diff for the deleted file when a file is renamed. | 1320 # -M/-C will not print the diff for the deleted file when a file is renamed. |
1304 # This is confusing because the original file will not be shown on the | 1321 # This is confusing because the original file will not be shown on the |
1305 # review when a file is renamed. So first get the diff of all deleted files, | 1322 # review when a file is renamed. So, get a diff with ONLY deletes, then |
1306 # then the diff of everything except deleted files with rename and copy | 1323 # append a diff (with rename detection), without deletes. |
1307 # support enabled. | |
1308 cmd = [ | 1324 cmd = [ |
1309 "git", "diff", "--no-color", "--no-ext-diff", "--full-index", | 1325 "git", "diff", "--no-color", "--no-ext-diff", "--full-index", |
1310 "--ignore-submodules", | 1326 "--ignore-submodules", |
1311 ] | 1327 ] |
1312 diff = RunShell( | 1328 diff = RunShell( |
1313 cmd + ["--diff-filter=D"] + extra_args, env=env, silent_ok=True) | 1329 cmd + ["--no-renames", "--diff-filter=D"] + extra_args, |
| 1330 env=env, silent_ok=True) |
1314 diff += RunShell( | 1331 diff += RunShell( |
1315 cmd + ["--find-copies-harder", "--diff-filter=ACMRT"] + extra_args, | 1332 cmd + ["--find-copies-harder", "-l100000", "--diff-filter=AMCRT"] |
| 1333 + extra_args, |
1316 env=env, silent_ok=True) | 1334 env=env, silent_ok=True) |
| 1335 |
1317 # The CL could be only file deletion or not. So accept silent diff for both | 1336 # The CL could be only file deletion or not. So accept silent diff for both |
1318 # commands then check for an empty diff manually. | 1337 # commands then check for an empty diff manually. |
1319 if not diff: | 1338 if not diff: |
1320 ErrorExit("No output from %s" % (cmd + extra_args)) | 1339 ErrorExit("No output from %s" % (cmd + extra_args)) |
1321 return diff | 1340 return diff |
1322 | 1341 |
1323 def GetUnknownFiles(self): | 1342 def GetUnknownFiles(self): |
1324 status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], | 1343 status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], |
1325 silent_ok=True) | 1344 silent_ok=True) |
1326 return status.splitlines() | 1345 return status.splitlines() |
(...skipping 1025 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2352 os.environ['LC_ALL'] = 'C' | 2371 os.environ['LC_ALL'] = 'C' |
2353 RealMain(sys.argv) | 2372 RealMain(sys.argv) |
2354 except KeyboardInterrupt: | 2373 except KeyboardInterrupt: |
2355 print | 2374 print |
2356 StatusUpdate("Interrupted.") | 2375 StatusUpdate("Interrupted.") |
2357 sys.exit(1) | 2376 sys.exit(1) |
2358 | 2377 |
2359 | 2378 |
2360 if __name__ == "__main__": | 2379 if __name__ == "__main__": |
2361 main() | 2380 main() |
OLD | NEW |