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 |