1c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#!/usr/bin/env python 2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# 3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Copyright 2007 Google Inc. 4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# 5c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Licensed under the Apache License, Version 2.0 (the "License"); 6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# you may not use this file except in compliance with the License. 7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# You may obtain a copy of the License at 8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# 9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# http://www.apache.org/licenses/LICENSE-2.0 10c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# 11c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Unless required by applicable law or agreed to in writing, software 12c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# distributed under the License is distributed on an "AS IS" BASIS, 13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# See the License for the specific language governing permissions and 15c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# limitations under the License. 16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch"""Tool for uploading diffs from a version control system to the codereview app. 18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 19c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochUsage summary: upload.py [options] [-- diff_options] 20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 21c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochDiff options are passed to the diff command of the underlying system. 22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 23c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochSupported version control systems: 24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Git 25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Mercurial 26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Subversion 27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 28c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochIt is important for Git/Mercurial users to specify a tree/node/branch to diff 29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochagainst by using the '--rev' option. 30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch""" 31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# This code is derived from appcfg.py in the App Engine SDK (open source), 32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# and from ASPN recipe #146306. 33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport cookielib 35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport getpass 36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport logging 37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport md5 38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport mimetypes 39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport optparse 40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport os 41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport re 42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport socket 43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport subprocess 44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport sys 45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport urllib 46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport urllib2 47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochimport urlparse 48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochtry: 50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch import readline 51c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochexcept ImportError: 52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch pass 53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# The logging verbosity: 55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# 0: Errors only. 56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# 1: Status messages. 57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# 2: Info logs. 58c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# 3: Debug logs. 59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochverbosity = 1 60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Max size of patch or base file. 62c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochMAX_UPLOAD_SIZE = 900 * 1024 63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef GetEmail(prompt): 66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Prompts the user for their email address and returns it. 67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch The last used email address is saved to a file and offered up as a suggestion 69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch to the user. If the user presses enter without typing in anything the last 70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch used email address is used. If the user enters a new address, it is saved 71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for next time we prompt. 72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_email_file_name = os.path.expanduser("~/.last_codereview_email_address") 75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_email = "" 76c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if os.path.exists(last_email_file_name): 77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_email_file = open(last_email_file_name, "r") 79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_email = last_email_file.readline().strip("\n") 80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_email_file.close() 81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch prompt += " [%s]" % last_email 82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except IOError, e: 83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch pass 84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch email = raw_input(prompt + ": ").strip() 85c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if email: 86c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 87c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_email_file = open(last_email_file_name, "w") 88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_email_file.write(email) 89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_email_file.close() 90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except IOError, e: 91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch pass 92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch email = last_email 94c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return email 95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef StatusUpdate(msg): 98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Print a status message to stdout. 99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch If 'verbosity' is greater than 0, print the message. 101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch msg: The string to print. 104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if verbosity > 0: 106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print msg 107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef ErrorExit(msg): 110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Print an error message to stderr and exit.""" 111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, msg 112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch sys.exit(1) 113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass ClientLoginError(urllib2.HTTPError): 116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Raised to indicate there was an error authenticating with ClientLogin.""" 117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def __init__(self, url, code, msg, headers, args): 119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch urllib2.HTTPError.__init__(self, url, code, msg, headers, None) 120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.args = args 121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.reason = args["Error"] 122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass AbstractRpcServer(object): 125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Provides a common interface for a simple RPC server.""" 126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 127c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def __init__(self, host, auth_function, host_override=None, extra_headers={}, 128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_cookies=False): 129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Creates a new HttpRpcServer. 130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch host: The host to send requests to. 133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch auth_function: A function that takes no arguments and returns an 134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (email, password) tuple when called. Will be called if authentication 135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch is required. 136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch host_override: The host header to send to the server (defaults to host). 137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch extra_headers: A dict of extra headers to append to every request. 138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_cookies: If True, save the authentication cookies to local disk. 139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch If False, use an in-memory cookiejar instead. Subclasses must 140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch implement this functionality. Defaults to False. 141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.host = host 143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.host_override = host_override 144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.auth_function = auth_function 145c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.authenticated = False 146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.extra_headers = extra_headers 147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.save_cookies = save_cookies 148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.opener = self._GetOpener() 149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.host_override: 150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Server: %s; Host: %s", self.host, self.host_override) 151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Server: %s", self.host) 153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _GetOpener(self): 155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Returns an OpenerDirector for making HTTP requests. 156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch A urllib2.OpenerDirector object. 159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise NotImplementedError() 161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _CreateRequest(self, url, data=None): 163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Creates a new urllib request.""" 164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.debug("Creating request for: '%s' with payload:\n%s", url, data) 165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch req = urllib2.Request(url, data=data) 166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.host_override: 167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch req.add_header("Host", self.host_override) 168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for key, value in self.extra_headers.iteritems(): 169c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch req.add_header(key, value) 170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return req 171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _GetAuthToken(self, email, password): 173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Uses ClientLogin to authenticate the user, returning an auth token. 174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch email: The user's email address 177c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch password: The user's password 178c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 179c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Raises: 180c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ClientLoginError: If there was an error authenticating with ClientLogin. 181c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch HTTPError: If there was some other form of HTTP error. 182c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 183c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 184c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch The authentication token returned by ClientLogin. 185c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 186c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch account_type = "GOOGLE" 187c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.host.endswith(".google.com"): 188c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Needed for use inside Google. 189c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch account_type = "HOSTED" 190c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch req = self._CreateRequest( 191c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url="https://www.google.com/accounts/ClientLogin", 192c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch data=urllib.urlencode({ 193c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "Email": email, 194c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "Passwd": password, 195c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "service": "ah", 196c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "source": "rietveld-codereview-upload", 197c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "accountType": account_type, 198c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch }), 199c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ) 200c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 201c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response = self.opener.open(req) 202c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_body = response.read() 203c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_dict = dict(x.split("=") 204c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for x in response_body.split("\n") if x) 205c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return response_dict["Auth"] 206c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except urllib2.HTTPError, e: 207c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.code == 403: 208c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch body = e.read() 209c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_dict = dict(x.split("=", 1) for x in body.split("\n") if x) 210c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise ClientLoginError(req.get_full_url(), e.code, e.msg, 211c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch e.headers, response_dict) 212c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 213c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise 214c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 215c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _GetAuthCookie(self, auth_token): 216c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Fetches authentication cookies for an authentication token. 217c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 218c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 219c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch auth_token: The authentication token returned by ClientLogin. 220c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 221c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Raises: 222c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch HTTPError: If there was an error fetching the authentication cookies. 223c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 224c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # This is a dummy value to allow us to identify when we're successful. 225c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch continue_location = "http://localhost/" 226c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args = {"continue": continue_location, "auth": auth_token} 227c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch req = self._CreateRequest("http://%s/_ah/login?%s" % 228c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (self.host, urllib.urlencode(args))) 229c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 230c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response = self.opener.open(req) 231c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except urllib2.HTTPError, e: 232c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response = e 233c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (response.code != 302 or 234c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response.info()["location"] != continue_location): 235c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, 236c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response.headers, response.fp) 237c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.authenticated = True 238c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 239c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _Authenticate(self): 240c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Authenticates the user. 241c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 242c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch The authentication process works as follows: 243c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1) We get a username and password from the user 244c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 2) We use ClientLogin to obtain an AUTH token for the user 245c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (see http://code.google.com/apis/accounts/AuthForInstalledApps.html). 246c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 3) We pass the auth token to /_ah/login on the server to obtain an 247c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch authentication cookie. If login was successful, it tries to redirect 248c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch us to the URL we provided. 249c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 250c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch If we attempt to access the upload API without first obtaining an 251c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch authentication cookie, it returns a 401 response and directs us to 252c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch authenticate ourselves with ClientLogin. 253c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 254c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for i in range(3): 255c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch credentials = self.auth_function() 256c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 257c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch auth_token = self._GetAuthToken(credentials[0], credentials[1]) 258c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except ClientLoginError, e: 259c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.reason == "BadAuthentication": 260c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, "Invalid username or password." 261c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch continue 262c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.reason == "CaptchaRequired": 263c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, ( 264c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "Please go to\n" 265c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "https://www.google.com/accounts/DisplayUnlockCaptcha\n" 266c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "and verify you are a human. Then try again.") 267c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break 268c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.reason == "NotVerified": 269c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, "Account not verified." 270c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break 271c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.reason == "TermsNotAgreed": 272c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, "User has not agreed to TOS." 273c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break 274c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.reason == "AccountDeleted": 275c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, "The user account has been deleted." 276c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break 277c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.reason == "AccountDisabled": 278c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, "The user account has been disabled." 279c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break 280c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.reason == "ServiceDisabled": 281c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, ("The user's access to the service has been " 282c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "disabled.") 283c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break 284c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if e.reason == "ServiceUnavailable": 285c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, "The service is not available; try again later." 286c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break 287c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise 288c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self._GetAuthCookie(auth_token) 289c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return 290c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 291c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def Send(self, request_path, payload=None, 292c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch content_type="application/octet-stream", 293c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch timeout=None, 294c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch **kwargs): 295c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Sends an RPC and returns the response. 296c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 297c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 298c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_path: The path to send the request to, eg /api/appversion/create. 299c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch payload: The body of the request, or None to send an empty request. 300c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch content_type: The Content-Type header to use. 301c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch timeout: timeout in seconds; default None i.e. no timeout. 302c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (Note: for large requests on OS X, the timeout doesn't work right.) 303c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch kwargs: Any keyword arguments are converted into query string parameters. 304c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 305c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 306c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch The response body, as a string. 307c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 308c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # TODO: Don't require authentication. Let the server say 309c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # whether it is necessary. 310c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not self.authenticated: 311c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self._Authenticate() 312c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 313c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch old_timeout = socket.getdefaulttimeout() 314c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch socket.setdefaulttimeout(timeout) 315c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 316c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch tries = 0 317c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch while True: 318c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch tries += 1 319c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args = dict(kwargs) 320c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = "http://%s%s" % (self.host, request_path) 321c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if args: 322c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url += "?" + urllib.urlencode(args) 323c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch req = self._CreateRequest(url=url, data=payload) 324c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch req.add_header("Content-Type", content_type) 325c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 326c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch f = self.opener.open(req) 327c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response = f.read() 328c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch f.close() 329c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return response 330c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except urllib2.HTTPError, e: 331c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if tries > 3: 332c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise 333c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif e.code == 401: 334c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self._Authenticate() 335c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch## elif e.code >= 500 and e.code < 600: 336c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch## # Server Error - try again. 337c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch## continue 338c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 339c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise 340c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch finally: 341c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch socket.setdefaulttimeout(old_timeout) 342c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 343c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 344c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass HttpRpcServer(AbstractRpcServer): 345c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Provides a simplified RPC-style interface for HTTP requests.""" 346c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 347c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _Authenticate(self): 348c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Save the cookie jar after authentication.""" 349c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch super(HttpRpcServer, self)._Authenticate() 350c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.save_cookies: 351c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) 352c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.cookie_jar.save() 353c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 354c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _GetOpener(self): 355c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Returns an OpenerDirector that supports cookies and ignores redirects. 356c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 357c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 358c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch A urllib2.OpenerDirector object. 359c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 360c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch opener = urllib2.OpenerDirector() 361c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch opener.add_handler(urllib2.ProxyHandler()) 362c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch opener.add_handler(urllib2.UnknownHandler()) 363c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch opener.add_handler(urllib2.HTTPHandler()) 364c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch opener.add_handler(urllib2.HTTPDefaultErrorHandler()) 365c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch opener.add_handler(urllib2.HTTPSHandler()) 366c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch opener.add_handler(urllib2.HTTPErrorProcessor()) 367c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.save_cookies: 368c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies") 369c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file) 370c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if os.path.exists(self.cookie_file): 371c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 372c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.cookie_jar.load() 373c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.authenticated = True 374c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch StatusUpdate("Loaded authentication cookies from %s" % 375c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.cookie_file) 376c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except (cookielib.LoadError, IOError): 377c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Failed to load cookies - just ignore them. 378c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch pass 379c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 380c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Create an empty cookie file with mode 600 381c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch fd = os.open(self.cookie_file, os.O_CREAT, 0600) 382c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch os.close(fd) 383c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Always chmod the cookie file 384c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch os.chmod(self.cookie_file, 0600) 385c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 386c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Don't save cookies across runs of update.py. 387c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.cookie_jar = cookielib.CookieJar() 388c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar)) 389c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return opener 390c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 391c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 392c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochparser = optparse.OptionParser(usage="%prog [options] [-- diff_options]") 393c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochparser.add_option("-y", "--assume_yes", action="store_true", 394c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dest="assume_yes", default=False, 395c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Assume that the answer to yes/no questions is 'yes'.") 396c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Logging 397c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup = parser.add_option_group("Logging options") 398c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-q", "--quiet", action="store_const", const=0, 399c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dest="verbose", help="Print errors only.") 400c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-v", "--verbose", action="store_const", const=2, 401c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dest="verbose", default=1, 402c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Print info level logs (default).") 403c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("--noisy", action="store_const", const=3, 404c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dest="verbose", help="Print all logs.") 405c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Review server 406c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup = parser.add_option_group("Review server options") 407c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-s", "--server", action="store", dest="server", 408c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch default="codereview.appspot.com", 409c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="SERVER", 410c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help=("The server to upload to. The format is host[:port]. " 411c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "Defaults to 'codereview.appspot.com'.")) 412c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-e", "--email", action="store", dest="email", 413c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="EMAIL", default=None, 414c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="The username to use. Will prompt if omitted.") 415c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-H", "--host", action="store", dest="host", 416c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="HOST", default=None, 417c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Overrides the Host header sent with all RPCs.") 418c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("--no_cookies", action="store_false", 419c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dest="save_cookies", default=True, 420c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Do not save authentication cookies to local disk.") 421c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Issue 422c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup = parser.add_option_group("Issue options") 423c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-d", "--description", action="store", dest="description", 424c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="DESCRIPTION", default=None, 425c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Optional description when creating an issue.") 426c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-f", "--description_file", action="store", 427c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dest="description_file", metavar="DESCRIPTION_FILE", 428c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch default=None, 429c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Optional path of a file that contains " 430c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "the description when creating an issue.") 431c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-r", "--reviewers", action="store", dest="reviewers", 432c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="REVIEWERS", default=None, 433c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Add reviewers (comma separated email addresses).") 434c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("--cc", action="store", dest="cc", 435c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="CC", default=None, 436c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Add CC (comma separated email addresses).") 437c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Upload options 438c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup = parser.add_option_group("Patch options") 439c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-m", "--message", action="store", dest="message", 440c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="MESSAGE", default=None, 441c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="A message to identify the patch. " 442c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "Will prompt if omitted.") 443c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("-i", "--issue", type="int", action="store", 444c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="ISSUE", default=None, 445c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Issue number to which to add. Defaults to new issue.") 446c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("--download_base", action="store_true", 447c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dest="download_base", default=False, 448c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Base files will be downloaded by the server " 449c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "(side-by-side diffs may not work on files with CRs).") 450c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("--rev", action="store", dest="revision", 451c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch metavar="REV", default=None, 452c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Branch/tree/revision to diff against (used by DVCS).") 453c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgroup.add_option("--send_mail", action="store_true", 454c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dest="send_mail", default=False, 455c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch help="Send notification email to reviewers.") 456c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 457c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 458c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef GetRpcServer(options): 459c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Returns an instance of an AbstractRpcServer. 460c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 461c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 462c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch A new AbstractRpcServer, on which RPC calls can be made. 463c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 464c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 465c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch rpc_server_class = HttpRpcServer 466c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 467c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetUserCredentials(): 468c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Prompts the user for a username and password.""" 469c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch email = options.email 470c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if email is None: 471c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch email = GetEmail("Email (login for uploading to %s)" % options.server) 472c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch password = getpass.getpass("Password for %s: " % email) 473c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return (email, password) 474c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 475c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # If this is the dev_appserver, use fake authentication. 476c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch host = (options.host or options.server).lower() 477c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if host == "localhost" or host.startswith("localhost:"): 478c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch email = options.email 479c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if email is None: 480c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch email = "test@example.com" 481c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Using debug user %s. Override with --email" % email) 482c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch server = rpc_server_class( 483c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch options.server, 484c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lambda: (email, "password"), 485c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch host_override=options.host, 486c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch extra_headers={"Cookie": 487c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'dev_appserver_login="%s:False"' % email}, 488c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_cookies=options.save_cookies) 489c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Don't try to talk to ClientLogin. 490c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch server.authenticated = True 491c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return server 492c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 493c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return rpc_server_class(options.server, GetUserCredentials, 494c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch host_override=options.host, 495c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_cookies=options.save_cookies) 496c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 497c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 498c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef EncodeMultipartFormData(fields, files): 499c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Encode form fields for multipart/form-data. 500c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 501c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 502c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch fields: A sequence of (name, value) elements for regular form fields. 503c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch files: A sequence of (name, filename, value) elements for data to be 504c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch uploaded as files. 505c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 506c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (content_type, body) ready for httplib.HTTP instance. 507c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 508c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Source: 509c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 510c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 511c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-' 512c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch CRLF = '\r\n' 513c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines = [] 514c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (key, value) in fields: 515c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('--' + BOUNDARY) 516c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('Content-Disposition: form-data; name="%s"' % key) 517c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('') 518c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append(value) 519c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (key, filename, value) in files: 520c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('--' + BOUNDARY) 521c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % 522c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (key, filename)) 523c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('Content-Type: %s' % GetContentType(filename)) 524c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('') 525c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append(value) 526c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('--' + BOUNDARY + '--') 527c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines.append('') 528c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch body = CRLF.join(lines) 529c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch content_type = 'multipart/form-data; boundary=%s' % BOUNDARY 530c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return content_type, body 531c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 532c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 533c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef GetContentType(filename): 534c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Helper to guess the content-type from the filename.""" 535c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return mimetypes.guess_type(filename)[0] or 'application/octet-stream' 536c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 537c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 538c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# Use a shell for subcommands on Windows to get a PATH search. 539c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochuse_shell = sys.platform.startswith("win") 540c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 541c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef RunShellWithReturnCode(command, print_output=False, 542c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch universal_newlines=True): 543c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Executes a command and returns the output from stdout and the return code. 544c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 545c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 546c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch command: Command to execute. 547c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print_output: If True, the output is printed to stdout. 548c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch If False, both stdout and stderr are ignored. 549c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch universal_newlines: Use universal_newlines flag (default: True). 550c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 551c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 552c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Tuple (output, return code) 553c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 554c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Running %s", command) 555c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 556c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch shell=use_shell, universal_newlines=universal_newlines) 557c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if print_output: 558c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch output_array = [] 559c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch while True: 560c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch line = p.stdout.readline() 561c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not line: 562c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break 563c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print line.strip("\n") 564c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch output_array.append(line) 565c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch output = "".join(output_array) 566c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 567c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch output = p.stdout.read() 568c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch p.wait() 569c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch errout = p.stderr.read() 570c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if print_output and errout: 571c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print >>sys.stderr, errout 572c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch p.stdout.close() 573c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch p.stderr.close() 574c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return output, p.returncode 575c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 576c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 577c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef RunShell(command, silent_ok=False, universal_newlines=True, 578c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print_output=False): 579c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch data, retcode = RunShellWithReturnCode(command, print_output, 580c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch universal_newlines) 581c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if retcode: 582c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Got error status from %s:\n%s" % (command, data)) 583c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not silent_ok and not data: 584c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("No output from %s" % command) 585c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return data 586c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 587c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 588c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass VersionControlSystem(object): 589c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Abstract base class providing an interface to the VCS.""" 590c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 591c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def __init__(self, options): 592c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Constructor. 593c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 594c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 595c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch options: Command line options. 596c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 597c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.options = options 598c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 599c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GenerateDiff(self, args): 600c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Return the current diff as a string. 601c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 602c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 603c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args: Extra arguments to pass to the diff command. 604c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 605c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise NotImplementedError( 606c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "abstract method -- subclass %s must override" % self.__class__) 607c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 608c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetUnknownFiles(self): 609c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Return a list of files unknown to the VCS.""" 610c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise NotImplementedError( 611c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "abstract method -- subclass %s must override" % self.__class__) 612c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 613c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def CheckForUnknownFiles(self): 614c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Show an "are you sure?" prompt if there are unknown files.""" 615c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch unknown_files = self.GetUnknownFiles() 616c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if unknown_files: 617c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print "The following files are not added to version control:" 618c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in unknown_files: 619c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print line 620c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch prompt = "Are you sure to continue?(y/N) " 621c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch answer = raw_input(prompt).strip() 622c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if answer != "y": 623c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("User aborted") 624c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 625c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetBaseFile(self, filename): 626c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Get the content of the upstream version of a file. 627c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 628c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 629c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch A tuple (base_content, new_content, is_binary, status) 630c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content: The contents of the base file. 631c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content: For text files, this is empty. For binary files, this is 632c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch the contents of the new file, since the diff output won't contain 633c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch information to reconstruct the current file. 634c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch is_binary: True iff the file is binary. 635c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status: The status of the file. 636c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 637c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 638c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise NotImplementedError( 639c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "abstract method -- subclass %s must override" % self.__class__) 640c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 641c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 642c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetBaseFiles(self, diff): 643c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Helper that calls GetBase file for each file in the patch. 644c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 645c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 646c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch A dictionary that maps from filename to GetBaseFile's tuple. Filenames 647c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch are retrieved based on lines that start with "Index:" or 648c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "Property changes on:". 649c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 650c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch files = {} 651c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in diff.splitlines(True): 652c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if line.startswith('Index:') or line.startswith('Property changes on:'): 653c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch unused, filename = line.split(':', 1) 654c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # On Windows if a file has property changes its filename uses '\' 655c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # instead of '/'. 656c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filename = filename.strip().replace('\\', '/') 657c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch files[filename] = self.GetBaseFile(filename) 658c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return files 659c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 660c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 661c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options, 662c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch files): 663c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Uploads the base files (and if necessary, the current ones as well).""" 664c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 665c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def UploadFile(filename, file_id, content, is_binary, status, is_base): 666c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Uploads a file to the server.""" 667c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file_too_large = False 668c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if is_base: 669c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch type = "base" 670c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 671c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch type = "current" 672c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if len(content) > MAX_UPLOAD_SIZE: 673c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print ("Not uploading the %s file for %s because it's too large." % 674c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (type, filename)) 675c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file_too_large = True 676c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch content = "" 677c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch checksum = md5.new(content).hexdigest() 678c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.verbose > 0 and not file_too_large: 679c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print "Uploading %s file for %s" % (type, filename) 680c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id) 681c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields = [("filename", filename), 682c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ("status", status), 683c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ("checksum", checksum), 684c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ("is_binary", str(is_binary)), 685c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ("is_current", str(not is_base)), 686c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ] 687c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if file_too_large: 688c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("file_too_large", "1")) 689c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.email: 690c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("user", options.email)) 691c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ctype, body = EncodeMultipartFormData(form_fields, 692c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch [("data", filename, content)]) 693c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_body = rpc_server.Send(url, body, 694c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch content_type=ctype) 695c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not response_body.startswith("OK"): 696c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch StatusUpdate(" --> %s" % response_body) 697c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch sys.exit(1) 698c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 699c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patches = dict() 700c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch [patches.setdefault(v, k) for k, v in patch_list] 701c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for filename in patches.keys(): 702c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content, new_content, is_binary, status = files[filename] 703c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file_id_str = patches.get(filename) 704c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if file_id_str.find("nobase") != -1: 705c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = None 706c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file_id_str = file_id_str[file_id_str.rfind("_") + 1:] 707c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file_id = int(file_id_str) 708c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if base_content != None: 709c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch UploadFile(filename, file_id, base_content, is_binary, status, True) 710c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if new_content != None: 711c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch UploadFile(filename, file_id, new_content, is_binary, status, False) 712c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 713c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def IsImage(self, filename): 714c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Returns true if the filename has an image extension.""" 715c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch mimetype = mimetypes.guess_type(filename)[0] 716c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not mimetype: 717c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return False 718c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return mimetype.startswith("image/") 719c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 720c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 721c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass SubversionVCS(VersionControlSystem): 722c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Implementation of the VersionControlSystem interface for Subversion.""" 723c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 724c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def __init__(self, options): 725c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch super(SubversionVCS, self).__init__(options) 726c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.options.revision: 727c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch match = re.match(r"(\d+)(:(\d+))?", self.options.revision) 728c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not match: 729c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Invalid Subversion revision %s." % self.options.revision) 730c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.rev_start = match.group(1) 731c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.rev_end = match.group(3) 732c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 733c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.rev_start = self.rev_end = None 734c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Cache output from "svn list -r REVNO dirname". 735c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Keys: dirname, Values: 2-tuple (ouput for start rev and end rev). 736c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.svnls_cache = {} 737c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # SVN base URL is required to fetch files deleted in an older revision. 738c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Result is cached to not guess it over and over again in GetBaseFile(). 739c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch required = self.options.download_base or self.options.revision is not None 740c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.svn_base = self._GuessBase(required) 741c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 742c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GuessBase(self, required): 743c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Wrapper for _GuessBase.""" 744c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return self.svn_base 745c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 746c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _GuessBase(self, required): 747c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Returns the SVN base URL. 748c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 749c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 750c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch required: If true, exits if the url can't be guessed, otherwise None is 751c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch returned. 752c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 753c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch info = RunShell(["svn", "info"]) 754c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in info.splitlines(): 755c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch words = line.split() 756c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if len(words) == 2 and words[0] == "URL:": 757c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = words[1] 758c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) 759c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch username, netloc = urllib.splituser(netloc) 760c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if username: 761c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Removed username from base URL") 762c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if netloc.endswith("svn.python.org"): 763c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if netloc == "svn.python.org": 764c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if path.startswith("/projects/"): 765c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch path = path[9:] 766c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif netloc != "pythondev@svn.python.org": 767c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Unrecognized Python URL: %s" % url) 768c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base = "http://svn.python.org/view/*checkout*%s/" % path 769c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Guessed Python base = %s", base) 770c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif netloc.endswith("svn.collab.net"): 771c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if path.startswith("/repos/"): 772c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch path = path[6:] 773c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base = "http://svn.collab.net/viewvc/*checkout*%s/" % path 774c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Guessed CollabNet base = %s", base) 775c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif netloc.endswith(".googlecode.com"): 776c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch path = path + "/" 777c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base = urlparse.urlunparse(("http", netloc, path, params, 778c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch query, fragment)) 779c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Guessed Google Code base = %s", base) 780c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 781c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch path = path + "/" 782c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base = urlparse.urlunparse((scheme, netloc, path, params, 783c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch query, fragment)) 784c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Guessed base = %s", base) 785c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return base 786c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if required: 787c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Can't find URL in output from svn info") 788c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return None 789c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 790c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GenerateDiff(self, args): 791c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cmd = ["svn", "diff"] 792c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.options.revision: 793c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cmd += ["-r", self.options.revision] 794c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cmd.extend(args) 795c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch data = RunShell(cmd) 796c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch count = 0 797c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in data.splitlines(): 798c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if line.startswith("Index:") or line.startswith("Property changes on:"): 799c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch count += 1 800c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info(line) 801c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not count: 802c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("No valid patches found in output from svn diff") 803c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return data 804c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 805c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _CollapseKeywords(self, content, keyword_str): 806c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Collapses SVN keywords.""" 807c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # svn cat translates keywords but svn diff doesn't. As a result of this 808c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # behavior patching.PatchChunks() fails with a chunk mismatch error. 809c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # This part was originally written by the Review Board development team 810c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # who had the same problem (http://reviews.review-board.org/r/276/). 811c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Mapping of keywords to known aliases 812c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch svn_keywords = { 813c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Standard keywords 814c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'Date': ['Date', 'LastChangedDate'], 815c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'Revision': ['Revision', 'LastChangedRevision', 'Rev'], 816c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'Author': ['Author', 'LastChangedBy'], 817c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'HeadURL': ['HeadURL', 'URL'], 818c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'Id': ['Id'], 819c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 820c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Aliases 821c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'LastChangedDate': ['LastChangedDate', 'Date'], 822c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'LastChangedRevision': ['LastChangedRevision', 'Rev', 'Revision'], 823c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'LastChangedBy': ['LastChangedBy', 'Author'], 824c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 'URL': ['URL', 'HeadURL'], 825c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 826c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 827c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def repl(m): 828c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if m.group(2): 829c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return "$%s::%s$" % (m.group(1), " " * len(m.group(3))) 830c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return "$%s$" % m.group(1) 831c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch keywords = [keyword 832c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for name in keyword_str.split(" ") 833c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for keyword in svn_keywords.get(name, [])] 834c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content) 835c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 836c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetUnknownFiles(self): 837c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True) 838c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch unknown_files = [] 839c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in status.split("\n"): 840c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if line and line[0] == "?": 841c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch unknown_files.append(line) 842c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return unknown_files 843c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 844c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def ReadFile(self, filename): 845c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Returns the contents of a file.""" 846c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file = open(filename, 'rb') 847c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch result = "" 848c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 849c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch result = file.read() 850c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch finally: 851c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file.close() 852c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return result 853c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 854c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetStatus(self, filename): 855c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Returns the status of a file.""" 856c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not self.options.revision: 857c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = RunShell(["svn", "status", "--ignore-externals", filename]) 858c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not status: 859c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("svn status returned no output for %s" % filename) 860c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status_lines = status.splitlines() 861c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # If file is in a cl, the output will begin with 862c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # "\n--- Changelist 'cl_name':\n". See 863c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # http://svn.collab.net/repos/svn/trunk/notes/changelist-design.txt 864c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (len(status_lines) == 3 and 865c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch not status_lines[0] and 866c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status_lines[1].startswith("--- Changelist")): 867c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = status_lines[2] 868c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 869c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = status_lines[0] 870c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # If we have a revision to diff against we need to run "svn list" 871c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # for the old and the new revision and compare the results to get 872c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # the correct status for a file. 873c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 874c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch dirname, relfilename = os.path.split(filename) 875c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if dirname not in self.svnls_cache: 876c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cmd = ["svn", "list", "-r", self.rev_start, dirname or "."] 877c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch out, returncode = RunShellWithReturnCode(cmd) 878c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if returncode: 879c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Failed to get status for %s." % filename) 880c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch old_files = out.splitlines() 881c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args = ["svn", "list"] 882c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.rev_end: 883c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args += ["-r", self.rev_end] 884c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cmd = args + [dirname or "."] 885c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch out, returncode = RunShellWithReturnCode(cmd) 886c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if returncode: 887c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Failed to run command %s" % cmd) 888c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.svnls_cache[dirname] = (old_files, out.splitlines()) 889c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch old_files, new_files = self.svnls_cache[dirname] 890c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if relfilename in old_files and relfilename not in new_files: 891c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = "D " 892c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif relfilename in old_files and relfilename in new_files: 893c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = "M " 894c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 895c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = "A " 896c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return status 897c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 898c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetBaseFile(self, filename): 899c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = self.GetStatus(filename) 900c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = None 901c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content = None 902c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 903c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # If a file is copied its status will be "A +", which signifies 904c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # "addition-with-history". See "svn st" for more information. We need to 905c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # upload the original file or else diff parsing will fail if the file was 906c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # edited. 907c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if status[0] == "A" and status[3] != "+": 908c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # We'll need to upload the new content if we're adding a binary file 909c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # since diff's output won't contain it. 910c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch mimetype = RunShell(["svn", "propget", "svn:mime-type", filename], 911c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch silent_ok=True) 912c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = "" 913c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch is_binary = mimetype and not mimetype.startswith("text/") 914c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if is_binary and self.IsImage(filename): 915c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content = self.ReadFile(filename) 916c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif (status[0] in ("M", "D", "R") or 917c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (status[0] == "A" and status[3] == "+") or # Copied file. 918c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (status[0] == " " and status[1] == "M")): # Property change. 919c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args = [] 920c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.options.revision: 921c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) 922c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 923c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Don't change filename, it's needed later. 924c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = filename 925c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args += ["-r", "BASE"] 926c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cmd = ["svn"] + args + ["propget", "svn:mime-type", url] 927c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch mimetype, returncode = RunShellWithReturnCode(cmd) 928c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if returncode: 929c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # File does not exist in the requested revision. 930c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Reset mimetype, it contains an error message. 931c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch mimetype = "" 932c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch get_base = False 933c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch is_binary = mimetype and not mimetype.startswith("text/") 934c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if status[0] == " ": 935c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Empty base content just to force an upload. 936c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = "" 937c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif is_binary: 938c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.IsImage(filename): 939c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch get_base = True 940c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if status[0] == "M": 941c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not self.rev_end: 942c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content = self.ReadFile(filename) 943c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 944c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = "%s/%s@%s" % (self.svn_base, filename, self.rev_end) 945c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content = RunShell(["svn", "cat", url], 946c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch universal_newlines=True, silent_ok=True) 947c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 948c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = "" 949c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 950c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch get_base = True 951c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 952c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if get_base: 953c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if is_binary: 954c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch universal_newlines = False 955c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 956c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch universal_newlines = True 957c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.rev_start: 958c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # "svn cat -r REV delete_file.txt" doesn't work. cat requires 959c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # the full URL with "@REV" appended instead of using "-r" option. 960c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) 961c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = RunShell(["svn", "cat", url], 962c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch universal_newlines=universal_newlines, 963c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch silent_ok=True) 964c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 965c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = RunShell(["svn", "cat", filename], 966c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch universal_newlines=universal_newlines, 967c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch silent_ok=True) 968c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not is_binary: 969c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args = [] 970c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.rev_start: 971c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start) 972c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 973c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = filename 974c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args += ["-r", "BASE"] 975c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cmd = ["svn"] + args + ["propget", "svn:keywords", url] 976c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch keywords, returncode = RunShellWithReturnCode(cmd) 977c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if keywords and not returncode: 978c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = self._CollapseKeywords(base_content, keywords) 979c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 980c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch StatusUpdate("svn status returned unexpected output: %s" % status) 981c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch sys.exit(1) 982c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return base_content, new_content, is_binary, status[0:5] 983c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 984c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 985c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass GitVCS(VersionControlSystem): 986c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Implementation of the VersionControlSystem interface for Git.""" 987c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 988c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def __init__(self, options): 989c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch super(GitVCS, self).__init__(options) 990c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Map of filename -> hash of base file. 991c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.base_hashes = {} 992c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 993c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GenerateDiff(self, extra_args): 994c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # This is more complicated than svn's GenerateDiff because we must convert 995c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # the diff output to include an svn-style "Index:" line as well as record 996c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # the hashes of the base files, so we can upload them along with our diff. 997c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.options.revision: 998c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch extra_args = [self.options.revision] + extra_args 999c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch gitdiff = RunShell(["git", "diff", "--full-index"] + extra_args) 1000c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch svndiff = [] 1001c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filecount = 0 1002c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filename = None 1003c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in gitdiff.splitlines(): 1004c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch match = re.match(r"diff --git a/(.*) b/.*$", line) 1005c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if match: 1006c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filecount += 1 1007c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filename = match.group(1) 1008c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch svndiff.append("Index: %s\n" % filename) 1009c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1010c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # The "index" line in a git diff looks like this (long hashes elided): 1011c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # index 82c0d44..b2cee3f 100755 1012c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # We want to save the left hash, as that identifies the base file. 1013c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch match = re.match(r"index (\w+)\.\.", line) 1014c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if match: 1015c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.base_hashes[filename] = match.group(1) 1016c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch svndiff.append(line + "\n") 1017c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not filecount: 1018c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("No valid patches found in output from git diff") 1019c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return "".join(svndiff) 1020c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1021c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetUnknownFiles(self): 1022c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], 1023c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch silent_ok=True) 1024c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return status.splitlines() 1025c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1026c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetBaseFile(self, filename): 1027c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch hash = self.base_hashes[filename] 1028c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = None 1029c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content = None 1030c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch is_binary = False 1031c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if hash == "0" * 40: # All-zero hash indicates no base file. 1032c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = "A" 1033c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = "" 1034c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1035c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = "M" 1036c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content, returncode = RunShellWithReturnCode(["git", "show", hash]) 1037c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if returncode: 1038c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Got error status from 'git show %s'" % hash) 1039c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return (base_content, new_content, is_binary, status) 1040c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1041c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1042c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass MercurialVCS(VersionControlSystem): 1043c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Implementation of the VersionControlSystem interface for Mercurial.""" 1044c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1045c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def __init__(self, options, repo_dir): 1046c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch super(MercurialVCS, self).__init__(options) 1047c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Absolute path to repository (we can be in a subdir) 1048c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.repo_dir = os.path.normpath(repo_dir) 1049c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Compute the subdir 1050c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cwd = os.path.normpath(os.getcwd()) 1051c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch assert cwd.startswith(self.repo_dir) 1052c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.subdir = cwd[len(self.repo_dir):].lstrip(r"\/") 1053c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if self.options.revision: 1054c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.base_rev = self.options.revision 1055c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1056c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch self.base_rev = RunShell(["hg", "parent", "-q"]).split(':')[1].strip() 1057c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1058c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def _GetRelPath(self, filename): 1059c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Get relative path of a file according to the current directory, 1060c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch given its logical path in the repo.""" 1061c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch assert filename.startswith(self.subdir), filename 1062c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return filename[len(self.subdir):].lstrip(r"\/") 1063c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1064c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GenerateDiff(self, extra_args): 1065c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # If no file specified, restrict to the current subdir 1066c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch extra_args = extra_args or ["."] 1067c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args 1068c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch data = RunShell(cmd, silent_ok=True) 1069c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch svndiff = [] 1070c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filecount = 0 1071c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in data.splitlines(): 1072c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch m = re.match("diff --git a/(\S+) b/(\S+)", line) 1073c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if m: 1074c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Modify line to make it look like as it comes from svn diff. 1075c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # With this modification no changes on the server side are required 1076c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # to make upload.py work with Mercurial repos. 1077c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # NOTE: for proper handling of moved/copied files, we have to use 1078c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # the second filename. 1079c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filename = m.group(2) 1080c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch svndiff.append("Index: %s" % filename) 1081c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch svndiff.append("=" * 67) 1082c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filecount += 1 1083c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info(line) 1084c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1085c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch svndiff.append(line) 1086c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not filecount: 1087c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("No valid patches found in output from hg diff") 1088c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return "\n".join(svndiff) + "\n" 1089c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1090c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetUnknownFiles(self): 1091c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Return a list of files unknown to the VCS.""" 1092c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch args = [] 1093c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."], 1094c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch silent_ok=True) 1095c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch unknown_files = [] 1096c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in status.splitlines(): 1097c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch st, fn = line.split(" ", 1) 1098c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if st == "?": 1099c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch unknown_files.append(fn) 1100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return unknown_files 1101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch def GetBaseFile(self, filename): 1103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # "hg status" and "hg cat" both take a path relative to the current subdir 1104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # rather than to the repo root, but "hg diff" has given us the full path 1105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # to the repo root. 1106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = "" 1107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content = None 1108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch is_binary = False 1109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch oldrelpath = relpath = self._GetRelPath(filename) 1110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # "hg status -C" returns two lines for moved/copied files, one otherwise 1111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath]) 1112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch out = out.splitlines() 1113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # HACK: strip error message about missing file/directory if it isn't in 1114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # the working copy 1115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if out[0].startswith('%s: ' % relpath): 1116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch out = out[1:] 1117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if len(out) > 1: 1118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Moved/copied => considered as modified, use old filename to 1119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # retrieve base contents 1120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch oldrelpath = out[1].strip() 1121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status = "M" 1122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status, _ = out[0].split(' ', 1) 1124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if status != "A": 1125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = RunShell(["hg", "cat", "-r", self.base_rev, oldrelpath], 1126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch silent_ok=True) 1127c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch is_binary = "\0" in base_content # Mercurial's heuristic 1128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if status != "R": 1129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content = open(relpath, "rb").read() 1130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch is_binary = is_binary or "\0" in new_content 1131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if is_binary and base_content: 1132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Fetch again without converting newlines 1133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_content = RunShell(["hg", "cat", "-r", self.base_rev, oldrelpath], 1134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch silent_ok=True, universal_newlines=False) 1135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not is_binary or not self.IsImage(relpath): 1136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_content = None 1137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return base_content, new_content, is_binary, status 1138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch# NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync. 1141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef SplitPatch(data): 1142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Splits a patch into separate pieces for each file. 1143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 1145c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch data: A string containing the output of svn diff. 1146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 1148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch A list of 2-tuple (filename, text) where text is the svn diff output 1149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch pertaining to filename. 1150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 1151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patches = [] 1152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filename = None 1153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch diff = [] 1154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for line in data.splitlines(True): 1155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_filename = None 1156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if line.startswith('Index:'): 1157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch unused, new_filename = line.split(':', 1) 1158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_filename = new_filename.strip() 1159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif line.startswith('Property changes on:'): 1160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch unused, temp_filename = line.split(':', 1) 1161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # When a file is modified, paths use '/' between directories, however 1162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # when a property is modified '\' is used on Windows. Make them the same 1163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # otherwise the file shows up twice. 1164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch temp_filename = temp_filename.strip().replace('\\', '/') 1165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if temp_filename != filename: 1166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # File has property changes but no modifications, create a new diff. 1167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch new_filename = temp_filename 1168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if new_filename: 1169c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if filename and diff: 1170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patches.append((filename, ''.join(diff))) 1171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filename = new_filename 1172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch diff = [line] 1173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch continue 1174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if diff is not None: 1175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch diff.append(line) 1176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if filename and diff: 1177c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patches.append((filename, ''.join(diff))) 1178c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return patches 1179c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1180c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1181c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef UploadSeparatePatches(issue, rpc_server, patchset, data, options): 1182c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Uploads a separate patch for each file in the diff output. 1183c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1184c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns a list of [patch_key, filename] for each file. 1185c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 1186c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patches = SplitPatch(data) 1187c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch rv = [] 1188c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for patch in patches: 1189c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if len(patch[1]) > MAX_UPLOAD_SIZE: 1190c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print ("Not uploading the patch for " + patch[0] + 1191c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch " because the file is too large.") 1192c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch continue 1193c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields = [("filename", patch[0])] 1194c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not options.download_base: 1195c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("content_upload", "1")) 1196c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch files = [("data", "data.diff", patch[1])] 1197c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ctype, body = EncodeMultipartFormData(form_fields, files) 1198c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch url = "/%d/upload_patch/%d" % (int(issue), int(patchset)) 1199c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print "Uploading patch for " + patch[0] 1200c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_body = rpc_server.Send(url, body, content_type=ctype) 1201c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines = response_body.splitlines() 1202c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not lines or lines[0] != "OK": 1203c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch StatusUpdate(" --> %s" % response_body) 1204c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch sys.exit(1) 1205c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch rv.append([lines[1], patch[0]]) 1206c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return rv 1207c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1208c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1209c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef GuessVCS(options): 1210c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """Helper to guess the version control system. 1211c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1212c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch This examines the current directory, guesses which VersionControlSystem 1213c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch we're using, and returns an instance of the appropriate class. Exit with an 1214c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch error if we can't figure it out. 1215c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1216c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 1217c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch A VersionControlSystem instance. Exits if the VCS can't be guessed. 1218c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 1219c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Mercurial has a command to get the base directory of a repository 1220c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Try running it, but don't die if we don't have hg installed. 1221c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. 1222c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 1223c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch out, returncode = RunShellWithReturnCode(["hg", "root"]) 1224c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if returncode == 0: 1225c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return MercurialVCS(options, out.strip()) 1226c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except OSError, (errno, message): 1227c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if errno != 2: # ENOENT -- they don't have hg installed. 1228c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise 1229c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1230c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Subversion has a .svn in all working directories. 1231c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if os.path.isdir('.svn'): 1232c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Guessed VCS = Subversion") 1233c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return SubversionVCS(options) 1234c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1235c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Git has a command to test if you're in a git tree. 1236c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Try running it, but don't die if we don't have git installed. 1237c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 1238c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch out, returncode = RunShellWithReturnCode(["git", "rev-parse", 1239c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "--is-inside-work-tree"]) 1240c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if returncode == 0: 1241c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return GitVCS(options) 1242c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except OSError, (errno, message): 1243c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if errno != 2: # ENOENT -- they don't have git installed. 1244c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raise 1245c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1246c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit(("Could not guess version control system. " 1247c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "Are you in a working copy directory?")) 1248c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1249c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1250c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef RealMain(argv, data=None): 1251c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """The real main function. 1252c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1253c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Args: 1254c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch argv: Command line arguments. 1255c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch data: Diff contents. If None (default) the diff is generated by 1256c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch the VersionControlSystem implementation returned by GuessVCS(). 1257c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1258c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Returns: 1259c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch A 2-tuple (issue id, patchset id). 1260c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch The patchset id is None if the base files are not uploaded by this 1261c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch script (applies only to SVN checkouts). 1262c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch """ 1263c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" 1264c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "%(lineno)s %(message)s ")) 1265c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch os.environ['LC_ALL'] = 'C' 1266c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch options, args = parser.parse_args(argv[1:]) 1267c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch global verbosity 1268c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch verbosity = options.verbose 1269c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if verbosity >= 3: 1270c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.getLogger().setLevel(logging.DEBUG) 1271c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch elif verbosity >= 2: 1272c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.getLogger().setLevel(logging.INFO) 1273c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch vcs = GuessVCS(options) 1274c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if isinstance(vcs, SubversionVCS): 1275c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # base field is only allowed for Subversion. 1276c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Note: Fetching base files may become deprecated in future releases. 1277c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base = vcs.GuessBase(options.download_base) 1278c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1279c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base = None 1280c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not base and options.download_base: 1281c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch options.download_base = True 1282c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch logging.info("Enabled upload of base file") 1283c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not options.assume_yes: 1284c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch vcs.CheckForUnknownFiles() 1285c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if data is None: 1286c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch data = vcs.GenerateDiff(args) 1287c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch files = vcs.GetBaseFiles(data) 1288c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if verbosity >= 1: 1289c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print "Upload server:", options.server, "(change with -s/--server)" 1290c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.issue: 1291c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch prompt = "Message describing this patch set: " 1292c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1293c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch prompt = "New issue subject: " 1294c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch message = options.message or raw_input(prompt).strip() 1295c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not message: 1296c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("A non-empty message is required") 1297c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch rpc_server = GetRpcServer(options) 1298c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields = [("subject", message)] 1299c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if base: 1300c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("base", base)) 1301c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.issue: 1302c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("issue", str(options.issue))) 1303c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.email: 1304c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("user", options.email)) 1305c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.reviewers: 1306c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for reviewer in options.reviewers.split(','): 1307c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if "@" in reviewer and not reviewer.split("@")[1].count(".") == 1: 1308c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Invalid email address: %s" % reviewer) 1309c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("reviewers", options.reviewers)) 1310c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.cc: 1311c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for cc in options.cc.split(','): 1312c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if "@" in cc and not cc.split("@")[1].count(".") == 1: 1313c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Invalid email address: %s" % cc) 1314c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("cc", options.cc)) 1315c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch description = options.description 1316c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.description_file: 1317c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.description: 1318c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ErrorExit("Can't specify description and description_file") 1319c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file = open(options.description_file, 'r') 1320c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch description = file.read() 1321c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch file.close() 1322c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if description: 1323c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("description", description)) 1324c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # Send a hash of all the base file so the server can determine if a copy 1325c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # already exists in an earlier patchset. 1326c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_hashes = "" 1327c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for file, info in files.iteritems(): 1328c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not info[0] is None: 1329c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch checksum = md5.new(info[0]).hexdigest() 1330c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if base_hashes: 1331c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_hashes += "|" 1332c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base_hashes += checksum + ":" + file 1333c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("base_hashes", base_hashes)) 1334c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # If we're uploading base files, don't send the email before the uploads, so 1335c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch # that it contains the file status. 1336c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.send_mail and options.download_base: 1337c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("send_mail", "1")) 1338c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not options.download_base: 1339c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("content_upload", "1")) 1340c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if len(data) > MAX_UPLOAD_SIZE: 1341c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print "Patch is large, so uploading file patches separately." 1342c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch uploaded_diff_file = [] 1343c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch form_fields.append(("separate_patches", "1")) 1344c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1345c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch uploaded_diff_file = [("data", "data.diff", data)] 1346c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file) 1347c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_body = rpc_server.Send("/upload", body, content_type=ctype) 1348c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patchset = None 1349c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not options.download_base or not uploaded_diff_file: 1350c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch lines = response_body.splitlines() 1351c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if len(lines) >= 2: 1352c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch msg = lines[0] 1353c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patchset = lines[1].strip() 1354c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patches = [x.split(" ", 1) for x in lines[2:]] 1355c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1356c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch msg = response_body 1357c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else: 1358c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch msg = response_body 1359c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch StatusUpdate(msg) 1360c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not response_body.startswith("Issue created.") and \ 1361c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch not response_body.startswith("Issue updated."): 1362c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch sys.exit(0) 1363c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch issue = msg[msg.rfind("/")+1:] 1364c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1365c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not uploaded_diff_file: 1366c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch result = UploadSeparatePatches(issue, rpc_server, patchset, data, options) 1367c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not options.download_base: 1368c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch patches = result 1369c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1370c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if not options.download_base: 1371c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files) 1372c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if options.send_mail: 1373c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch rpc_server.Send("/" + issue + "/mail", payload="") 1374c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return issue, patchset 1375c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1376c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1377c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochdef main(): 1378c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch try: 1379c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch RealMain(sys.argv) 1380c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch except KeyboardInterrupt: 1381c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch print 1382c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch StatusUpdate("Interrupted.") 1383c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch sys.exit(1) 1384c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1385c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1386c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochif __name__ == "__main__": 1387c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch main() 1388