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 |