| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 """Client-side script to send a try job to the try server. It communicates to | 5 """Client-side script to send a try job to the try server. It communicates to |
| 6 the try server by either writting to a svn repository or by directly connecting | 6 the try server by either writting to a svn repository or by directly connecting |
| 7 to the server by HTTP. | 7 to the server by HTTP. |
| 8 """ | 8 """ |
| 9 | 9 |
| 10 | 10 |
| 11 import datetime | 11 import datetime |
| 12 import getpass | 12 import getpass |
| 13 import logging | 13 import logging |
| 14 import optparse | 14 import optparse |
| 15 import os | 15 import os |
| 16 import shutil | 16 import shutil |
| 17 import socket | |
| 18 import sys | 17 import sys |
| 19 import tempfile | 18 import tempfile |
| 20 import traceback | 19 import traceback |
| 21 import urllib | 20 import urllib |
| 22 | 21 |
| 23 import gcl | 22 import gcl |
| 24 import gclient | 23 import gclient |
| 25 import upload | 24 import upload |
| 26 | 25 |
| 27 __version__ = '1.1' | 26 __version__ = '1.1' |
| 28 | 27 |
| 29 | 28 |
| 30 # Constants | 29 # Constants |
| 31 HELP_STRING = "Sorry, Tryserver is not available." | 30 HELP_STRING = "Sorry, Tryserver is not available." |
| 31 SCRIPT_PATH = os.path.join('tools', 'tryserver', 'tryserver.py') |
| 32 USAGE = r"""%prog [options] | 32 USAGE = r"""%prog [options] |
| 33 | 33 |
| 34 Client-side script to send a try job to the try server. It communicates to | 34 Client-side script to send a try job to the try server. It communicates to |
| 35 the try server by either writting to a svn repository or by directly connecting | 35 the try server by either writting to a svn repository or by directly connecting |
| 36 to the server by HTTP. | 36 to the server by HTTP. |
| 37 | 37 |
| 38 | 38 |
| 39 Examples: | 39 Examples: |
| 40 A git patch off a web site (git inserts a/ and b/) and fix the base dir: | 40 A git patch off a web site (git inserts a/ and b/) and fix the base dir: |
| 41 %prog --url http://url/to/patch.diff --patchlevel 1 --root src | 41 %prog --url http://url/to/patch.diff --patchlevel 1 --root src |
| (...skipping 26 matching lines...) Expand all Loading... |
| 68 return None | 68 return None |
| 69 # If the root does not have a trailing \ or /, we add it so the returned path | 69 # If the root does not have a trailing \ or /, we add it so the returned path |
| 70 # starts immediately after the seperator regardless of whether it is provided. | 70 # starts immediately after the seperator regardless of whether it is provided. |
| 71 if not root.endswith(os.sep): | 71 if not root.endswith(os.sep): |
| 72 root += os.sep | 72 root += os.sep |
| 73 return subpath[len(root):] | 73 return subpath[len(root):] |
| 74 | 74 |
| 75 | 75 |
| 76 def GetSourceRoot(): | 76 def GetSourceRoot(): |
| 77 """Returns the absolute directory one level up from the repository root.""" | 77 """Returns the absolute directory one level up from the repository root.""" |
| 78 # TODO(maruel): This is odd to assume that '..' is the source root. | |
| 79 return os.path.abspath(os.path.join(gcl.GetRepositoryRoot(), '..')) | 78 return os.path.abspath(os.path.join(gcl.GetRepositoryRoot(), '..')) |
| 80 | 79 |
| 81 | 80 |
| 82 def GetTryServerSettings(): | 81 def ExecuteTryServerScript(): |
| 83 """Grab try server settings local to the repository.""" | 82 """Locates the tryserver script, executes it and returns its dictionary. |
| 84 def _SafeResolve(host): | 83 |
| 84 The try server script contains the repository-specific try server commands.""" |
| 85 script_locals = {} |
| 86 try: |
| 87 # gcl.GetRepositoryRoot() may throw an exception. |
| 88 script_path = os.path.join(gcl.GetRepositoryRoot(), SCRIPT_PATH) |
| 89 except Exception: |
| 90 return script_locals |
| 91 if os.path.exists(script_path): |
| 85 try: | 92 try: |
| 86 return socket.getaddrinfo(host, None) | 93 exec(gcl.ReadFile(script_path), script_locals) |
| 87 except socket.gaierror: | 94 except Exception, e: |
| 88 return None | 95 # TODO(maruel): Need to specialize the exception trapper. |
| 89 | 96 traceback.print_exc() |
| 90 settings = {} | 97 raise InvalidScript('%s is invalid.' % script_path) |
| 91 settings['http_port'] = gcl.GetCodeReviewSetting('TRYSERVER_HTTP_PORT') | 98 return script_locals |
| 92 settings['http_host'] = gcl.GetCodeReviewSetting('TRYSERVER_HTTP_HOST') | |
| 93 settings['svn_repo'] = gcl.GetCodeReviewSetting('TRYSERVER_SVN_URL') | |
| 94 # Use http is the http_host name resolve, fallback to svn otherwise. | |
| 95 if (settings['http_port'] and settings['http_host'] and | |
| 96 _SafeResolve(settings['http_host'])): | |
| 97 settings['default_transport'] = 'http' | |
| 98 elif settings.get('svn_repo'): | |
| 99 settings['default_transport'] = 'svn' | |
| 100 return settings | |
| 101 | 99 |
| 102 | 100 |
| 103 def EscapeDot(name): | 101 def EscapeDot(name): |
| 104 return name.replace('.', '-') | 102 return name.replace('.', '-') |
| 105 | 103 |
| 106 | 104 |
| 107 def RunCommand(command): | 105 def RunCommand(command): |
| 108 output, retcode = gcl.RunShellWithReturnCode(command) | 106 output, retcode = gcl.RunShellWithReturnCode(command) |
| 109 if retcode: | 107 if retcode: |
| 110 raise NoTryServerAccess(' '.join(command) + '\nOuput:\n' + output) | 108 raise NoTryServerAccess(' '.join(command) + '\nOuput:\n' + output) |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 251 values['patchlevel'] = options.patchlevel | 249 values['patchlevel'] = options.patchlevel |
| 252 if options.issue: | 250 if options.issue: |
| 253 values['issue'] = options.issue | 251 values['issue'] = options.issue |
| 254 if options.patchset: | 252 if options.patchset: |
| 255 values['patchset'] = options.patchset | 253 values['patchset'] = options.patchset |
| 256 return values | 254 return values |
| 257 | 255 |
| 258 | 256 |
| 259 def _SendChangeHTTP(options): | 257 def _SendChangeHTTP(options): |
| 260 """Send a change to the try server using the HTTP protocol.""" | 258 """Send a change to the try server using the HTTP protocol.""" |
| 259 script_locals = ExecuteTryServerScript() |
| 260 |
| 261 if not options.host: | 261 if not options.host: |
| 262 raise NoTryServerAccess('Please use the --host option to specify the try ' | 262 options.host = script_locals.get('try_server_http_host', None) |
| 263 'server host to connect to.') | 263 if not options.host: |
| 264 raise NoTryServerAccess('Please use the --host option to specify the try ' |
| 265 'server host to connect to.') |
| 264 if not options.port: | 266 if not options.port: |
| 265 raise NoTryServerAccess('Please use the --port option to specify the try ' | 267 options.port = script_locals.get('try_server_http_port', None) |
| 266 'server port to connect to.') | 268 if not options.port: |
| 269 raise NoTryServerAccess('Please use the --port option to specify the try ' |
| 270 'server port to connect to.') |
| 267 | 271 |
| 268 values = _ParseSendChangeOptions(options) | 272 values = _ParseSendChangeOptions(options) |
| 269 values['patch'] = options.diff | 273 values['patch'] = options.diff |
| 270 | 274 |
| 271 url = 'http://%s:%s/send_try_patch' % (options.host, options.port) | 275 url = 'http://%s:%s/send_try_patch' % (options.host, options.port) |
| 272 proxies = None | 276 proxies = None |
| 273 if options.proxy: | 277 if options.proxy: |
| 274 if options.proxy.lower() == 'none': | 278 if options.proxy.lower() == 'none': |
| 275 # Effectively disable HTTP_PROXY or Internet settings proxy setup. | 279 # Effectively disable HTTP_PROXY or Internet settings proxy setup. |
| 276 proxies = {} | 280 proxies = {} |
| (...skipping 10 matching lines...) Expand all Loading... |
| 287 if not connection: | 291 if not connection: |
| 288 raise NoTryServerAccess('%s is unaccessible.' % url) | 292 raise NoTryServerAccess('%s is unaccessible.' % url) |
| 289 if connection.read() != 'OK': | 293 if connection.read() != 'OK': |
| 290 raise NoTryServerAccess('%s is unaccessible.' % url) | 294 raise NoTryServerAccess('%s is unaccessible.' % url) |
| 291 return options.name | 295 return options.name |
| 292 | 296 |
| 293 | 297 |
| 294 def _SendChangeSVN(options): | 298 def _SendChangeSVN(options): |
| 295 """Send a change to the try server by committing a diff file on a subversion | 299 """Send a change to the try server by committing a diff file on a subversion |
| 296 server.""" | 300 server.""" |
| 301 script_locals = ExecuteTryServerScript() |
| 297 if not options.svn_repo: | 302 if not options.svn_repo: |
| 298 raise NoTryServerAccess('Please use the --svn_repo option to specify the' | 303 options.svn_repo = script_locals.get('try_server_svn', None) |
| 299 ' try server svn repository to connect to.') | 304 if not options.svn_repo: |
| 305 raise NoTryServerAccess('Please use the --svn_repo option to specify the' |
| 306 ' try server svn repository to connect to.') |
| 300 | 307 |
| 301 values = _ParseSendChangeOptions(options) | 308 values = _ParseSendChangeOptions(options) |
| 302 description = '' | 309 description = '' |
| 303 for (k,v) in values.iteritems(): | 310 for (k,v) in values.iteritems(): |
| 304 description += "%s=%s\n" % (k,v) | 311 description += "%s=%s\n" % (k,v) |
| 305 | 312 |
| 306 # Do an empty checkout. | 313 # Do an empty checkout. |
| 307 temp_dir = tempfile.mkdtemp() | 314 temp_dir = tempfile.mkdtemp() |
| 308 temp_file = tempfile.NamedTemporaryFile() | 315 temp_file = tempfile.NamedTemporaryFile() |
| 309 temp_file_name = temp_file.name | 316 temp_file_name = temp_file.name |
| 310 try: | 317 try: |
| 311 # Don't use '--non-interactive', since we want it to prompt for | 318 RunCommand(['svn', 'checkout', '--depth', 'empty', '--non-interactive', |
| 312 # crendentials if necessary. | |
| 313 RunCommand(['svn', 'checkout', '--depth', 'empty', | |
| 314 options.svn_repo, temp_dir]) | 319 options.svn_repo, temp_dir]) |
| 315 # TODO(maruel): Use a subdirectory per user? | 320 # TODO(maruel): Use a subdirectory per user? |
| 316 current_time = str(datetime.datetime.now()).replace(':', '.') | 321 current_time = str(datetime.datetime.now()).replace(':', '.') |
| 317 file_name = (EscapeDot(options.user) + '.' + EscapeDot(options.name) + | 322 file_name = (EscapeDot(options.user) + '.' + EscapeDot(options.name) + |
| 318 '.%s.diff' % current_time) | 323 '.%s.diff' % current_time) |
| 319 full_path = os.path.join(temp_dir, file_name) | 324 full_path = os.path.join(temp_dir, file_name) |
| 320 full_url = options.svn_repo + '/' + file_name | 325 full_url = options.svn_repo + '/' + file_name |
| 321 file_found = False | 326 file_found = False |
| 322 try: | 327 try: |
| 323 RunCommand(['svn', 'ls', '--non-interactive', full_url]) | 328 RunCommand(['svn', 'ls', '--non-interactive', full_url]) |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 379 raise | 384 raise |
| 380 | 385 |
| 381 raise NoTryServerAccess("Could not guess version control system. " | 386 raise NoTryServerAccess("Could not guess version control system. " |
| 382 "Are you in a working copy directory?") | 387 "Are you in a working copy directory?") |
| 383 | 388 |
| 384 | 389 |
| 385 def TryChange(argv, | 390 def TryChange(argv, |
| 386 file_list, | 391 file_list, |
| 387 swallow_exception, | 392 swallow_exception, |
| 388 prog=None): | 393 prog=None): |
| 389 default_settings = GetTryServerSettings() | |
| 390 transport_functions = { 'http': _SendChangeHTTP, 'svn': _SendChangeSVN } | |
| 391 default_transport = transport_functions.get( | |
| 392 default_settings.get('default_transport')) | |
| 393 | |
| 394 # Parse argv | 394 # Parse argv |
| 395 parser = optparse.OptionParser(usage=USAGE, | 395 parser = optparse.OptionParser(usage=USAGE, |
| 396 version=__version__, | 396 version=__version__, |
| 397 prog=prog) | 397 prog=prog) |
| 398 | 398 |
| 399 group = optparse.OptionGroup(parser, "Result and status") | 399 group = optparse.OptionGroup(parser, "Result and status") |
| 400 group.add_option("-u", "--user", default=getpass.getuser(), | 400 group.add_option("-u", "--user", default=getpass.getuser(), |
| 401 help="Owner user name [default: %default]") | 401 help="Owner user name [default: %default]") |
| 402 group.add_option("-e", "--email", default=os.environ.get('EMAIL_ADDRESS'), | 402 group.add_option("-e", "--email", default=os.environ.get('EMAIL_ADDRESS'), |
| 403 help="Email address where to send the results. Use the " | 403 help="Email address where to send the results. Use the " |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 439 group.add_option("--url", | 439 group.add_option("--url", |
| 440 help="Url where to grab a patch") | 440 help="Url where to grab a patch") |
| 441 group.add_option("--root", | 441 group.add_option("--root", |
| 442 help="Root to use for the patch; base subdirectory for " | 442 help="Root to use for the patch; base subdirectory for " |
| 443 "patch created in a subdirectory") | 443 "patch created in a subdirectory") |
| 444 group.add_option("--patchlevel", type='int', metavar="LEVEL", | 444 group.add_option("--patchlevel", type='int', metavar="LEVEL", |
| 445 help="Used as -pN parameter to patch") | 445 help="Used as -pN parameter to patch") |
| 446 parser.add_option_group(group) | 446 parser.add_option_group(group) |
| 447 | 447 |
| 448 group = optparse.OptionGroup(parser, "Access the try server by HTTP") | 448 group = optparse.OptionGroup(parser, "Access the try server by HTTP") |
| 449 group.add_option("--use_http", | 449 group.add_option("--use_http", action="store_const", const=_SendChangeHTTP, |
| 450 action="store_const", | 450 dest="send_patch", default=_SendChangeHTTP, |
| 451 const=_SendChangeHTTP, | |
| 452 dest="send_patch", | |
| 453 default=default_transport, | |
| 454 help="Use HTTP to talk to the try server [default]") | 451 help="Use HTTP to talk to the try server [default]") |
| 455 group.add_option("--host", | 452 group.add_option("--host", |
| 456 default=default_settings['http_host'], | |
| 457 help="Host address") | 453 help="Host address") |
| 458 group.add_option("--port", | 454 group.add_option("--port", |
| 459 default=default_settings['http_port'], | |
| 460 help="HTTP port") | 455 help="HTTP port") |
| 461 group.add_option("--proxy", | 456 group.add_option("--proxy", |
| 462 help="HTTP proxy") | 457 help="HTTP proxy") |
| 463 parser.add_option_group(group) | 458 parser.add_option_group(group) |
| 464 | 459 |
| 465 group = optparse.OptionGroup(parser, "Access the try server with SVN") | 460 group = optparse.OptionGroup(parser, "Access the try server with SVN") |
| 466 group.add_option("--use_svn", | 461 group.add_option("--use_svn", action="store_const", const=_SendChangeSVN, |
| 467 action="store_const", | |
| 468 const=_SendChangeSVN, | |
| 469 dest="send_patch", | 462 dest="send_patch", |
| 470 help="Use SVN to talk to the try server") | 463 help="Use SVN to talk to the try server") |
| 471 group.add_option("--svn_repo", | 464 group.add_option("--svn_repo", metavar="SVN_URL", |
| 472 metavar="SVN_URL", | |
| 473 default=default_settings['svn_repo'], | |
| 474 help="SVN url to use to write the changes in; --use_svn is " | 465 help="SVN url to use to write the changes in; --use_svn is " |
| 475 "implied when using --svn_repo") | 466 "implied when using --svn_repo") |
| 476 parser.add_option_group(group) | 467 parser.add_option_group(group) |
| 477 | 468 |
| 478 options, args = parser.parse_args(argv) | 469 options, args = parser.parse_args(argv) |
| 479 | 470 # Switch the default accordingly. |
| 480 # Switch the default accordingly if there was no default send_patch. | 471 if options.svn_repo: |
| 481 if not options.send_patch: | 472 options.send_patch = _SendChangeSVN |
| 482 if options.http_port and options.http_host: | |
| 483 options.send_patch = _SendChangeHTTP | |
| 484 elif options.svn_repo: | |
| 485 options.send_patch = _SendChangeSVN | |
| 486 | 473 |
| 487 if len(args) == 1 and args[0] == 'help': | 474 if len(args) == 1 and args[0] == 'help': |
| 488 parser.print_help() | 475 parser.print_help() |
| 489 if (not options.files and (not options.issue and options.patchset) and | 476 if (not options.files and (not options.issue and options.patchset) and |
| 490 not options.diff and not options.url): | 477 not options.diff and not options.url): |
| 491 # TODO(maruel): It should just try the modified files showing up in a | 478 # TODO(maruel): It should just try the modified files showing up in a |
| 492 # svn status. | 479 # svn status. |
| 493 print "Nothing to try, changelist is empty." | 480 print "Nothing to try, changelist is empty." |
| 494 return | 481 return |
| 495 | 482 |
| (...skipping 18 matching lines...) Expand all Loading... |
| 514 if patch_name == 'Unnamed': | 501 if patch_name == 'Unnamed': |
| 515 print "Note: use --name NAME to change the try's name." | 502 print "Note: use --name NAME to change the try's name." |
| 516 except (InvalidScript, NoTryServerAccess), e: | 503 except (InvalidScript, NoTryServerAccess), e: |
| 517 if swallow_exception: | 504 if swallow_exception: |
| 518 return | 505 return |
| 519 print e | 506 print e |
| 520 | 507 |
| 521 | 508 |
| 522 if __name__ == "__main__": | 509 if __name__ == "__main__": |
| 523 TryChange(None, None, False) | 510 TryChange(None, None, False) |
| OLD | NEW |