| Index: third_party/upload.py
|
| diff --git a/third_party/upload.py b/third_party/upload.py
|
| index f8e35ea5fce59eead98778ec70566c82acc74d64..22ea8f50d7a29e7a642fb94b24b85331b7d5a7c2 100644
|
| --- a/third_party/upload.py
|
| +++ b/third_party/upload.py
|
| @@ -58,6 +58,11 @@ try:
|
| except ImportError:
|
| pass
|
|
|
| +try:
|
| + import keyring
|
| +except ImportError:
|
| + keyring = None
|
| +
|
| # The logging verbosity:
|
| # 0: Errors only.
|
| # 1: Status messages.
|
| @@ -65,6 +70,15 @@ except ImportError:
|
| # 3: Debug logs.
|
| verbosity = 1
|
|
|
| +# The account type used for authentication.
|
| +# This line could be changed by the review server (see handler for
|
| +# upload.py).
|
| +AUTH_ACCOUNT_TYPE = "GOOGLE"
|
| +
|
| +# URL of the default review server. As for AUTH_ACCOUNT_TYPE, this line could be
|
| +# changed by the review server (see handler for upload.py).
|
| +DEFAULT_REVIEW_SERVER = "codereview.appspot.com"
|
| +
|
| # Max size of patch or base file.
|
| MAX_UPLOAD_SIZE = 900 * 1024
|
|
|
| @@ -77,7 +91,8 @@ VCS_UNKNOWN = "Unknown"
|
| # whitelist for non-binary filetypes which do not start with "text/"
|
| # .mm (Objective-C) shows up as application/x-freemind on my Linux box.
|
| TEXT_MIMETYPES = ['application/javascript', 'application/x-javascript',
|
| - 'application/xml', 'application/x-freemind']
|
| + 'application/xml', 'application/x-freemind',
|
| + 'application/x-sh']
|
|
|
| VCS_ABBREVIATIONS = {
|
| VCS_MERCURIAL.lower(): VCS_MERCURIAL,
|
| @@ -153,7 +168,7 @@ class AbstractRpcServer(object):
|
| """Provides a common interface for a simple RPC server."""
|
|
|
| def __init__(self, host, auth_function, host_override=None, extra_headers={},
|
| - save_cookies=False):
|
| + save_cookies=False, account_type=AUTH_ACCOUNT_TYPE):
|
| """Creates a new HttpRpcServer.
|
|
|
| Args:
|
| @@ -166,6 +181,8 @@ class AbstractRpcServer(object):
|
| save_cookies: If True, save the authentication cookies to local disk.
|
| If False, use an in-memory cookiejar instead. Subclasses must
|
| implement this functionality. Defaults to False.
|
| + account_type: Account type used for authentication. Defaults to
|
| + AUTH_ACCOUNT_TYPE.
|
| """
|
| self.host = host
|
| if (not self.host.startswith("http://") and
|
| @@ -176,6 +193,7 @@ class AbstractRpcServer(object):
|
| self.authenticated = False
|
| self.extra_headers = extra_headers
|
| self.save_cookies = save_cookies
|
| + self.account_type = account_type
|
| self.opener = self._GetOpener()
|
| if self.host_override:
|
| logging.info("Server: %s; Host: %s", self.host, self.host_override)
|
| @@ -214,7 +232,7 @@ class AbstractRpcServer(object):
|
| Returns:
|
| The authentication token returned by ClientLogin.
|
| """
|
| - account_type = "GOOGLE"
|
| + account_type = self.account_type
|
| if self.host.endswith(".google.com"):
|
| # Needed for use inside Google.
|
| account_type = "HOSTED"
|
| @@ -294,7 +312,9 @@ class AbstractRpcServer(object):
|
| print >>sys.stderr, (
|
| "Please go to\n"
|
| "https://www.google.com/accounts/DisplayUnlockCaptcha\n"
|
| - "and verify you are a human. Then try again.")
|
| + "and verify you are a human. Then try again.\n"
|
| + "If you are using a Google Apps account the URL is:\n"
|
| + "https://www.google.com/a/yourdomain.com/UnlockCaptcha")
|
| break
|
| if e.reason == "NotVerified":
|
| print >>sys.stderr, "Account not verified."
|
| @@ -443,7 +463,7 @@ group.add_option("--noisy", action="store_const", const=3,
|
| # Review server
|
| group = parser.add_option_group("Review server options")
|
| group.add_option("-s", "--server", action="store", dest="server",
|
| - default="codereview.appspot.com",
|
| + default=DEFAULT_REVIEW_SERVER,
|
| metavar="SERVER",
|
| help=("The server to upload to. The format is host[:port]. "
|
| "Defaults to '%default'."))
|
| @@ -456,6 +476,12 @@ group.add_option("-H", "--host", action="store", dest="host",
|
| group.add_option("--no_cookies", action="store_false",
|
| dest="save_cookies", default=True,
|
| help="Do not save authentication cookies to local disk.")
|
| +group.add_option("--account_type", action="store", dest="account_type",
|
| + metavar="TYPE", default=AUTH_ACCOUNT_TYPE,
|
| + choices=["GOOGLE", "HOSTED"],
|
| + help=("Override the default account type "
|
| + "(defaults to '%default', "
|
| + "valid choices are 'GOOGLE' and 'HOSTED')."))
|
| # Issue
|
| group = parser.add_option_group("Issue options")
|
| group.add_option("-d", "--description", action="store", dest="description",
|
| @@ -508,7 +534,8 @@ group.add_option("--emulate_svn_auto_props", action="store_true",
|
| help=("Emulate Subversion's auto properties feature."))
|
|
|
|
|
| -def GetRpcServer(server, email=None, host_override=None, save_cookies=True):
|
| +def GetRpcServer(server, email=None, host_override=None, save_cookies=True,
|
| + account_type=AUTH_ACCOUNT_TYPE):
|
| """Returns an instance of an AbstractRpcServer.
|
|
|
| Args:
|
| @@ -517,6 +544,8 @@ def GetRpcServer(server, email=None, host_override=None, save_cookies=True):
|
| host_override: If not None, string containing an alternate hostname to use
|
| in the host header.
|
| save_cookies: Whether authentication cookies should be saved to disk.
|
| + account_type: Account type for authentication, either 'GOOGLE'
|
| + or 'HOSTED'. Defaults to AUTH_ACCOUNT_TYPE.
|
|
|
| Returns:
|
| A new AbstractRpcServer, on which RPC calls can be made.
|
| @@ -536,7 +565,8 @@ def GetRpcServer(server, email=None, host_override=None, save_cookies=True):
|
| host_override=host_override,
|
| extra_headers={"Cookie":
|
| 'dev_appserver_login="%s:False"' % email},
|
| - save_cookies=save_cookies)
|
| + save_cookies=save_cookies,
|
| + account_type=account_type)
|
| # Don't try to talk to ClientLogin.
|
| server.authenticated = True
|
| return server
|
| @@ -548,7 +578,17 @@ def GetRpcServer(server, email=None, host_override=None, save_cookies=True):
|
| local_email = email
|
| if local_email is None:
|
| local_email = GetEmail("Email (login for uploading to %s)" % server)
|
| - password = getpass.getpass("Password for %s: " % local_email)
|
| + password = None
|
| + if keyring:
|
| + password = keyring.get_password(host, local_email)
|
| + if password is not None:
|
| + print "Using password from system keyring."
|
| + else:
|
| + password = getpass.getpass("Password for %s: " % local_email)
|
| + if keyring:
|
| + answer = raw_input("Store password in system keyring?(y/N) ").strip()
|
| + if answer == "y":
|
| + keyring.set_password(host, local_email, password)
|
| return (local_email, password)
|
|
|
| return rpc_server_class(server,
|
| @@ -577,6 +617,8 @@ def EncodeMultipartFormData(fields, files):
|
| lines.append('--' + BOUNDARY)
|
| lines.append('Content-Disposition: form-data; name="%s"' % key)
|
| lines.append('')
|
| + if isinstance(value, unicode):
|
| + value = value.encode('utf-8')
|
| lines.append(value)
|
| for (key, filename, value) in files:
|
| lines.append('--' + BOUNDARY)
|
| @@ -584,6 +626,8 @@ def EncodeMultipartFormData(fields, files):
|
| (key, filename))
|
| lines.append('Content-Type: %s' % GetContentType(filename))
|
| lines.append('')
|
| + if isinstance(value, unicode):
|
| + value = value.encode('utf-8')
|
| lines.append(value)
|
| lines.append('--' + BOUNDARY + '--')
|
| lines.append('')
|
| @@ -1041,9 +1085,17 @@ class SubversionVCS(VersionControlSystem):
|
| universal_newlines=universal_newlines,
|
| silent_ok=True)
|
| else:
|
| - base_content = RunShell(["svn", "cat", filename],
|
| - universal_newlines=universal_newlines,
|
| - silent_ok=True)
|
| + base_content, ret_code = RunShellWithReturnCode(
|
| + ["svn", "cat", filename], universal_newlines=universal_newlines)
|
| + if ret_code and status[0] == "R":
|
| + # It's a replaced file without local history (see issue208).
|
| + # The base file needs to be fetched from the server.
|
| + url = "%s/%s" % (self.svn_base, filename)
|
| + base_content = RunShell(["svn", "cat", url],
|
| + universal_newlines=universal_newlines,
|
| + silent_ok=True)
|
| + elif ret_code:
|
| + ErrorExit("Got error status from 'svn cat %s'", filename)
|
| if not is_binary:
|
| args = []
|
| if self.rev_start:
|
| @@ -1625,7 +1677,8 @@ def RealMain(argv, data=None):
|
| rpc_server = GetRpcServer(options.server,
|
| options.email,
|
| options.host,
|
| - options.save_cookies)
|
| + options.save_cookies,
|
| + options.account_type)
|
| form_fields = [("subject", message)]
|
| if base:
|
| form_fields.append(("base", base))
|
|
|