Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(386)

Side by Side Diff: git_cl/upload.py

Issue 6730004: Update upload.py to r680 from upstream. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | third_party/upload.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2007 Google Inc. 3 # Copyright 2007 Google Inc.
4 # 4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License. 6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at 7 # You may obtain a copy of the License at
8 # 8 #
9 # http://www.apache.org/licenses/LICENSE-2.0 9 # http://www.apache.org/licenses/LICENSE-2.0
10 # 10 #
11 # Unless required by applicable law or agreed to in writing, software 11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, 12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and 14 # See the License for the specific language governing permissions and
15 # limitations under the License. 15 # limitations under the License.
16 16
17 """Tool for uploading diffs from a version control system to the codereview app. 17 """Tool for uploading diffs from a version control system to the codereview app.
18 18
19 Usage summary: upload.py [options] [-- diff_options] [path...] 19 Usage summary: upload.py [options] [-- diff_options] [path...]
20 20
21 Diff options are passed to the diff command of the underlying system. 21 Diff options are passed to the diff command of the underlying system.
22 22
23 Supported version control systems: 23 Supported version control systems:
24 Git 24 Git
25 Mercurial 25 Mercurial
26 Subversion 26 Subversion
27 Perforce
28 CVS
27 29
28 It is important for Git/Mercurial users to specify a tree/node/branch to diff 30 It is important for Git/Mercurial users to specify a tree/node/branch to diff
29 against by using the '--rev' option. 31 against by using the '--rev' option.
30 """ 32 """
31 # This code is derived from appcfg.py in the App Engine SDK (open source), 33 # This code is derived from appcfg.py in the App Engine SDK (open source),
32 # and from ASPN recipe #146306. 34 # and from ASPN recipe #146306.
33 35
34 import ConfigParser 36 import ConfigParser
35 import cookielib 37 import cookielib
36 import fnmatch 38 import fnmatch
37 import getpass 39 import getpass
38 import logging 40 import logging
41 import marshal
39 import mimetypes 42 import mimetypes
40 import optparse 43 import optparse
41 import os 44 import os
42 import re 45 import re
43 import socket 46 import socket
44 import subprocess 47 import subprocess
45 import sys 48 import sys
46 import urllib 49 import urllib
47 import urllib2 50 import urllib2
48 import urlparse 51 import urlparse
(...skipping 30 matching lines...) Expand all
79 # changed by the review server (see handler for upload.py). 82 # changed by the review server (see handler for upload.py).
80 DEFAULT_REVIEW_SERVER = "codereview.appspot.com" 83 DEFAULT_REVIEW_SERVER = "codereview.appspot.com"
81 84
82 # Max size of patch or base file. 85 # Max size of patch or base file.
83 MAX_UPLOAD_SIZE = 900 * 1024 86 MAX_UPLOAD_SIZE = 900 * 1024
84 87
85 # Constants for version control names. Used by GuessVCSName. 88 # Constants for version control names. Used by GuessVCSName.
86 VCS_GIT = "Git" 89 VCS_GIT = "Git"
87 VCS_MERCURIAL = "Mercurial" 90 VCS_MERCURIAL = "Mercurial"
88 VCS_SUBVERSION = "Subversion" 91 VCS_SUBVERSION = "Subversion"
92 VCS_PERFORCE = "Perforce"
93 VCS_CVS = "CVS"
89 VCS_UNKNOWN = "Unknown" 94 VCS_UNKNOWN = "Unknown"
90 95
91 # whitelist for non-binary filetypes which do not start with "text/" 96 # whitelist for non-binary filetypes which do not start with "text/"
92 # .mm (Objective-C) shows up as application/x-freemind on my Linux box. 97 # .mm (Objective-C) shows up as application/x-freemind on my Linux box.
93 TEXT_MIMETYPES = ['application/javascript', 'application/json', 98 TEXT_MIMETYPES = ['application/javascript', 'application/json',
94 'application/x-javascript', 'application/xml', 99 'application/x-javascript', 'application/xml',
95 'application/x-freemind', 'application/x-sh'] 100 'application/x-freemind', 'application/x-sh']
96 101
97 VCS_ABBREVIATIONS = { 102 VCS_ABBREVIATIONS = {
98 VCS_MERCURIAL.lower(): VCS_MERCURIAL, 103 VCS_MERCURIAL.lower(): VCS_MERCURIAL,
99 "hg": VCS_MERCURIAL, 104 "hg": VCS_MERCURIAL,
100 VCS_SUBVERSION.lower(): VCS_SUBVERSION, 105 VCS_SUBVERSION.lower(): VCS_SUBVERSION,
101 "svn": VCS_SUBVERSION, 106 "svn": VCS_SUBVERSION,
107 VCS_PERFORCE.lower(): VCS_PERFORCE,
108 "p4": VCS_PERFORCE,
102 VCS_GIT.lower(): VCS_GIT, 109 VCS_GIT.lower(): VCS_GIT,
110 VCS_CVS.lower(): VCS_CVS,
103 } 111 }
104 112
105 # The result of parsing Subversion's [auto-props] setting. 113 # The result of parsing Subversion's [auto-props] setting.
106 svn_auto_props_map = None 114 svn_auto_props_map = None
107 115
108 def GetEmail(prompt): 116 def GetEmail(prompt):
109 """Prompts the user for their email address and returns it. 117 """Prompts the user for their email address and returns it.
110 118
111 The last used email address is saved to a file and offered up as a suggestion 119 The last used email address is saved to a file and offered up as a suggestion
112 to the user. If the user presses enter without typing in anything the last 120 to the user. If the user presses enter without typing in anything the last
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
181 save_cookies: If True, save the authentication cookies to local disk. 189 save_cookies: If True, save the authentication cookies to local disk.
182 If False, use an in-memory cookiejar instead. Subclasses must 190 If False, use an in-memory cookiejar instead. Subclasses must
183 implement this functionality. Defaults to False. 191 implement this functionality. Defaults to False.
184 account_type: Account type used for authentication. Defaults to 192 account_type: Account type used for authentication. Defaults to
185 AUTH_ACCOUNT_TYPE. 193 AUTH_ACCOUNT_TYPE.
186 """ 194 """
187 self.host = host 195 self.host = host
188 if (not self.host.startswith("http://") and 196 if (not self.host.startswith("http://") and
189 not self.host.startswith("https://")): 197 not self.host.startswith("https://")):
190 self.host = "http://" + self.host 198 self.host = "http://" + self.host
191 assert re.match(r'^[a-z]+://[a-z0-9\.-_]+(|:[0-9]+)$', self.host), (
192 '%s is malformed' % host)
193 self.host_override = host_override 199 self.host_override = host_override
194 self.auth_function = auth_function 200 self.auth_function = auth_function
195 self.authenticated = False 201 self.authenticated = False
196 self.extra_headers = extra_headers 202 self.extra_headers = extra_headers
197 self.save_cookies = save_cookies 203 self.save_cookies = save_cookies
198 self.account_type = account_type 204 self.account_type = account_type
199 self.opener = self._GetOpener() 205 self.opener = self._GetOpener()
200 if self.host_override: 206 if self.host_override:
201 logging.info("Server: %s; Host: %s", self.host, self.host_override) 207 logging.info("Server: %s; Host: %s", self.host, self.host_override)
202 else: 208 else:
(...skipping 10 matching lines...) Expand all
213 def _CreateRequest(self, url, data=None): 219 def _CreateRequest(self, url, data=None):
214 """Creates a new urllib request.""" 220 """Creates a new urllib request."""
215 logging.debug("Creating request for: '%s' with payload:\n%s", url, data) 221 logging.debug("Creating request for: '%s' with payload:\n%s", url, data)
216 req = urllib2.Request(url, data=data) 222 req = urllib2.Request(url, data=data)
217 if self.host_override: 223 if self.host_override:
218 req.add_header("Host", self.host_override) 224 req.add_header("Host", self.host_override)
219 for key, value in self.extra_headers.iteritems(): 225 for key, value in self.extra_headers.iteritems():
220 req.add_header(key, value) 226 req.add_header(key, value)
221 return req 227 return req
222 228
223 def _GetAuthToken(self, host, email, password): 229 def _GetAuthToken(self, email, password):
224 """Uses ClientLogin to authenticate the user, returning an auth token. 230 """Uses ClientLogin to authenticate the user, returning an auth token.
225 231
226 Args: 232 Args:
227 host: Host to get a token against.
228 email: The user's email address 233 email: The user's email address
229 password: The user's password 234 password: The user's password
230 235
231 Raises: 236 Raises:
232 ClientLoginError: If there was an error authenticating with ClientLogin. 237 ClientLoginError: If there was an error authenticating with ClientLogin.
233 HTTPError: If there was some other form of HTTP error. 238 HTTPError: If there was some other form of HTTP error.
234 239
235 Returns: 240 Returns:
236 The authentication token returned by ClientLogin. 241 The authentication token returned by ClientLogin.
237 """ 242 """
238 account_type = self.account_type 243 account_type = self.account_type
239 if host.endswith(".google.com"): 244 if self.host.endswith(".google.com"):
240 # Needed for use inside Google. 245 # Needed for use inside Google.
241 account_type = "HOSTED" 246 account_type = "HOSTED"
242 req = self._CreateRequest( 247 req = self._CreateRequest(
243 url="https://www.google.com/accounts/ClientLogin", 248 url="https://www.google.com/accounts/ClientLogin",
244 data=urllib.urlencode({ 249 data=urllib.urlencode({
245 "Email": email, 250 "Email": email,
246 "Passwd": password, 251 "Passwd": password,
247 "service": "ah", 252 "service": "ah",
248 "source": "rietveld-codereview-upload", 253 "source": "rietveld-codereview-upload",
249 "accountType": account_type, 254 "accountType": account_type,
250 }), 255 }),
251 ) 256 )
252 try: 257 try:
253 response = self.opener.open(req) 258 response = self.opener.open(req)
254 response_body = response.read() 259 response_body = response.read()
255 response_dict = dict(x.split("=") 260 response_dict = dict(x.split("=")
256 for x in response_body.split("\n") if x) 261 for x in response_body.split("\n") if x)
257 return response_dict["Auth"] 262 return response_dict["Auth"]
258 except urllib2.HTTPError, e: 263 except urllib2.HTTPError, e:
259 if e.code == 403: 264 if e.code == 403:
260 body = e.read() 265 body = e.read()
261 response_dict = dict(x.split("=", 1) for x in body.split("\n") if x) 266 response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
262 raise ClientLoginError(req.get_full_url(), e.code, e.msg, 267 raise ClientLoginError(req.get_full_url(), e.code, e.msg,
263 e.headers, response_dict) 268 e.headers, response_dict)
264 else: 269 else:
265 raise 270 raise
266 271
267 def _GetAuthCookie(self, host, auth_token): 272 def _GetAuthCookie(self, auth_token):
268 """Fetches authentication cookies for an authentication token. 273 """Fetches authentication cookies for an authentication token.
269 274
270 Args: 275 Args:
271 host: The host to get a cookie against. Because of 301, it may be a
272 different host than self.host.
273 auth_token: The authentication token returned by ClientLogin. 276 auth_token: The authentication token returned by ClientLogin.
274 277
275 Raises: 278 Raises:
276 HTTPError: If there was an error fetching the authentication cookies. 279 HTTPError: If there was an error fetching the authentication cookies.
277 """ 280 """
278 # This is a dummy value to allow us to identify when we're successful. 281 # This is a dummy value to allow us to identify when we're successful.
279 continue_location = "http://localhost/" 282 continue_location = "http://localhost/"
280 args = {"continue": continue_location, "auth": auth_token} 283 args = {"continue": continue_location, "auth": auth_token}
281 tries = 0 284 req = self._CreateRequest("%s/_ah/login?%s" %
282 url = "%s/_ah/login?%s" % (host, urllib.urlencode(args)) 285 (self.host, urllib.urlencode(args)))
283 while tries < 3: 286 try:
284 tries += 1 287 response = self.opener.open(req)
285 req = self._CreateRequest(url) 288 except urllib2.HTTPError, e:
286 try: 289 response = e
287 response = self.opener.open(req)
288 except urllib2.HTTPError, e:
289 response = e
290 if e.code == 301:
291 # Handle permanent redirect manually.
292 url = e.info()["location"]
293 continue
294 break
295 if (response.code != 302 or 290 if (response.code != 302 or
296 response.info()["location"] != continue_location): 291 response.info()["location"] != continue_location):
297 raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, 292 raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg,
298 response.headers, response.fp) 293 response.headers, response.fp)
299 self.authenticated = True 294 self.authenticated = True
300 295
301 def _Authenticate(self, host): 296 def _Authenticate(self):
302 """Authenticates the user. 297 """Authenticates the user.
303 298
304 Args:
305 host: The host to get a cookie against. Because of 301, it may be a
306 different host than self.host.
307
308 The authentication process works as follows: 299 The authentication process works as follows:
309 1) We get a username and password from the user 300 1) We get a username and password from the user
310 2) We use ClientLogin to obtain an AUTH token for the user 301 2) We use ClientLogin to obtain an AUTH token for the user
311 (see http://code.google.com/apis/accounts/AuthForInstalledApps.html). 302 (see http://code.google.com/apis/accounts/AuthForInstalledApps.html).
312 3) We pass the auth token to /_ah/login on the server to obtain an 303 3) We pass the auth token to /_ah/login on the server to obtain an
313 authentication cookie. If login was successful, it tries to redirect 304 authentication cookie. If login was successful, it tries to redirect
314 us to the URL we provided. 305 us to the URL we provided.
315 306
316 If we attempt to access the upload API without first obtaining an 307 If we attempt to access the upload API without first obtaining an
317 authentication cookie, it returns a 401 response (or a 302) and 308 authentication cookie, it returns a 401 response (or a 302) and
318 directs us to authenticate ourselves with ClientLogin. 309 directs us to authenticate ourselves with ClientLogin.
319 """ 310 """
320 for i in range(3): 311 for i in range(3):
321 credentials = self.auth_function() 312 credentials = self.auth_function()
322 try: 313 try:
323 auth_token = self._GetAuthToken(host, credentials[0], credentials[1]) 314 auth_token = self._GetAuthToken(credentials[0], credentials[1])
324 except ClientLoginError, e: 315 except ClientLoginError, e:
325 if e.reason == "BadAuthentication": 316 if e.reason == "BadAuthentication":
326 print >>sys.stderr, "Invalid username or password." 317 print >>sys.stderr, "Invalid username or password."
327 continue 318 continue
328 if e.reason == "CaptchaRequired": 319 if e.reason == "CaptchaRequired":
329 print >>sys.stderr, ( 320 print >>sys.stderr, (
330 "Please go to\n" 321 "Please go to\n"
331 "https://www.google.com/accounts/DisplayUnlockCaptcha\n" 322 "https://www.google.com/accounts/DisplayUnlockCaptcha\n"
332 "and verify you are a human. Then try again.\n" 323 "and verify you are a human. Then try again.\n"
333 "If you are using a Google Apps account the URL is:\n" 324 "If you are using a Google Apps account the URL is:\n"
(...skipping 12 matching lines...) Expand all
346 print >>sys.stderr, "The user account has been disabled." 337 print >>sys.stderr, "The user account has been disabled."
347 break 338 break
348 if e.reason == "ServiceDisabled": 339 if e.reason == "ServiceDisabled":
349 print >>sys.stderr, ("The user's access to the service has been " 340 print >>sys.stderr, ("The user's access to the service has been "
350 "disabled.") 341 "disabled.")
351 break 342 break
352 if e.reason == "ServiceUnavailable": 343 if e.reason == "ServiceUnavailable":
353 print >>sys.stderr, "The service is not available; try again later." 344 print >>sys.stderr, "The service is not available; try again later."
354 break 345 break
355 raise 346 raise
356 self._GetAuthCookie(host, auth_token) 347 self._GetAuthCookie(auth_token)
357 return 348 return
358 349
359 def Send(self, request_path, payload=None, 350 def Send(self, request_path, payload=None,
360 content_type="application/octet-stream", 351 content_type="application/octet-stream",
361 timeout=None, 352 timeout=None,
362 extra_headers=None, 353 extra_headers=None,
363 **kwargs): 354 **kwargs):
364 """Sends an RPC and returns the response. 355 """Sends an RPC and returns the response.
365 356
366 Args: 357 Args:
367 request_path: The path to send the request to, eg /api/appversion/create. 358 request_path: The path to send the request to, eg /api/appversion/create.
368 payload: The body of the request, or None to send an empty request. 359 payload: The body of the request, or None to send an empty request.
369 content_type: The Content-Type header to use. 360 content_type: The Content-Type header to use.
370 timeout: timeout in seconds; default None i.e. no timeout. 361 timeout: timeout in seconds; default None i.e. no timeout.
371 (Note: for large requests on OS X, the timeout doesn't work right.) 362 (Note: for large requests on OS X, the timeout doesn't work right.)
372 extra_headers: Dict containing additional HTTP headers that should be 363 extra_headers: Dict containing additional HTTP headers that should be
373 included in the request (string header names mapped to their values), 364 included in the request (string header names mapped to their values),
374 or None to not include any additional headers. 365 or None to not include any additional headers.
375 kwargs: Any keyword arguments are converted into query string parameters. 366 kwargs: Any keyword arguments are converted into query string parameters.
376 367
377 Returns: 368 Returns:
378 The response body, as a string. 369 The response body, as a string.
379 """ 370 """
380 # TODO: Don't require authentication. Let the server say 371 # TODO: Don't require authentication. Let the server say
381 # whether it is necessary. 372 # whether it is necessary.
382 if not self.authenticated: 373 if not self.authenticated:
383 self._Authenticate(self.host) 374 self._Authenticate()
384 375
385 old_timeout = socket.getdefaulttimeout() 376 old_timeout = socket.getdefaulttimeout()
386 socket.setdefaulttimeout(timeout) 377 socket.setdefaulttimeout(timeout)
387 try: 378 try:
388 tries = 0 379 tries = 0
389 args = dict(kwargs)
390 url = "%s%s" % (self.host, request_path)
391 if args:
392 url += "?" + urllib.urlencode(args)
393 while True: 380 while True:
394 tries += 1 381 tries += 1
382 args = dict(kwargs)
383 url = "%s%s" % (self.host, request_path)
384 if args:
385 url += "?" + urllib.urlencode(args)
395 req = self._CreateRequest(url=url, data=payload) 386 req = self._CreateRequest(url=url, data=payload)
396 req.add_header("Content-Type", content_type) 387 req.add_header("Content-Type", content_type)
397 if extra_headers: 388 if extra_headers:
398 for header, value in extra_headers.items(): 389 for header, value in extra_headers.items():
399 req.add_header(header, value) 390 req.add_header(header, value)
400 try: 391 try:
401 f = self.opener.open(req) 392 f = self.opener.open(req)
402 response = f.read() 393 response = f.read()
403 f.close() 394 f.close()
404 return response 395 return response
405 except urllib2.HTTPError, e: 396 except urllib2.HTTPError, e:
406 if tries > 3: 397 if tries > 3:
407 raise 398 raise
408 elif e.code == 401 or e.code == 302: 399 elif e.code == 401 or e.code == 302:
409 url_loc = urlparse.urlparse(url) 400 self._Authenticate()
410 self._Authenticate('%s://%s' % (url_loc[0], url_loc[1]))
411 ## elif e.code >= 500 and e.code < 600: 401 ## elif e.code >= 500 and e.code < 600:
412 ## # Server Error - try again. 402 ## # Server Error - try again.
413 ## continue 403 ## continue
414 elif e.code == 301: 404 elif e.code == 301:
415 # Handle permanent redirect manually. 405 # Handle permanent redirect manually.
416 url = e.info()["location"] 406 url = e.info()["location"]
407 url_loc = urlparse.urlparse(url)
408 self.host = '%s://%s' % (url_loc[0], url_loc[1])
417 else: 409 else:
418 raise 410 raise
419 except urllib2.URLError, e:
420 reason = getattr(e, 'reason', None)
421 if isinstance(reason, str) and reason.find("110") != -1:
422 # Connection timeout error.
423 if tries <= 3:
424 # Try again.
425 continue
426 raise
427 finally: 411 finally:
428 socket.setdefaulttimeout(old_timeout) 412 socket.setdefaulttimeout(old_timeout)
429 413
430 414
431 class HttpRpcServer(AbstractRpcServer): 415 class HttpRpcServer(AbstractRpcServer):
432 """Provides a simplified RPC-style interface for HTTP requests.""" 416 """Provides a simplified RPC-style interface for HTTP requests."""
433 417
434 def _Authenticate(self, *args): 418 def _Authenticate(self):
435 """Save the cookie jar after authentication.""" 419 """Save the cookie jar after authentication."""
436 super(HttpRpcServer, self)._Authenticate(*args) 420 super(HttpRpcServer, self)._Authenticate()
437 if self.save_cookies: 421 if self.save_cookies:
438 StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) 422 StatusUpdate("Saving authentication cookies to %s" % self.cookie_file)
439 self.cookie_jar.save() 423 self.cookie_jar.save()
440 424
441 def _GetOpener(self): 425 def _GetOpener(self):
442 """Returns an OpenerDirector that supports cookies and ignores redirects. 426 """Returns an OpenerDirector that supports cookies and ignores redirects.
443 427
444 Returns: 428 Returns:
445 A urllib2.OpenerDirector object. 429 A urllib2.OpenerDirector object.
446 """ 430 """
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
483 help="Assume that the answer to yes/no questions is 'yes'.") 467 help="Assume that the answer to yes/no questions is 'yes'.")
484 # Logging 468 # Logging
485 group = parser.add_option_group("Logging options") 469 group = parser.add_option_group("Logging options")
486 group.add_option("-q", "--quiet", action="store_const", const=0, 470 group.add_option("-q", "--quiet", action="store_const", const=0,
487 dest="verbose", help="Print errors only.") 471 dest="verbose", help="Print errors only.")
488 group.add_option("-v", "--verbose", action="store_const", const=2, 472 group.add_option("-v", "--verbose", action="store_const", const=2,
489 dest="verbose", default=1, 473 dest="verbose", default=1,
490 help="Print info level logs.") 474 help="Print info level logs.")
491 group.add_option("--noisy", action="store_const", const=3, 475 group.add_option("--noisy", action="store_const", const=3,
492 dest="verbose", help="Print all logs.") 476 dest="verbose", help="Print all logs.")
477 group.add_option("--print_diffs", dest="print_diffs", action="store_true",
478 help="Print full diffs.")
493 # Review server 479 # Review server
494 group = parser.add_option_group("Review server options") 480 group = parser.add_option_group("Review server options")
495 group.add_option("-s", "--server", action="store", dest="server", 481 group.add_option("-s", "--server", action="store", dest="server",
496 default=DEFAULT_REVIEW_SERVER, 482 default=DEFAULT_REVIEW_SERVER,
497 metavar="SERVER", 483 metavar="SERVER",
498 help=("The server to upload to. The format is host[:port]. " 484 help=("The server to upload to. The format is host[:port]. "
499 "Defaults to '%default'.")) 485 "Defaults to '%default'."))
500 group.add_option("-e", "--email", action="store", dest="email", 486 group.add_option("-e", "--email", action="store", dest="email",
501 metavar="EMAIL", default=None, 487 metavar="EMAIL", default=None,
502 help="The username to use. Will prompt if omitted.") 488 help="The username to use. Will prompt if omitted.")
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
555 group.add_option("--send_mail", action="store_true", 541 group.add_option("--send_mail", action="store_true",
556 dest="send_mail", default=False, 542 dest="send_mail", default=False,
557 help="Send notification email to reviewers.") 543 help="Send notification email to reviewers.")
558 group.add_option("--vcs", action="store", dest="vcs", 544 group.add_option("--vcs", action="store", dest="vcs",
559 metavar="VCS", default=None, 545 metavar="VCS", default=None,
560 help=("Version control system (optional, usually upload.py " 546 help=("Version control system (optional, usually upload.py "
561 "already guesses the right VCS).")) 547 "already guesses the right VCS)."))
562 group.add_option("--emulate_svn_auto_props", action="store_true", 548 group.add_option("--emulate_svn_auto_props", action="store_true",
563 dest="emulate_svn_auto_props", default=False, 549 dest="emulate_svn_auto_props", default=False,
564 help=("Emulate Subversion's auto properties feature.")) 550 help=("Emulate Subversion's auto properties feature."))
565 551 # Perforce-specific
552 group = parser.add_option_group("Perforce-specific options "
553 "(overrides P4 environment variables)")
554 group.add_option("--p4_port", action="store", dest="p4_port",
555 metavar="P4_PORT", default=None,
556 help=("Perforce server and port (optional)"))
557 group.add_option("--p4_changelist", action="store", dest="p4_changelist",
558 metavar="P4_CHANGELIST", default=None,
559 help=("Perforce changelist id"))
560 group.add_option("--p4_client", action="store", dest="p4_client",
561 metavar="P4_CLIENT", default=None,
562 help=("Perforce client/workspace"))
563 group.add_option("--p4_user", action="store", dest="p4_user",
564 metavar="P4_USER", default=None,
565 help=("Perforce user"))
566 566
567 def GetRpcServer(server, email=None, host_override=None, save_cookies=True, 567 def GetRpcServer(server, email=None, host_override=None, save_cookies=True,
568 account_type=AUTH_ACCOUNT_TYPE): 568 account_type=AUTH_ACCOUNT_TYPE):
569 """Returns an instance of an AbstractRpcServer. 569 """Returns an instance of an AbstractRpcServer.
570 570
571 Args: 571 Args:
572 server: String containing the review server URL. 572 server: String containing the review server URL.
573 email: String containing user's email address. 573 email: String containing user's email address.
574 host_override: If not None, string containing an alternate hostname to use 574 host_override: If not None, string containing an alternate hostname to use
575 in the host header. 575 in the host header.
(...skipping 325 matching lines...) Expand 10 before | Expand all | Expand 10 after
901 901
902 Args: 902 Args:
903 required: If true, exits if the url can't be guessed, otherwise None is 903 required: If true, exits if the url can't be guessed, otherwise None is
904 returned. 904 returned.
905 """ 905 """
906 info = RunShell(["svn", "info"]) 906 info = RunShell(["svn", "info"])
907 for line in info.splitlines(): 907 for line in info.splitlines():
908 if line.startswith("URL: "): 908 if line.startswith("URL: "):
909 url = line.split()[1] 909 url = line.split()[1]
910 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) 910 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
911 username, netloc = urllib.splituser(netloc)
912 if username:
913 logging.info("Removed username from base URL")
914 guess = "" 911 guess = ""
915 if netloc == "svn.python.org" and scheme == "svn+ssh": 912 if netloc == "svn.python.org" and scheme == "svn+ssh":
916 path = "projects" + path 913 path = "projects" + path
917 scheme = "http" 914 scheme = "http"
918 guess = "Python " 915 guess = "Python "
919 elif netloc.endswith(".googlecode.com"): 916 elif netloc.endswith(".googlecode.com"):
920 scheme = "http" 917 scheme = "http"
921 guess = "Google Code " 918 guess = "Google Code "
922 path = path + "/" 919 path = path + "/"
923 base = urlparse.urlunparse((scheme, netloc, path, params, 920 base = urlparse.urlunparse((scheme, netloc, path, params,
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after
1063 else: 1060 else:
1064 # Don't change filename, it's needed later. 1061 # Don't change filename, it's needed later.
1065 url = filename 1062 url = filename
1066 args += ["-r", "BASE"] 1063 args += ["-r", "BASE"]
1067 cmd = ["svn"] + args + ["propget", "svn:mime-type", url] 1064 cmd = ["svn"] + args + ["propget", "svn:mime-type", url]
1068 mimetype, returncode = RunShellWithReturnCode(cmd) 1065 mimetype, returncode = RunShellWithReturnCode(cmd)
1069 if returncode: 1066 if returncode:
1070 # File does not exist in the requested revision. 1067 # File does not exist in the requested revision.
1071 # Reset mimetype, it contains an error message. 1068 # Reset mimetype, it contains an error message.
1072 mimetype = "" 1069 mimetype = ""
1070 else:
1071 mimetype = mimetype.strip()
1073 get_base = False 1072 get_base = False
1074 is_binary = bool(mimetype) and not mimetype.startswith("text/") 1073 is_binary = (bool(mimetype) and
1074 not mimetype.startswith("text/") and
1075 not mimetype in TEXT_MIMETYPES)
1075 if status[0] == " ": 1076 if status[0] == " ":
1076 # Empty base content just to force an upload. 1077 # Empty base content just to force an upload.
1077 base_content = "" 1078 base_content = ""
1078 elif is_binary: 1079 elif is_binary:
1079 if self.IsImage(filename): 1080 if self.IsImage(filename):
1080 get_base = True 1081 get_base = True
1081 if status[0] == "M": 1082 if status[0] == "M":
1082 if not self.rev_end: 1083 if not self.rev_end:
1083 new_content = self.ReadFile(filename) 1084 new_content = self.ReadFile(filename)
1084 else: 1085 else:
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after
1258 if base_content is None and hash_before: 1259 if base_content is None and hash_before:
1259 base_content = self.GetFileContent(hash_before, is_binary) 1260 base_content = self.GetFileContent(hash_before, is_binary)
1260 # Only include the "after" file if it's an image; otherwise it 1261 # Only include the "after" file if it's an image; otherwise it
1261 # it is reconstructed from the diff. 1262 # it is reconstructed from the diff.
1262 if is_image and hash_after: 1263 if is_image and hash_after:
1263 new_content = self.GetFileContent(hash_after, is_binary) 1264 new_content = self.GetFileContent(hash_after, is_binary)
1264 1265
1265 return (base_content, new_content, is_binary, status) 1266 return (base_content, new_content, is_binary, status)
1266 1267
1267 1268
1269 class CVSVCS(VersionControlSystem):
1270 """Implementation of the VersionControlSystem interface for CVS."""
1271
1272 def __init__(self, options):
1273 super(CVSVCS, self).__init__(options)
1274
1275 def GetOriginalContent_(self, filename):
1276 RunShell(["cvs", "up", filename], silent_ok=True)
1277 # TODO need detect file content encoding
1278 content = open(filename).read()
1279 return content.replace("\r\n", "\n")
1280
1281 def GetBaseFile(self, filename):
1282 base_content = None
1283 new_content = None
1284 is_binary = False
1285 status = "A"
1286
1287 output, retcode = RunShellWithReturnCode(["cvs", "status", filename])
1288 if retcode:
1289 ErrorExit("Got error status from 'cvs status %s'" % filename)
1290
1291 if output.find("Status: Locally Modified") != -1:
1292 status = "M"
1293 temp_filename = "%s.tmp123" % filename
1294 os.rename(filename, temp_filename)
1295 base_content = self.GetOriginalContent_(filename)
1296 os.rename(temp_filename, filename)
1297 elif output.find("Status: Locally Added"):
1298 status = "A"
1299 base_content = ""
1300 elif output.find("Status: Needs Checkout"):
1301 status = "D"
1302 base_content = self.GetOriginalContent_(filename)
1303
1304 return (base_content, new_content, is_binary, status)
1305
1306 def GenerateDiff(self, extra_args):
1307 cmd = ["cvs", "diff", "-u", "-N"]
1308 if self.options.revision:
1309 cmd += ["-r", self.options.revision]
1310
1311 cmd.extend(extra_args)
1312 data, retcode = RunShellWithReturnCode(cmd)
1313 count = 0
1314 if retcode == 0:
1315 for line in data.splitlines():
1316 if line.startswith("Index:"):
1317 count += 1
1318 logging.info(line)
1319
1320 if not count:
1321 ErrorExit("No valid patches found in output from cvs diff")
1322
1323 return data
1324
1325 def GetUnknownFiles(self):
1326 status = RunShell(["cvs", "diff"],
1327 silent_ok=True)
1328 unknown_files = []
1329 for line in status.split("\n"):
1330 if line and line[0] == "?":
1331 unknown_files.append(line)
1332 return unknown_files
1333
1268 class MercurialVCS(VersionControlSystem): 1334 class MercurialVCS(VersionControlSystem):
1269 """Implementation of the VersionControlSystem interface for Mercurial.""" 1335 """Implementation of the VersionControlSystem interface for Mercurial."""
1270 1336
1271 def __init__(self, options, repo_dir): 1337 def __init__(self, options, repo_dir):
1272 super(MercurialVCS, self).__init__(options) 1338 super(MercurialVCS, self).__init__(options)
1273 # Absolute path to repository (we can be in a subdir) 1339 # Absolute path to repository (we can be in a subdir)
1274 self.repo_dir = os.path.normpath(repo_dir) 1340 self.repo_dir = os.path.normpath(repo_dir)
1275 # Compute the subdir 1341 # Compute the subdir
1276 cwd = os.path.normpath(os.getcwd()) 1342 cwd = os.path.normpath(os.getcwd())
1277 assert cwd.startswith(self.repo_dir) 1343 assert cwd.startswith(self.repo_dir)
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
1357 is_binary = is_binary or "\0" in new_content 1423 is_binary = is_binary or "\0" in new_content
1358 if is_binary and base_content: 1424 if is_binary and base_content:
1359 # Fetch again without converting newlines 1425 # Fetch again without converting newlines
1360 base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], 1426 base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
1361 silent_ok=True, universal_newlines=False) 1427 silent_ok=True, universal_newlines=False)
1362 if not is_binary or not self.IsImage(relpath): 1428 if not is_binary or not self.IsImage(relpath):
1363 new_content = None 1429 new_content = None
1364 return base_content, new_content, is_binary, status 1430 return base_content, new_content, is_binary, status
1365 1431
1366 1432
1433 class PerforceVCS(VersionControlSystem):
1434 """Implementation of the VersionControlSystem interface for Perforce."""
1435
1436 def __init__(self, options):
1437
1438 def ConfirmLogin():
1439 # Make sure we have a valid perforce session
1440 while True:
1441 data, retcode = self.RunPerforceCommandWithReturnCode(
1442 ["login", "-s"], marshal_output=True)
1443 if not data:
1444 ErrorExit("Error checking perforce login")
1445 if not retcode and (not "code" in data or data["code"] != "error"):
1446 break
1447 print "Enter perforce password: "
1448 self.RunPerforceCommandWithReturnCode(["login"])
1449
1450 super(PerforceVCS, self).__init__(options)
1451
1452 self.p4_changelist = options.p4_changelist
1453 if not self.p4_changelist:
1454 ErrorExit("A changelist id is required")
1455 if (options.revision):
1456 ErrorExit("--rev is not supported for perforce")
1457
1458 self.p4_port = options.p4_port
1459 self.p4_client = options.p4_client
1460 self.p4_user = options.p4_user
1461
1462 ConfirmLogin()
1463
1464 if not options.message:
1465 description = self.RunPerforceCommand(["describe", self.p4_changelist],
1466 marshal_output=True)
1467 if description and "desc" in description:
1468 # Rietveld doesn't support multi-line descriptions
1469 raw_message = description["desc"].strip()
1470 lines = raw_message.splitlines()
1471 if len(lines):
1472 options.message = lines[0]
1473
1474 def RunPerforceCommandWithReturnCode(self, extra_args, marshal_output=False,
1475 universal_newlines=True):
1476 args = ["p4"]
1477 if marshal_output:
1478 # -G makes perforce format its output as marshalled python objects
1479 args.extend(["-G"])
1480 if self.p4_port:
1481 args.extend(["-p", self.p4_port])
1482 if self.p4_client:
1483 args.extend(["-c", self.p4_client])
1484 if self.p4_user:
1485 args.extend(["-u", self.p4_user])
1486 args.extend(extra_args)
1487
1488 data, retcode = RunShellWithReturnCode(
1489 args, print_output=False, universal_newlines=universal_newlines)
1490 if marshal_output and data:
1491 data = marshal.loads(data)
1492 return data, retcode
1493
1494 def RunPerforceCommand(self, extra_args, marshal_output=False,
1495 universal_newlines=True):
1496 # This might be a good place to cache call results, since things like
1497 # describe or fstat might get called repeatedly.
1498 data, retcode = self.RunPerforceCommandWithReturnCode(
1499 extra_args, marshal_output, universal_newlines)
1500 if retcode:
1501 ErrorExit("Got error status from %s:\n%s" % (extra_args, data))
1502 return data
1503
1504 def GetFileProperties(self, property_key_prefix = "", command = "describe"):
1505 description = self.RunPerforceCommand(["describe", self.p4_changelist],
1506 marshal_output=True)
1507
1508 changed_files = {}
1509 file_index = 0
1510 # Try depotFile0, depotFile1, ... until we don't find a match
1511 while True:
1512 file_key = "depotFile%d" % file_index
1513 if file_key in description:
1514 filename = description[file_key]
1515 change_type = description[property_key_prefix + str(file_index)]
1516 changed_files[filename] = change_type
1517 file_index += 1
1518 else:
1519 break
1520 return changed_files
1521
1522 def GetChangedFiles(self):
1523 return self.GetFileProperties("action")
1524
1525 def GetUnknownFiles(self):
1526 # Perforce doesn't detect new files, they have to be explicitly added
1527 return []
1528
1529 def IsBaseBinary(self, filename):
1530 base_filename = self.GetBaseFilename(filename)
1531 return self.IsBinaryHelper(base_filename, "files")
1532
1533 def IsPendingBinary(self, filename):
1534 return self.IsBinaryHelper(filename, "describe")
1535
1536 def IsBinary(self, filename):
1537 ErrorExit("IsBinary is not safe: call IsBaseBinary or IsPendingBinary")
1538
1539 def IsBinaryHelper(self, filename, command):
1540 file_types = self.GetFileProperties("type", command)
1541 if not filename in file_types:
1542 ErrorExit("Trying to check binary status of unknown file %s." % filename)
1543 # This treats symlinks, macintosh resource files, temporary objects, and
1544 # unicode as binary. See the Perforce docs for more details:
1545 # http://www.perforce.com/perforce/doc.current/manuals/cmdref/o.ftypes.html
1546 return not file_types[filename].endswith("text")
1547
1548 def GetFileContent(self, filename, revision, is_binary):
1549 file_arg = filename
1550 if revision:
1551 file_arg += "#" + revision
1552 # -q suppresses the initial line that displays the filename and revision
1553 return self.RunPerforceCommand(["print", "-q", file_arg],
1554 universal_newlines=not is_binary)
1555
1556 def GetBaseFilename(self, filename):
1557 actionsWithDifferentBases = [
1558 "move/add", # p4 move
1559 "branch", # p4 integrate (to a new file), similar to hg "add"
1560 "add", # p4 integrate (to a new file), after modifying the new file
1561 ]
1562
1563 # We only see a different base for "add" if this is a downgraded branch
1564 # after a file was branched (integrated), then edited.
1565 if self.GetAction(filename) in actionsWithDifferentBases:
1566 # -Or shows information about pending integrations/moves
1567 fstat_result = self.RunPerforceCommand(["fstat", "-Or", filename],
1568 marshal_output=True)
1569
1570 baseFileKey = "resolveFromFile0" # I think it's safe to use only file0
1571 if baseFileKey in fstat_result:
1572 return fstat_result[baseFileKey]
1573
1574 return filename
1575
1576 def GetBaseRevision(self, filename):
1577 base_filename = self.GetBaseFilename(filename)
1578
1579 have_result = self.RunPerforceCommand(["have", base_filename],
1580 marshal_output=True)
1581 if "haveRev" in have_result:
1582 return have_result["haveRev"]
1583
1584 def GetLocalFilename(self, filename):
1585 where = self.RunPerforceCommand(["where", filename], marshal_output=True)
1586 if "path" in where:
1587 return where["path"]
1588
1589 def GenerateDiff(self, args):
1590 class DiffData:
1591 def __init__(self, perforceVCS, filename, action):
1592 self.perforceVCS = perforceVCS
1593 self.filename = filename
1594 self.action = action
1595 self.base_filename = perforceVCS.GetBaseFilename(filename)
1596
1597 self.file_body = None
1598 self.base_rev = None
1599 self.prefix = None
1600 self.working_copy = True
1601 self.change_summary = None
1602
1603 def GenerateDiffHeader(diffData):
1604 header = []
1605 header.append("Index: %s" % diffData.filename)
1606 header.append("=" * 67)
1607
1608 if diffData.base_filename != diffData.filename:
1609 if diffData.action.startswith("move"):
1610 verb = "rename"
1611 else:
1612 verb = "copy"
1613 header.append("%s from %s" % (verb, diffData.base_filename))
1614 header.append("%s to %s" % (verb, diffData.filename))
1615
1616 suffix = "\t(revision %s)" % diffData.base_rev
1617 header.append("--- " + diffData.base_filename + suffix)
1618 if diffData.working_copy:
1619 suffix = "\t(working copy)"
1620 header.append("+++ " + diffData.filename + suffix)
1621 if diffData.change_summary:
1622 header.append(diffData.change_summary)
1623 return header
1624
1625 def GenerateMergeDiff(diffData, args):
1626 # -du generates a unified diff, which is nearly svn format
1627 diffData.file_body = self.RunPerforceCommand(
1628 ["diff", "-du", diffData.filename] + args)
1629 diffData.base_rev = self.GetBaseRevision(diffData.filename)
1630 diffData.prefix = ""
1631
1632 # We have to replace p4's file status output (the lines starting
1633 # with +++ or ---) to match svn's diff format
1634 lines = diffData.file_body.splitlines()
1635 first_good_line = 0
1636 while (first_good_line < len(lines) and
1637 not lines[first_good_line].startswith("@@")):
1638 first_good_line += 1
1639 diffData.file_body = "\n".join(lines[first_good_line:])
1640 return diffData
1641
1642 def GenerateAddDiff(diffData):
1643 fstat = self.RunPerforceCommand(["fstat", diffData.filename],
1644 marshal_output=True)
1645 if "headRev" in fstat:
1646 diffData.base_rev = fstat["headRev"] # Re-adding a deleted file
1647 else:
1648 diffData.base_rev = "0" # Brand new file
1649 diffData.working_copy = False
1650 rel_path = self.GetLocalFilename(diffData.filename)
1651 diffData.file_body = open(rel_path, 'r').read()
1652 # Replicate svn's list of changed lines
1653 line_count = len(diffData.file_body.splitlines())
1654 diffData.change_summary = "@@ -0,0 +1"
1655 if line_count > 1:
1656 diffData.change_summary += ",%d" % line_count
1657 diffData.change_summary += " @@"
1658 diffData.prefix = "+"
1659 return diffData
1660
1661 def GenerateDeleteDiff(diffData):
1662 diffData.base_rev = self.GetBaseRevision(diffData.filename)
1663 is_base_binary = self.IsBaseBinary(diffData.filename)
1664 # For deletes, base_filename == filename
1665 diffData.file_body = self.GetFileContent(diffData.base_filename,
1666 None,
1667 is_base_binary)
1668 # Replicate svn's list of changed lines
1669 line_count = len(diffData.file_body.splitlines())
1670 diffData.change_summary = "@@ -1"
1671 if line_count > 1:
1672 diffData.change_summary += ",%d" % line_count
1673 diffData.change_summary += " +0,0 @@"
1674 diffData.prefix = "-"
1675 return diffData
1676
1677 changed_files = self.GetChangedFiles()
1678
1679 svndiff = []
1680 filecount = 0
1681 for (filename, action) in changed_files.items():
1682 svn_status = self.PerforceActionToSvnStatus(action)
1683 if svn_status == "SKIP":
1684 continue
1685
1686 diffData = DiffData(self, filename, action)
1687 # Is it possible to diff a branched file? Stackoverflow says no:
1688 # http://stackoverflow.com/questions/1771314/in-perforce-command-line-how- to-diff-a-file-reopened-for-add
1689 if svn_status == "M":
1690 diffData = GenerateMergeDiff(diffData, args)
1691 elif svn_status == "A":
1692 diffData = GenerateAddDiff(diffData)
1693 elif svn_status == "D":
1694 diffData = GenerateDeleteDiff(diffData)
1695 else:
1696 ErrorExit("Unknown file action %s (svn action %s)." % \
1697 (action, svn_status))
1698
1699 svndiff += GenerateDiffHeader(diffData)
1700
1701 for line in diffData.file_body.splitlines():
1702 svndiff.append(diffData.prefix + line)
1703 filecount += 1
1704 if not filecount:
1705 ErrorExit("No valid patches found in output from p4 diff")
1706 return "\n".join(svndiff) + "\n"
1707
1708 def PerforceActionToSvnStatus(self, status):
1709 # Mirroring the list at http://permalink.gmane.org/gmane.comp.version-contro l.mercurial.devel/28717
1710 # Is there something more official?
1711 return {
1712 "add" : "A",
1713 "branch" : "A",
1714 "delete" : "D",
1715 "edit" : "M", # Also includes changing file types.
1716 "integrate" : "M",
1717 "move/add" : "M",
1718 "move/delete": "SKIP",
1719 "purge" : "D", # How does a file's status become "purge"?
1720 }[status]
1721
1722 def GetAction(self, filename):
1723 changed_files = self.GetChangedFiles()
1724 if not filename in changed_files:
1725 ErrorExit("Trying to get base version of unknown file %s." % filename)
1726
1727 return changed_files[filename]
1728
1729 def GetBaseFile(self, filename):
1730 base_filename = self.GetBaseFilename(filename)
1731 base_content = ""
1732 new_content = None
1733
1734 status = self.PerforceActionToSvnStatus(self.GetAction(filename))
1735
1736 if status != "A":
1737 revision = self.GetBaseRevision(base_filename)
1738 if not revision:
1739 ErrorExit("Couldn't find base revision for file %s" % filename)
1740 is_base_binary = self.IsBaseBinary(base_filename)
1741 base_content = self.GetFileContent(base_filename,
1742 revision,
1743 is_base_binary)
1744
1745 is_binary = self.IsPendingBinary(filename)
1746 if status != "D" and status != "SKIP":
1747 relpath = self.GetLocalFilename(filename)
1748 if is_binary and self.IsImage(relpath):
1749 new_content = open(relpath, "rb").read()
1750
1751 return base_content, new_content, is_binary, status
1752
1367 # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync. 1753 # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync.
1368 def SplitPatch(data): 1754 def SplitPatch(data):
1369 """Splits a patch into separate pieces for each file. 1755 """Splits a patch into separate pieces for each file.
1370 1756
1371 Args: 1757 Args:
1372 data: A string containing the output of svn diff. 1758 data: A string containing the output of svn diff.
1373 1759
1374 Returns: 1760 Returns:
1375 A list of 2-tuple (filename, text) where text is the svn diff output 1761 A list of 2-tuple (filename, text) where text is the svn diff output
1376 pertaining to filename. 1762 pertaining to filename.
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
1426 print "Uploading patch for " + patch[0] 1812 print "Uploading patch for " + patch[0]
1427 response_body = rpc_server.Send(url, body, content_type=ctype) 1813 response_body = rpc_server.Send(url, body, content_type=ctype)
1428 lines = response_body.splitlines() 1814 lines = response_body.splitlines()
1429 if not lines or lines[0] != "OK": 1815 if not lines or lines[0] != "OK":
1430 StatusUpdate(" --> %s" % response_body) 1816 StatusUpdate(" --> %s" % response_body)
1431 sys.exit(1) 1817 sys.exit(1)
1432 rv.append([lines[1], patch[0]]) 1818 rv.append([lines[1], patch[0]])
1433 return rv 1819 return rv
1434 1820
1435 1821
1436 def GuessVCSName(): 1822 def GuessVCSName(options):
1437 """Helper to guess the version control system. 1823 """Helper to guess the version control system.
1438 1824
1439 This examines the current directory, guesses which VersionControlSystem 1825 This examines the current directory, guesses which VersionControlSystem
1440 we're using, and returns an string indicating which VCS is detected. 1826 we're using, and returns an string indicating which VCS is detected.
1441 1827
1442 Returns: 1828 Returns:
1443 A pair (vcs, output). vcs is a string indicating which VCS was detected 1829 A pair (vcs, output). vcs is a string indicating which VCS was detected
1444 and is one of VCS_GIT, VCS_MERCURIAL, VCS_SUBVERSION, or VCS_UNKNOWN. 1830 and is one of VCS_GIT, VCS_MERCURIAL, VCS_SUBVERSION, VCS_PERFORCE,
1831 VCS_CVS, or VCS_UNKNOWN.
1832 Since local perforce repositories can't be easily detected, this method
1833 will only guess VCS_PERFORCE if any perforce options have been specified.
1445 output is a string containing any interesting output from the vcs 1834 output is a string containing any interesting output from the vcs
1446 detection routine, or None if there is nothing interesting. 1835 detection routine, or None if there is nothing interesting.
1447 """ 1836 """
1837 for attribute, value in options.__dict__.iteritems():
1838 if attribute.startswith("p4") and value != None:
1839 return (VCS_PERFORCE, None)
1840
1448 # Mercurial has a command to get the base directory of a repository 1841 # Mercurial has a command to get the base directory of a repository
1449 # Try running it, but don't die if we don't have hg installed. 1842 # Try running it, but don't die if we don't have hg installed.
1450 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. 1843 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy.
1451 try: 1844 try:
1452 out, returncode = RunShellWithReturnCode(["hg", "root"]) 1845 out, returncode = RunShellWithReturnCode(["hg", "root"])
1453 if returncode == 0: 1846 if returncode == 0:
1454 return (VCS_MERCURIAL, out.strip()) 1847 return (VCS_MERCURIAL, out.strip())
1455 except OSError, (errno, message): 1848 except OSError, (errno, message):
1456 if errno != 2: # ENOENT -- they don't have hg installed. 1849 if errno != 2: # ENOENT -- they don't have hg installed.
1457 raise 1850 raise
1458 1851
1459 # Subversion has a .svn in all working directories. 1852 # Subversion has a .svn in all working directories.
1460 if os.path.isdir('.svn'): 1853 if os.path.isdir('.svn'):
1461 logging.info("Guessed VCS = Subversion") 1854 logging.info("Guessed VCS = Subversion")
1462 return (VCS_SUBVERSION, None) 1855 return (VCS_SUBVERSION, None)
1463 1856
1464 # Git has a command to test if you're in a git tree. 1857 # Git has a command to test if you're in a git tree.
1465 # Try running it, but don't die if we don't have git installed. 1858 # Try running it, but don't die if we don't have git installed.
1466 try: 1859 try:
1467 out, returncode = RunShellWithReturnCode(["git", "rev-parse", 1860 out, returncode = RunShellWithReturnCode(["git", "rev-parse",
1468 "--is-inside-work-tree"]) 1861 "--is-inside-work-tree"])
1469 if returncode == 0: 1862 if returncode == 0:
1470 return (VCS_GIT, None) 1863 return (VCS_GIT, None)
1471 except OSError, (errno, message): 1864 except OSError, (errno, message):
1472 if errno != 2: # ENOENT -- they don't have git installed. 1865 if errno != 2: # ENOENT -- they don't have git installed.
1473 raise 1866 raise
1474 1867
1868 # detect CVS repos use `cvs status && $? == 0` rules
1869 try:
1870 out, returncode = RunShellWithReturnCode(["cvs", "status"])
1871 if returncode == 0:
1872 return (VCS_CVS, None)
1873 except OSError, (errno, message):
1874 if error != 2:
1875 raise
1876
1475 return (VCS_UNKNOWN, None) 1877 return (VCS_UNKNOWN, None)
1476 1878
1477 1879
1478 def GuessVCS(options): 1880 def GuessVCS(options):
1479 """Helper to guess the version control system. 1881 """Helper to guess the version control system.
1480 1882
1481 This verifies any user-specified VersionControlSystem (by command line 1883 This verifies any user-specified VersionControlSystem (by command line
1482 or environment variable). If the user didn't specify one, this examines 1884 or environment variable). If the user didn't specify one, this examines
1483 the current directory, guesses which VersionControlSystem we're using, 1885 the current directory, guesses which VersionControlSystem we're using,
1484 and returns an instance of the appropriate class. Exit with an error 1886 and returns an instance of the appropriate class. Exit with an error
1485 if we can't figure it out. 1887 if we can't figure it out.
1486 1888
1487 Returns: 1889 Returns:
1488 A VersionControlSystem instance. Exits if the VCS can't be guessed. 1890 A VersionControlSystem instance. Exits if the VCS can't be guessed.
1489 """ 1891 """
1490 vcs = options.vcs 1892 vcs = options.vcs
1491 if not vcs: 1893 if not vcs:
1492 vcs = os.environ.get("CODEREVIEW_VCS") 1894 vcs = os.environ.get("CODEREVIEW_VCS")
1493 if vcs: 1895 if vcs:
1494 v = VCS_ABBREVIATIONS.get(vcs.lower()) 1896 v = VCS_ABBREVIATIONS.get(vcs.lower())
1495 if v is None: 1897 if v is None:
1496 ErrorExit("Unknown version control system %r specified." % vcs) 1898 ErrorExit("Unknown version control system %r specified." % vcs)
1497 (vcs, extra_output) = (v, None) 1899 (vcs, extra_output) = (v, None)
1498 else: 1900 else:
1499 (vcs, extra_output) = GuessVCSName() 1901 (vcs, extra_output) = GuessVCSName(options)
1500 1902
1501 if vcs == VCS_MERCURIAL: 1903 if vcs == VCS_MERCURIAL:
1502 if extra_output is None: 1904 if extra_output is None:
1503 extra_output = RunShell(["hg", "root"]).strip() 1905 extra_output = RunShell(["hg", "root"]).strip()
1504 return MercurialVCS(options, extra_output) 1906 return MercurialVCS(options, extra_output)
1505 elif vcs == VCS_SUBVERSION: 1907 elif vcs == VCS_SUBVERSION:
1506 return SubversionVCS(options) 1908 return SubversionVCS(options)
1909 elif vcs == VCS_PERFORCE:
1910 return PerforceVCS(options)
1507 elif vcs == VCS_GIT: 1911 elif vcs == VCS_GIT:
1508 return GitVCS(options) 1912 return GitVCS(options)
1913 elif vcs == VCS_CVS:
1914 return CVSVCS(options)
1509 1915
1510 ErrorExit(("Could not guess version control system. " 1916 ErrorExit(("Could not guess version control system. "
1511 "Are you in a working copy directory?")) 1917 "Are you in a working copy directory?"))
1512 1918
1513 1919
1514 def CheckReviewer(reviewer): 1920 def CheckReviewer(reviewer):
1515 """Validate a reviewer -- either a nickname or an email addres. 1921 """Validate a reviewer -- either a nickname or an email addres.
1516 1922
1517 Args: 1923 Args:
1518 reviewer: A nickname or an email address. 1924 reviewer: A nickname or an email address.
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
1677 base = guessed_base 2083 base = guessed_base
1678 2084
1679 if not base and options.download_base: 2085 if not base and options.download_base:
1680 options.download_base = True 2086 options.download_base = True
1681 logging.info("Enabled upload of base file") 2087 logging.info("Enabled upload of base file")
1682 if not options.assume_yes: 2088 if not options.assume_yes:
1683 vcs.CheckForUnknownFiles() 2089 vcs.CheckForUnknownFiles()
1684 if data is None: 2090 if data is None:
1685 data = vcs.GenerateDiff(args) 2091 data = vcs.GenerateDiff(args)
1686 data = vcs.PostProcessDiff(data) 2092 data = vcs.PostProcessDiff(data)
2093 if options.print_diffs:
2094 print "Rietveld diff start:*****"
2095 print data
2096 print "Rietveld diff end:*****"
1687 files = vcs.GetBaseFiles(data) 2097 files = vcs.GetBaseFiles(data)
1688 if verbosity >= 1: 2098 if verbosity >= 1:
1689 print "Upload server:", options.server, "(change with -s/--server)" 2099 print "Upload server:", options.server, "(change with -s/--server)"
1690 if options.issue: 2100 if options.issue:
1691 prompt = "Message describing this patch set: " 2101 prompt = "Message describing this patch set: "
1692 else: 2102 else:
1693 prompt = "New issue subject: " 2103 prompt = "New issue subject: "
1694 message = options.message or raw_input(prompt).strip() 2104 message = options.message or raw_input(prompt).strip()
1695 if not message: 2105 if not message:
1696 ErrorExit("A non-empty message is required") 2106 ErrorExit("A non-empty message is required")
1697 rpc_server = GetRpcServer(options.server, 2107 rpc_server = GetRpcServer(options.server,
1698 options.email, 2108 options.email,
1699 options.host, 2109 options.host,
1700 options.save_cookies, 2110 options.save_cookies,
1701 options.account_type) 2111 options.account_type)
1702 form_fields = [("subject", message)] 2112 form_fields = [("subject", message)]
1703 if base: 2113 if base:
2114 b = urlparse.urlparse(base)
2115 username, netloc = urllib.splituser(b.netloc)
2116 if username:
2117 logging.info("Removed username from base URL")
2118 base = urlparse.urlunparse((b.scheme, netloc, b.path, b.params,
2119 b.query, b.fragment))
1704 form_fields.append(("base", base)) 2120 form_fields.append(("base", base))
1705 if options.issue: 2121 if options.issue:
1706 form_fields.append(("issue", str(options.issue))) 2122 form_fields.append(("issue", str(options.issue)))
1707 if options.email: 2123 if options.email:
1708 form_fields.append(("user", options.email)) 2124 form_fields.append(("user", options.email))
1709 if options.reviewers: 2125 if options.reviewers:
1710 for reviewer in options.reviewers.split(','): 2126 for reviewer in options.reviewers.split(','):
1711 CheckReviewer(reviewer) 2127 CheckReviewer(reviewer)
1712 form_fields.append(("reviewers", options.reviewers)) 2128 form_fields.append(("reviewers", options.reviewers))
1713 if options.cc: 2129 if options.cc:
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
1785 try: 2201 try:
1786 RealMain(sys.argv) 2202 RealMain(sys.argv)
1787 except KeyboardInterrupt: 2203 except KeyboardInterrupt:
1788 print 2204 print
1789 StatusUpdate("Interrupted.") 2205 StatusUpdate("Interrupted.")
1790 sys.exit(1) 2206 sys.exit(1)
1791 2207
1792 2208
1793 if __name__ == "__main__": 2209 if __name__ == "__main__":
1794 main() 2210 main()
OLDNEW
« no previous file with comments | « no previous file | third_party/upload.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698