| 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 |
| 17 import sys | 18 import sys |
| 18 import tempfile | 19 import tempfile |
| 19 import traceback | 20 import traceback |
| 20 import urllib | 21 import urllib |
| 21 | 22 |
| 22 import gcl | 23 import gcl |
| 23 import gclient | 24 import gclient |
| 24 import upload | 25 import upload |
| 25 | 26 |
| 26 __version__ = '1.1' | 27 __version__ = '1.1' |
| 27 | 28 |
| 28 | 29 |
| 29 # Constants | 30 # Constants |
| 30 HELP_STRING = "Sorry, Tryserver is not available." | 31 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. |
| 78 return os.path.abspath(os.path.join(gcl.GetRepositoryRoot(), '..')) | 79 return os.path.abspath(os.path.join(gcl.GetRepositoryRoot(), '..')) |
| 79 | 80 |
| 80 | 81 |
| 81 def ExecuteTryServerScript(): | 82 def GetTryServerSettings(): |
| 82 """Locates the tryserver script, executes it and returns its dictionary. | 83 """Grab try server settings local to the repository.""" |
| 84 def _SafeResolve(host): |
| 85 try: |
| 86 return socket.getaddrinfo(host, None) |
| 87 except socket.gaierror: |
| 88 return None |
| 83 | 89 |
| 84 The try server script contains the repository-specific try server commands.""" | 90 settings = {} |
| 85 script_locals = {} | 91 settings['http_port'] = gcl.GetCodeReviewSetting('TRYSERVER_HTTP_PORT') |
| 86 try: | 92 settings['http_host'] = gcl.GetCodeReviewSetting('TRYSERVER_HTTP_HOST') |
| 87 # gcl.GetRepositoryRoot() may throw an exception. | 93 settings['svn_repo'] = gcl.GetCodeReviewSetting('TRYSERVER_SVN_URL') |
| 88 script_path = os.path.join(gcl.GetRepositoryRoot(), SCRIPT_PATH) | 94 # Use http is the http_host name resolve, fallback to svn otherwise. |
| 89 except Exception: | 95 if (settings['http_port'] and settings['http_host'] and |
| 90 return script_locals | 96 _SafeResolve(settings['http_host'])): |
| 91 if os.path.exists(script_path): | 97 settings['default_transport'] = 'http' |
| 92 try: | 98 elif settings.get('svn_repo'): |
| 93 exec(gcl.ReadFile(script_path), script_locals) | 99 settings['default_transport'] = 'svn' |
| 94 except Exception, e: | 100 return settings |
| 95 # TODO(maruel): Need to specialize the exception trapper. | |
| 96 traceback.print_exc() | |
| 97 raise InvalidScript('%s is invalid.' % script_path) | |
| 98 return script_locals | |
| 99 | 101 |
| 100 | 102 |
| 101 def EscapeDot(name): | 103 def EscapeDot(name): |
| 102 return name.replace('.', '-') | 104 return name.replace('.', '-') |
| 103 | 105 |
| 104 | 106 |
| 105 def RunCommand(command): | 107 def RunCommand(command): |
| 106 output, retcode = gcl.RunShellWithReturnCode(command) | 108 output, retcode = gcl.RunShellWithReturnCode(command) |
| 107 if retcode: | 109 if retcode: |
| 108 raise NoTryServerAccess(' '.join(command) + '\nOuput:\n' + output) | 110 raise NoTryServerAccess(' '.join(command) + '\nOuput:\n' + output) |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 249 values['patchlevel'] = options.patchlevel | 251 values['patchlevel'] = options.patchlevel |
| 250 if options.issue: | 252 if options.issue: |
| 251 values['issue'] = options.issue | 253 values['issue'] = options.issue |
| 252 if options.patchset: | 254 if options.patchset: |
| 253 values['patchset'] = options.patchset | 255 values['patchset'] = options.patchset |
| 254 return values | 256 return values |
| 255 | 257 |
| 256 | 258 |
| 257 def _SendChangeHTTP(options): | 259 def _SendChangeHTTP(options): |
| 258 """Send a change to the try server using the HTTP protocol.""" | 260 """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 options.host = script_locals.get('try_server_http_host', None) | 262 raise NoTryServerAccess('Please use the --host option to specify the try ' |
| 263 if not options.host: | 263 'server host to connect to.') |
| 264 raise NoTryServerAccess('Please use the --host option to specify the try ' | |
| 265 'server host to connect to.') | |
| 266 if not options.port: | 264 if not options.port: |
| 267 options.port = script_locals.get('try_server_http_port', None) | 265 raise NoTryServerAccess('Please use the --port option to specify the try ' |
| 268 if not options.port: | 266 'server port to connect to.') |
| 269 raise NoTryServerAccess('Please use the --port option to specify the try ' | |
| 270 'server port to connect to.') | |
| 271 | 267 |
| 272 values = _ParseSendChangeOptions(options) | 268 values = _ParseSendChangeOptions(options) |
| 273 values['patch'] = options.diff | 269 values['patch'] = options.diff |
| 274 | 270 |
| 275 url = 'http://%s:%s/send_try_patch' % (options.host, options.port) | 271 url = 'http://%s:%s/send_try_patch' % (options.host, options.port) |
| 276 proxies = None | 272 proxies = None |
| 277 if options.proxy: | 273 if options.proxy: |
| 278 if options.proxy.lower() == 'none': | 274 if options.proxy.lower() == 'none': |
| 279 # Effectively disable HTTP_PROXY or Internet settings proxy setup. | 275 # Effectively disable HTTP_PROXY or Internet settings proxy setup. |
| 280 proxies = {} | 276 proxies = {} |
| (...skipping 10 matching lines...) Expand all Loading... |
| 291 if not connection: | 287 if not connection: |
| 292 raise NoTryServerAccess('%s is unaccessible.' % url) | 288 raise NoTryServerAccess('%s is unaccessible.' % url) |
| 293 if connection.read() != 'OK': | 289 if connection.read() != 'OK': |
| 294 raise NoTryServerAccess('%s is unaccessible.' % url) | 290 raise NoTryServerAccess('%s is unaccessible.' % url) |
| 295 return options.name | 291 return options.name |
| 296 | 292 |
| 297 | 293 |
| 298 def _SendChangeSVN(options): | 294 def _SendChangeSVN(options): |
| 299 """Send a change to the try server by committing a diff file on a subversion | 295 """Send a change to the try server by committing a diff file on a subversion |
| 300 server.""" | 296 server.""" |
| 301 script_locals = ExecuteTryServerScript() | |
| 302 if not options.svn_repo: | 297 if not options.svn_repo: |
| 303 options.svn_repo = script_locals.get('try_server_svn', None) | 298 raise NoTryServerAccess('Please use the --svn_repo option to specify the' |
| 304 if not options.svn_repo: | 299 ' try server svn repository to connect to.') |
| 305 raise NoTryServerAccess('Please use the --svn_repo option to specify the' | |
| 306 ' try server svn repository to connect to.') | |
| 307 | 300 |
| 308 values = _ParseSendChangeOptions(options) | 301 values = _ParseSendChangeOptions(options) |
| 309 description = '' | 302 description = '' |
| 310 for (k,v) in values.iteritems(): | 303 for (k,v) in values.iteritems(): |
| 311 description += "%s=%s\n" % (k,v) | 304 description += "%s=%s\n" % (k,v) |
| 312 | 305 |
| 313 # Do an empty checkout. | 306 # Do an empty checkout. |
| 314 temp_dir = tempfile.mkdtemp() | 307 temp_dir = tempfile.mkdtemp() |
| 315 temp_file = tempfile.NamedTemporaryFile() | 308 temp_file = tempfile.NamedTemporaryFile() |
| 316 temp_file_name = temp_file.name | 309 temp_file_name = temp_file.name |
| 317 try: | 310 try: |
| 318 RunCommand(['svn', 'checkout', '--depth', 'empty', '--non-interactive', | 311 # Don't use '--non-interactive', since we want it to prompt for |
| 312 # crendentials if necessary. |
| 313 RunCommand(['svn', 'checkout', '--depth', 'empty', |
| 319 options.svn_repo, temp_dir]) | 314 options.svn_repo, temp_dir]) |
| 320 # TODO(maruel): Use a subdirectory per user? | 315 # TODO(maruel): Use a subdirectory per user? |
| 321 current_time = str(datetime.datetime.now()).replace(':', '.') | 316 current_time = str(datetime.datetime.now()).replace(':', '.') |
| 322 file_name = (EscapeDot(options.user) + '.' + EscapeDot(options.name) + | 317 file_name = (EscapeDot(options.user) + '.' + EscapeDot(options.name) + |
| 323 '.%s.diff' % current_time) | 318 '.%s.diff' % current_time) |
| 324 full_path = os.path.join(temp_dir, file_name) | 319 full_path = os.path.join(temp_dir, file_name) |
| 325 full_url = options.svn_repo + '/' + file_name | 320 full_url = options.svn_repo + '/' + file_name |
| 326 file_found = False | 321 file_found = False |
| 327 try: | 322 try: |
| 328 RunCommand(['svn', 'ls', '--non-interactive', full_url]) | 323 RunCommand(['svn', 'ls', '--non-interactive', full_url]) |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 384 raise | 379 raise |
| 385 | 380 |
| 386 raise NoTryServerAccess("Could not guess version control system. " | 381 raise NoTryServerAccess("Could not guess version control system. " |
| 387 "Are you in a working copy directory?") | 382 "Are you in a working copy directory?") |
| 388 | 383 |
| 389 | 384 |
| 390 def TryChange(argv, | 385 def TryChange(argv, |
| 391 file_list, | 386 file_list, |
| 392 swallow_exception, | 387 swallow_exception, |
| 393 prog=None): | 388 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", action="store_const", const=_SendChangeHTTP, | 449 group.add_option("--use_http", |
| 450 dest="send_patch", default=_SendChangeHTTP, | 450 action="store_const", |
| 451 const=_SendChangeHTTP, |
| 452 dest="send_patch", |
| 453 default=default_transport, |
| 451 help="Use HTTP to talk to the try server [default]") | 454 help="Use HTTP to talk to the try server [default]") |
| 452 group.add_option("--host", | 455 group.add_option("--host", |
| 456 default=default_settings['http_host'], |
| 453 help="Host address") | 457 help="Host address") |
| 454 group.add_option("--port", | 458 group.add_option("--port", |
| 459 default=default_settings['http_port'], |
| 455 help="HTTP port") | 460 help="HTTP port") |
| 456 group.add_option("--proxy", | 461 group.add_option("--proxy", |
| 457 help="HTTP proxy") | 462 help="HTTP proxy") |
| 458 parser.add_option_group(group) | 463 parser.add_option_group(group) |
| 459 | 464 |
| 460 group = optparse.OptionGroup(parser, "Access the try server with SVN") | 465 group = optparse.OptionGroup(parser, "Access the try server with SVN") |
| 461 group.add_option("--use_svn", action="store_const", const=_SendChangeSVN, | 466 group.add_option("--use_svn", |
| 467 action="store_const", |
| 468 const=_SendChangeSVN, |
| 462 dest="send_patch", | 469 dest="send_patch", |
| 463 help="Use SVN to talk to the try server") | 470 help="Use SVN to talk to the try server") |
| 464 group.add_option("--svn_repo", metavar="SVN_URL", | 471 group.add_option("--svn_repo", |
| 472 metavar="SVN_URL", |
| 473 default=default_settings['svn_repo'], |
| 465 help="SVN url to use to write the changes in; --use_svn is " | 474 help="SVN url to use to write the changes in; --use_svn is " |
| 466 "implied when using --svn_repo") | 475 "implied when using --svn_repo") |
| 467 parser.add_option_group(group) | 476 parser.add_option_group(group) |
| 468 | 477 |
| 469 options, args = parser.parse_args(argv) | 478 options, args = parser.parse_args(argv) |
| 470 # Switch the default accordingly. | 479 |
| 471 if options.svn_repo: | 480 # Switch the default accordingly if there was no default send_patch. |
| 472 options.send_patch = _SendChangeSVN | 481 if not options.send_patch: |
| 482 if options.http_port and options.http_host: |
| 483 options.send_patch = _SendChangeHTTP |
| 484 elif options.svn_repo: |
| 485 options.send_patch = _SendChangeSVN |
| 473 | 486 |
| 474 if len(args) == 1 and args[0] == 'help': | 487 if len(args) == 1 and args[0] == 'help': |
| 475 parser.print_help() | 488 parser.print_help() |
| 476 if (not options.files and (not options.issue and options.patchset) and | 489 if (not options.files and (not options.issue and options.patchset) and |
| 477 not options.diff and not options.url): | 490 not options.diff and not options.url): |
| 478 # TODO(maruel): It should just try the modified files showing up in a | 491 # TODO(maruel): It should just try the modified files showing up in a |
| 479 # svn status. | 492 # svn status. |
| 480 print "Nothing to try, changelist is empty." | 493 print "Nothing to try, changelist is empty." |
| 481 return | 494 return |
| 482 | 495 |
| (...skipping 18 matching lines...) Expand all Loading... |
| 501 if patch_name == 'Unnamed': | 514 if patch_name == 'Unnamed': |
| 502 print "Note: use --name NAME to change the try's name." | 515 print "Note: use --name NAME to change the try's name." |
| 503 except (InvalidScript, NoTryServerAccess), e: | 516 except (InvalidScript, NoTryServerAccess), e: |
| 504 if swallow_exception: | 517 if swallow_exception: |
| 505 return | 518 return |
| 506 print e | 519 print e |
| 507 | 520 |
| 508 | 521 |
| 509 if __name__ == "__main__": | 522 if __name__ == "__main__": |
| 510 TryChange(None, None, False) | 523 TryChange(None, None, False) |
| OLD | NEW |