| 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 import datetime | 10 import datetime |
| 11 import errno |
| 11 import getpass | 12 import getpass |
| 12 import logging | 13 import logging |
| 13 import optparse | 14 import optparse |
| 14 import os | 15 import os |
| 15 import posixpath | 16 import posixpath |
| 16 import shutil | 17 import shutil |
| 17 import socket | |
| 18 import subprocess | |
| 19 import sys | 18 import sys |
| 20 import tempfile | 19 import tempfile |
| 21 import urllib | 20 import urllib |
| 22 | 21 |
| 23 try: | 22 try: |
| 24 import breakpad | 23 import breakpad |
| 25 except ImportError: | 24 except ImportError: |
| 26 pass | 25 pass |
| 27 | 26 |
| 28 import gclient_utils | 27 import gclient_utils |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 113 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'), | 112 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'), |
| 114 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'), | 113 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'), |
| 115 } | 114 } |
| 116 for (k, v) in settings.iteritems(): | 115 for (k, v) in settings.iteritems(): |
| 117 if v and getattr(self.options, k) is None: | 116 if v and getattr(self.options, k) is None: |
| 118 setattr(self.options, k, v) | 117 setattr(self.options, k, v) |
| 119 | 118 |
| 120 def GclientStyleSettings(self): | 119 def GclientStyleSettings(self): |
| 121 """Find the root, assuming a gclient-style checkout.""" | 120 """Find the root, assuming a gclient-style checkout.""" |
| 122 if not self.options.no_gclient and not self.options.root: | 121 if not self.options.no_gclient and not self.options.root: |
| 123 root = self.GetLocalRoot() | 122 root = self.checkout_root |
| 124 gclient_root = gclient_utils.FindGclientRoot(root) | 123 gclient_root = gclient_utils.FindGclientRoot(root) |
| 125 if gclient_root: | 124 if gclient_root: |
| 126 self.options.root = gclient_utils.PathDifference(gclient_root, root) | 125 self.options.root = gclient_utils.PathDifference(gclient_root, root) |
| 127 | 126 |
| 128 def AutomagicalSettings(self): | 127 def AutomagicalSettings(self): |
| 129 """Determines settings based on supported code review and checkout tools. | 128 """Determines settings based on supported code review and checkout tools. |
| 130 """ | 129 """ |
| 131 self.GclStyleSettings() | 130 self.GclStyleSettings() |
| 132 self.GclientStyleSettings() | 131 self.GclientStyleSettings() |
| 133 | 132 |
| 133 def ReadRootFile(self, filename): |
| 134 raise NotImplementedError() |
| 135 |
| 134 | 136 |
| 135 class SVN(SCM): | 137 class SVN(SCM): |
| 136 """Gathers the options and diff for a subversion checkout.""" | 138 """Gathers the options and diff for a subversion checkout.""" |
| 137 def __init__(self, *args, **kwargs): | 139 def __init__(self, *args, **kwargs): |
| 138 SCM.__init__(self, *args, **kwargs) | 140 SCM.__init__(self, *args, **kwargs) |
| 139 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) | 141 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) |
| 140 if not self.options.email: | 142 if not self.options.email: |
| 141 # Assumes the svn credential is an email address. | 143 # Assumes the svn credential is an email address. |
| 142 self.options.email = scm.SVN.GetEmail(self.checkout_root) | 144 self.options.email = scm.SVN.GetEmail(self.checkout_root) |
| 145 logging.info("SVN(%s)" % self.checkout_root) |
| 143 | 146 |
| 144 def ReadRootFile(self, filename): | 147 def ReadRootFile(self, filename): |
| 145 try: | 148 try: |
| 146 # Try to search on the subversion repository for the file. | 149 # Try to search on the subversion repository for the file. |
| 147 import gcl | 150 import gcl |
| 148 data = gcl.GetCachedFile(filename, use_root=True) | 151 data = gcl.GetCachedFile(filename, use_root=True) |
| 149 logging.debug('%s:\n%s' % (filename, data)) | 152 logging.debug('%s:\n%s' % (filename, data)) |
| 150 return data | 153 return data |
| 151 except ImportError: | 154 except ImportError: |
| 152 try: | 155 try: |
| (...skipping 15 matching lines...) Expand all Loading... |
| 168 previous_cwd = os.getcwd() | 171 previous_cwd = os.getcwd() |
| 169 os.chdir(self.checkout_root) | 172 os.chdir(self.checkout_root) |
| 170 excluded = ['!', '?', 'X', ' ', '~'] | 173 excluded = ['!', '?', 'X', ' ', '~'] |
| 171 self.files = [ | 174 self.files = [ |
| 172 f[1] for f in scm.SVN.CaptureStatus(self.checkout_root) | 175 f[1] for f in scm.SVN.CaptureStatus(self.checkout_root) |
| 173 if f[0][0] not in excluded | 176 if f[0][0] not in excluded |
| 174 ] | 177 ] |
| 175 os.chdir(previous_cwd) | 178 os.chdir(previous_cwd) |
| 176 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True) | 179 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True) |
| 177 | 180 |
| 178 def GetLocalRoot(self): | |
| 179 """Return the path of the repository root.""" | |
| 180 return self.checkout_root | |
| 181 | |
| 182 | 181 |
| 183 class GIT(SCM): | 182 class GIT(SCM): |
| 184 """Gathers the options and diff for a git checkout.""" | 183 """Gathers the options and diff for a git checkout.""" |
| 185 def __init__(self, *args, **kwargs): | 184 def __init__(self, *args, **kwargs): |
| 186 SCM.__init__(self, *args, **kwargs) | 185 SCM.__init__(self, *args, **kwargs) |
| 187 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) | 186 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) |
| 188 if not self.options.name: | 187 if not self.options.name: |
| 189 self.options.name = scm.GIT.GetPatchName(self.checkout_root) | 188 self.options.name = scm.GIT.GetPatchName(self.checkout_root) |
| 190 if not self.options.email: | 189 if not self.options.email: |
| 191 self.options.email = scm.GIT.GetEmail(self.checkout_root) | 190 self.options.email = scm.GIT.GetEmail(self.checkout_root) |
| 191 logging.info("GIT(%s)" % self.checkout_root) |
| 192 | 192 |
| 193 def ReadRootFile(self, filename): | 193 def ReadRootFile(self, filename): |
| 194 try: | 194 try: |
| 195 # A git checkout is always a full checkout. | 195 # A git checkout is always a full checkout. |
| 196 data = gclient_utils.FileRead(os.path.join(self.checkout_root, filename)) | 196 data = gclient_utils.FileRead(os.path.join(self.checkout_root, filename)) |
| 197 logging.debug('%s:\n%s' % (filename, data)) | 197 logging.debug('%s:\n%s' % (filename, data)) |
| 198 return data | 198 return data |
| 199 except (IOError, OSError): | 199 except (IOError, OSError): |
| 200 logging.debug('%s:\nNone' % filename) | 200 logging.debug('%s:\nNone' % filename) |
| 201 return None | 201 return None |
| 202 | 202 |
| 203 def GetLocalRoot(self): | |
| 204 """Return the path of the repository root.""" | |
| 205 return self.checkout_root | |
| 206 | |
| 207 def GenerateDiff(self): | 203 def GenerateDiff(self): |
| 208 # For now, ignores self.files | 204 # For now, ignores self.files |
| 209 return scm.GIT.GenerateDiff(self.checkout_root, full_move=True) | 205 return scm.GIT.GenerateDiff(self.checkout_root, full_move=True) |
| 210 | 206 |
| 211 | 207 |
| 212 def _ParseSendChangeOptions(options): | 208 def _ParseSendChangeOptions(options): |
| 213 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" | 209 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" |
| 214 values = {} | 210 values = {} |
| 215 if options.email: | 211 if options.email: |
| 216 values['email'] = options.email | 212 values['email'] = options.email |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 348 | 344 |
| 349 | 345 |
| 350 def PrintSuccess(options): | 346 def PrintSuccess(options): |
| 351 if not options.dry_run: | 347 if not options.dry_run: |
| 352 text = 'Patch \'%s\' sent to try server' % options.name | 348 text = 'Patch \'%s\' sent to try server' % options.name |
| 353 if options.bot: | 349 if options.bot: |
| 354 text += ': %s' % ', '.join(options.bot) | 350 text += ': %s' % ', '.join(options.bot) |
| 355 print(text) | 351 print(text) |
| 356 | 352 |
| 357 | 353 |
| 358 def GuessVCS(options, cwd): | 354 def GuessVCS(options, path): |
| 359 """Helper to guess the version control system. | 355 """Helper to guess the version control system. |
| 360 | 356 |
| 361 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't | 357 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't |
| 362 support it yet. | 358 support it yet. |
| 363 | 359 |
| 364 This examines the current directory, guesses which SCM we're using, and | 360 This examines the path directory, guesses which SCM we're using, and |
| 365 returns an instance of the appropriate class. Exit with an error if we can't | 361 returns an instance of the appropriate class. Exit with an error if we can't |
| 366 figure it out. | 362 figure it out. |
| 367 | 363 |
| 368 Returns: | 364 Returns: |
| 369 A SCM instance. Exits if the SCM can't be guessed. | 365 A SCM instance. Exits if the SCM can't be guessed. |
| 370 """ | 366 """ |
| 371 __pychecker__ = 'no-returnvalues' | 367 __pychecker__ = 'no-returnvalues' |
| 368 logging.info("GuessVCS(%s)" % path) |
| 372 # Subversion has a .svn in all working directories. | 369 # Subversion has a .svn in all working directories. |
| 373 if os.path.isdir(os.path.join(cwd, '.svn')): | 370 if os.path.isdir(os.path.join(path, '.svn')): |
| 374 logging.info("GuessVCS(%s) = Subversion" % cwd) | 371 return SVN(options, path) |
| 375 return SVN(options, cwd) | |
| 376 | 372 |
| 377 # Git has a command to test if you're in a git tree. | 373 # Git has a command to test if you're in a git tree. |
| 378 # Try running it, but don't die if we don't have git installed. | 374 # Try running it, but don't die if we don't have git installed. |
| 379 try: | 375 try: |
| 380 gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"], cwd) | 376 gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"], |
| 381 logging.info("GuessVCS(%s) = Git" % cwd) | 377 path) |
| 382 return GIT(options, cwd) | 378 return GIT(options, path) |
| 383 except gclient_utils.CheckCallError, e: | 379 except gclient_utils.CheckCallError, e: |
| 384 if e.retcode != 2: # ENOENT -- they don't have git installed. | 380 if e.retcode != errno.ENOENT and e.retcode != 128: |
| 381 # ENOENT == 2 = they don't have git installed. |
| 382 # 128 = git error code when not in a repo. |
| 383 logging.warn(e.retcode) |
| 385 raise | 384 raise |
| 386 raise NoTryServerAccess("Could not guess version control system. " | 385 raise NoTryServerAccess("Could not guess version control system. " |
| 387 "Are you in a working copy directory?") | 386 "Are you in a working copy directory?") |
| 388 | 387 |
| 389 | 388 |
| 390 def TryChange(argv, | 389 def TryChange(argv, |
| 391 file_list, | 390 file_list, |
| 392 swallow_exception, | 391 swallow_exception, |
| 393 prog=None): | 392 prog=None): |
| 394 """ | 393 """ |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 454 metavar="FILE", action="append", | 453 metavar="FILE", action="append", |
| 455 help="Use many times to list the files to include in the " | 454 help="Use many times to list the files to include in the " |
| 456 "try, relative to the repository root") | 455 "try, relative to the repository root") |
| 457 group.add_option("--diff", | 456 group.add_option("--diff", |
| 458 help="File containing the diff to try") | 457 help="File containing the diff to try") |
| 459 group.add_option("--url", | 458 group.add_option("--url", |
| 460 help="Url where to grab a patch") | 459 help="Url where to grab a patch") |
| 461 group.add_option("--root", | 460 group.add_option("--root", |
| 462 help="Root to use for the patch; base subdirectory for " | 461 help="Root to use for the patch; base subdirectory for " |
| 463 "patch created in a subdirectory") | 462 "patch created in a subdirectory") |
| 464 group.add_option("--patchlevel", type='int', metavar="LEVEL", | 463 group.add_option("-p", "--patchlevel", type='int', metavar="LEVEL", |
| 465 help="Used as -pN parameter to patch") | 464 help="Used as -pN parameter to patch") |
| 466 group.add_option("--sub_rep", action="append", default=[], | 465 group.add_option("-s", "--sub_rep", action="append", default=[], |
| 467 help="Subcheckout to use in addition. This is mainly " | 466 help="Subcheckout to use in addition. This is mainly " |
| 468 "useful for gclient-style checkouts.") | 467 "useful for gclient-style checkouts.") |
| 469 group.add_option("--no_gclient", action="store_true", | 468 group.add_option("--no_gclient", action="store_true", |
| 470 help="Disable automatic search for gclient checkout.") | 469 help="Disable automatic search for gclient checkout.") |
| 471 parser.add_option_group(group) | 470 parser.add_option_group(group) |
| 472 | 471 |
| 473 group = optparse.OptionGroup(parser, "Access the try server by HTTP") | 472 group = optparse.OptionGroup(parser, "Access the try server by HTTP") |
| 474 group.add_option("--use_http", | 473 group.add_option("--use_http", |
| 475 action="store_const", | 474 action="store_const", |
| 476 const=_SendChangeHTTP, | 475 const=_SendChangeHTTP, |
| 477 dest="send_patch", | 476 dest="send_patch", |
| 478 help="Use HTTP to talk to the try server [default]") | 477 help="Use HTTP to talk to the try server [default]") |
| 479 group.add_option("--host", | 478 group.add_option("-H", "--host", |
| 480 help="Host address") | 479 help="Host address") |
| 481 group.add_option("--port", | 480 group.add_option("-P", "--port", |
| 482 help="HTTP port") | 481 help="HTTP port") |
| 483 group.add_option("--proxy", | 482 group.add_option("--proxy", |
| 484 help="HTTP proxy") | 483 help="HTTP proxy") |
| 485 parser.add_option_group(group) | 484 parser.add_option_group(group) |
| 486 | 485 |
| 487 group = optparse.OptionGroup(parser, "Access the try server with SVN") | 486 group = optparse.OptionGroup(parser, "Access the try server with SVN") |
| 488 group.add_option("--use_svn", | 487 group.add_option("--use_svn", |
| 489 action="store_const", | 488 action="store_const", |
| 490 const=_SendChangeSVN, | 489 const=_SendChangeSVN, |
| 491 dest="send_patch", | 490 dest="send_patch", |
| 492 help="Use SVN to talk to the try server") | 491 help="Use SVN to talk to the try server") |
| 493 group.add_option("--svn_repo", | 492 group.add_option("-S", "--svn_repo", |
| 494 metavar="SVN_URL", | 493 metavar="SVN_URL", |
| 495 help="SVN url to use to write the changes in; --use_svn is " | 494 help="SVN url to use to write the changes in; --use_svn is " |
| 496 "implied when using --svn_repo") | 495 "implied when using --svn_repo") |
| 497 parser.add_option_group(group) | 496 parser.add_option_group(group) |
| 498 | 497 |
| 499 options, args = parser.parse_args(argv) | 498 options, args = parser.parse_args(argv) |
| 500 if len(args) == 1 and args[0] == 'help': | 499 if len(args) == 1 and args[0] == 'help': |
| 501 parser.print_help() | 500 parser.print_help() |
| 502 | 501 |
| 503 if options.verbose == 0: | 502 if not swallow_exception: |
| 504 logging.basicConfig(level=logging.ERROR) | 503 if options.verbose == 0: |
| 505 elif options.verbose == 1: | 504 logging.basicConfig(level=logging.ERROR) |
| 506 logging.basicConfig(level=logging.WARNING) | 505 elif options.verbose == 1: |
| 507 elif options.verbose == 2: | 506 logging.basicConfig(level=logging.WARNING) |
| 508 logging.basicConfig(level=logging.INFO) | 507 elif options.verbose == 2: |
| 509 elif options.verbose > 2: | 508 logging.basicConfig(level=logging.INFO) |
| 510 logging.basicConfig(level=logging.DEBUG) | 509 elif options.verbose > 2: |
| 510 logging.basicConfig(level=logging.DEBUG) |
| 511 | 511 |
| 512 try: | 512 try: |
| 513 # Always include os.getcwd() in the checkout settings. | 513 # Always include os.getcwd() in the checkout settings. |
| 514 checkouts = [] | 514 checkouts = [] |
| 515 checkouts.append(GuessVCS(options, os.getcwd())) | 515 checkouts.append(GuessVCS(options, os.getcwd())) |
| 516 checkouts[0].AutomagicalSettings() | 516 checkouts[0].AutomagicalSettings() |
| 517 for item in options.sub_rep: | 517 for item in options.sub_rep: |
| 518 checkout = GuessVCS(options, item) | 518 checkout = GuessVCS(options, os.path.join(checkouts[0].checkout_root, |
| 519 if checkout.GetLocalRoot() in [c.GetLocalRoot() for c in checkouts]: | 519 item)) |
| 520 if checkout.checkout_root in [c.checkout_root for c in checkouts]: |
| 520 parser.error('Specified the root %s two times.' % | 521 parser.error('Specified the root %s two times.' % |
| 521 checkout.GetLocalRoot()) | 522 checkout.checkout_root) |
| 522 checkouts.append(checkout) | 523 checkouts.append(checkout) |
| 523 | 524 |
| 524 can_http = options.port and options.host | 525 can_http = options.port and options.host |
| 525 can_svn = options.svn_repo | 526 can_svn = options.svn_repo |
| 526 # If there was no transport selected yet, now we must have enough data to | 527 # If there was no transport selected yet, now we must have enough data to |
| 527 # select one. | 528 # select one. |
| 528 if not options.send_patch and not (can_http or can_svn): | 529 if not options.send_patch and not (can_http or can_svn): |
| 529 parser.error('Please specify an access method.') | 530 parser.error('Please specify an access method.') |
| 530 | 531 |
| 531 # Convert options.diff into the content of the diff. | 532 # Convert options.diff into the content of the diff. |
| 532 if options.url: | 533 if options.url: |
| 533 if options.files: | 534 if options.files: |
| 534 parser.error('You cannot specify files and --url at the same time.') | 535 parser.error('You cannot specify files and --url at the same time.') |
| 535 options.diff = urllib.urlopen(options.url).read() | 536 options.diff = urllib.urlopen(options.url).read() |
| 536 elif options.diff: | 537 elif options.diff: |
| 537 if options.files: | 538 if options.files: |
| 538 parser.error('You cannot specify files and --diff at the same time.') | 539 parser.error('You cannot specify files and --diff at the same time.') |
| 539 options.diff = gclient_utils.FileRead(options.diff, 'rb') | 540 options.diff = gclient_utils.FileRead(options.diff, 'rb') |
| 540 else: | 541 else: |
| 541 # Use this as the base. | 542 # Use this as the base. |
| 542 root = checkouts[0].GetLocalRoot() | 543 root = checkouts[0].checkout_root |
| 543 diffs = [] | 544 diffs = [] |
| 544 for checkout in checkouts: | 545 for checkout in checkouts: |
| 545 diff = checkout.GenerateDiff().splitlines(True) | 546 diff = checkout.GenerateDiff().splitlines(True) |
| 546 # Munge it. | 547 # Munge it. |
| 547 path_diff = gclient_utils.PathDifference(root, checkout.GetLocalRoot()) | 548 path_diff = gclient_utils.PathDifference(root, checkout.checkout_root) |
| 548 for i in range(len(diff)): | 549 for i in range(len(diff)): |
| 549 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): | 550 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): |
| 550 diff[i] = diff[i][0:4] + posixpath.join(path_diff, diff[i][4:]) | 551 diff[i] = diff[i][0:4] + posixpath.join(path_diff, diff[i][4:]) |
| 551 diffs.extend(diff) | 552 diffs.extend(diff) |
| 552 options.diff = ''.join(diffs) | 553 options.diff = ''.join(diffs) |
| 553 | 554 |
| 554 if not options.bot: | 555 if not options.bot: |
| 555 # Get try slaves from PRESUBMIT.py files if not specified. | 556 # Get try slaves from PRESUBMIT.py files if not specified. |
| 556 # Even if the diff comes from options.url, use the local checkout for bot | 557 # Even if the diff comes from options.url, use the local checkout for bot |
| 557 # selection. | 558 # selection. |
| 558 try: | 559 try: |
| 559 import presubmit_support | 560 import presubmit_support |
| 560 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py') | 561 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py') |
| 561 options.bot = presubmit_support.DoGetTrySlaves( | 562 options.bot = presubmit_support.DoGetTrySlaves( |
| 562 checkouts[0].GetFileNames(), | 563 checkouts[0].GetFileNames(), |
| 563 checkouts[0].GetLocalRoot(), | 564 checkouts[0].checkout_root, |
| 564 root_presubmit, | 565 root_presubmit, |
| 565 False, | 566 False, |
| 566 sys.stdout) | 567 sys.stdout) |
| 567 except ImportError: | 568 except ImportError: |
| 568 pass | 569 pass |
| 569 # If no bot is specified, either the default pool will be selected or the | 570 # If no bot is specified, either the default pool will be selected or the |
| 570 # try server will refuse the job. Either case we don't need to interfere. | 571 # try server will refuse the job. Either case we don't need to interfere. |
| 571 | 572 |
| 572 if options.name is None: | 573 if options.name is None: |
| 573 if options.issue: | 574 if options.issue: |
| (...skipping 27 matching lines...) Expand all Loading... |
| 601 except (InvalidScript, NoTryServerAccess), e: | 602 except (InvalidScript, NoTryServerAccess), e: |
| 602 if swallow_exception: | 603 if swallow_exception: |
| 603 return 1 | 604 return 1 |
| 604 print e | 605 print e |
| 605 return 1 | 606 return 1 |
| 606 return 0 | 607 return 0 |
| 607 | 608 |
| 608 | 609 |
| 609 if __name__ == "__main__": | 610 if __name__ == "__main__": |
| 610 sys.exit(TryChange(None, [], False)) | 611 sys.exit(TryChange(None, [], False)) |
| OLD | NEW |