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 |