| 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 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 65 def __str__(self): | 65 def __str__(self): |
| 66 return self.args[0] + '\n' + HELP_STRING | 66 return self.args[0] + '\n' + HELP_STRING |
| 67 | 67 |
| 68 | 68 |
| 69 def EscapeDot(name): | 69 def EscapeDot(name): |
| 70 return name.replace('.', '-') | 70 return name.replace('.', '-') |
| 71 | 71 |
| 72 | 72 |
| 73 class SCM(object): | 73 class SCM(object): |
| 74 """Simplistic base class to implement one function: ProcessOptions.""" | 74 """Simplistic base class to implement one function: ProcessOptions.""" |
| 75 def __init__(self, options): | 75 def __init__(self, options, cwd): |
| 76 self.checkout_root = cwd |
| 76 self.options = options | 77 self.options = options |
| 78 self.files = self.options.files |
| 79 self.options.files = None |
| 77 | 80 |
| 78 def GetFileNames(self): | 81 def GetFileNames(self): |
| 79 """Return the list of files in the diff.""" | 82 """Return the list of files in the diff.""" |
| 80 return self.options.files | 83 return self.files |
| 81 | 84 |
| 82 | 85 |
| 83 class SVN(SCM): | 86 class SVN(SCM): |
| 84 """Gathers the options and diff for a subversion checkout.""" | 87 """Gathers the options and diff for a subversion checkout.""" |
| 85 def __init__(self, *args, **kwargs): | 88 def __init__(self, *args, **kwargs): |
| 86 SCM.__init__(self, *args, **kwargs) | 89 SCM.__init__(self, *args, **kwargs) |
| 87 self.checkout_root = scm.SVN.GetCheckoutRoot(os.getcwd()) | 90 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) |
| 88 if not self.options.diff: | |
| 89 # Generate the diff from the scm. | |
| 90 self.options.diff = self._GenerateDiff() | |
| 91 if not self.options.email: | 91 if not self.options.email: |
| 92 # Assumes the svn credential is an email address. | 92 # Assumes the svn credential is an email address. |
| 93 self.options.email = scm.SVN.GetEmail(self.checkout_root) | 93 self.options.email = scm.SVN.GetEmail(self.checkout_root) |
| 94 | 94 |
| 95 def _GenerateDiff(self): | 95 def GenerateDiff(self): |
| 96 """Returns a string containing the diff for the given file list. | 96 """Returns a string containing the diff for the given file list. |
| 97 | 97 |
| 98 The files in the list should either be absolute paths or relative to the | 98 The files in the list should either be absolute paths or relative to the |
| 99 given root. | 99 given root. |
| 100 """ | 100 """ |
| 101 if not self.options.files: | 101 if not self.files: |
| 102 previous_cwd = os.getcwd() | 102 previous_cwd = os.getcwd() |
| 103 os.chdir(self.checkout_root) | 103 os.chdir(self.checkout_root) |
| 104 excluded = ['!', '?', 'X', ' ', '~'] | 104 excluded = ['!', '?', 'X', ' ', '~'] |
| 105 self.options.files = [ | 105 self.files = [ |
| 106 f[1] for f in scm.SVN.CaptureStatus(self.checkout_root) | 106 f[1] for f in scm.SVN.CaptureStatus(self.checkout_root) |
| 107 if f[0][0] not in excluded | 107 if f[0][0] not in excluded |
| 108 ] | 108 ] |
| 109 os.chdir(previous_cwd) | 109 os.chdir(previous_cwd) |
| 110 return scm.SVN.GenerateDiff(self.options.files, full_move=True) | 110 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True) |
| 111 | 111 |
| 112 def GetLocalRoot(self): | 112 def GetLocalRoot(self): |
| 113 """Return the path of the repository root.""" | 113 """Return the path of the repository root.""" |
| 114 return self.checkout_root | 114 return self.checkout_root |
| 115 | 115 |
| 116 def GetBots(self): | 116 def GetBots(self): |
| 117 try: | 117 try: |
| 118 # Try to search on the subversion repository for the file. | 118 # Try to search on the subversion repository for the file. |
| 119 import gcl | 119 import gcl |
| 120 return gcl.GetCachedFile('PRESUBMIT.py', use_root=True) | 120 return gcl.GetCachedFile('PRESUBMIT.py', use_root=True) |
| 121 except ImportError: | 121 except ImportError: |
| 122 try: | 122 try: |
| 123 return gclient_utils.FileRead(os.path.join(self.checkout_root, | 123 return gclient_utils.FileRead(os.path.join(self.checkout_root, |
| 124 'PRESUBMIT.py')) | 124 'PRESUBMIT.py')) |
| 125 except (IOError, OSError): | 125 except (IOError, OSError): |
| 126 return None | 126 return None |
| 127 | 127 |
| 128 | 128 |
| 129 class GIT(SCM): | 129 class GIT(SCM): |
| 130 """Gathers the options and diff for a git checkout.""" | 130 """Gathers the options and diff for a git checkout.""" |
| 131 def __init__(self, *args, **kwargs): | 131 def __init__(self, *args, **kwargs): |
| 132 SCM.__init__(self, *args, **kwargs) | 132 SCM.__init__(self, *args, **kwargs) |
| 133 self.checkout_root = scm.GIT.GetCheckoutRoot(os.getcwd()) | 133 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) |
| 134 if not self.options.diff: | |
| 135 self.options.diff = scm.GIT.GenerateDiff(self.checkout_root, | |
| 136 full_move=True) | |
| 137 if not self.options.name: | 134 if not self.options.name: |
| 138 self.options.name = scm.GIT.GetPatchName(self.checkout_root) | 135 self.options.name = scm.GIT.GetPatchName(self.checkout_root) |
| 139 if not self.options.email: | 136 if not self.options.email: |
| 140 self.options.email = scm.GIT.GetEmail(self.checkout_root) | 137 self.options.email = scm.GIT.GetEmail(self.checkout_root) |
| 141 | 138 |
| 142 def GetLocalRoot(self): | 139 def GetLocalRoot(self): |
| 143 """Return the path of the repository root.""" | 140 """Return the path of the repository root.""" |
| 144 return self.checkout_root | 141 return self.checkout_root |
| 145 | 142 |
| 143 def GenerateDiff(self): |
| 144 # For now, ignores self.files |
| 145 return scm.GIT.GenerateDiff(self.checkout_root, full_move=True) |
| 146 |
| 146 def GetBots(self): | 147 def GetBots(self): |
| 147 try: | 148 try: |
| 148 # A git checkout is always a full checkout. | 149 # A git checkout is always a full checkout. |
| 149 return gclient_utils.FileRead(os.path.join(self.checkout_root, | 150 return gclient_utils.FileRead(os.path.join(self.checkout_root, |
| 150 'PRESUBMIT.py')) | 151 'PRESUBMIT.py')) |
| 151 except (IOError, OSError): | 152 except (IOError, OSError): |
| 152 return None | 153 return None |
| 153 | 154 |
| 154 | 155 |
| 155 def _ParseSendChangeOptions(options): | 156 def _ParseSendChangeOptions(options): |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 284 gclient_utils.CheckCall(["svn", "commit", full_path, '--file', | 285 gclient_utils.CheckCall(["svn", "commit", full_path, '--file', |
| 285 temp_file.name], print_error=False) | 286 temp_file.name], print_error=False) |
| 286 except gclient_utils.CheckCallError, e: | 287 except gclient_utils.CheckCallError, e: |
| 287 raise NoTryServerAccess(' '.join(e.command) + '\nOuput:\n' + | 288 raise NoTryServerAccess(' '.join(e.command) + '\nOuput:\n' + |
| 288 e.stdout) | 289 e.stdout) |
| 289 finally: | 290 finally: |
| 290 temp_file.close() | 291 temp_file.close() |
| 291 shutil.rmtree(temp_dir, True) | 292 shutil.rmtree(temp_dir, True) |
| 292 | 293 |
| 293 | 294 |
| 294 def GuessVCS(options): | 295 def GuessVCS(options, cwd): |
| 295 """Helper to guess the version control system. | 296 """Helper to guess the version control system. |
| 296 | 297 |
| 297 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't | 298 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't |
| 298 support it yet. | 299 support it yet. |
| 299 | 300 |
| 300 This examines the current directory, guesses which SCM we're using, and | 301 This examines the current directory, guesses which SCM we're using, and |
| 301 returns an instance of the appropriate class. Exit with an error if we can't | 302 returns an instance of the appropriate class. Exit with an error if we can't |
| 302 figure it out. | 303 figure it out. |
| 303 | 304 |
| 304 Returns: | 305 Returns: |
| 305 A SCM instance. Exits if the SCM can't be guessed. | 306 A SCM instance. Exits if the SCM can't be guessed. |
| 306 """ | 307 """ |
| 307 __pychecker__ = 'no-returnvalues' | 308 __pychecker__ = 'no-returnvalues' |
| 308 # Subversion has a .svn in all working directories. | 309 # Subversion has a .svn in all working directories. |
| 309 if os.path.isdir('.svn'): | 310 if os.path.isdir(os.path.join(cwd, '.svn')): |
| 310 logging.info("Guessed VCS = Subversion") | 311 logging.info("Guessed VCS = Subversion") |
| 311 return SVN(options) | 312 return SVN(options, cwd) |
| 312 | 313 |
| 313 # Git has a command to test if you're in a git tree. | 314 # Git has a command to test if you're in a git tree. |
| 314 # Try running it, but don't die if we don't have git installed. | 315 # Try running it, but don't die if we don't have git installed. |
| 315 try: | 316 try: |
| 316 gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"]) | 317 gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"], cwd) |
| 317 logging.info("Guessed VCS = Git") | 318 logging.info("Guessed VCS = Git") |
| 318 return GIT(options) | 319 return GIT(options, cwd) |
| 319 except gclient_utils.CheckCallError, e: | 320 except gclient_utils.CheckCallError, e: |
| 320 if e.retcode != 2: # ENOENT -- they don't have git installed. | 321 if e.retcode != 2: # ENOENT -- they don't have git installed. |
| 321 raise | 322 raise |
| 322 raise NoTryServerAccess("Could not guess version control system. " | 323 raise NoTryServerAccess("Could not guess version control system. " |
| 323 "Are you in a working copy directory?") | 324 "Are you in a working copy directory?") |
| 324 | 325 |
| 325 | 326 |
| 326 def TryChange(argv, | 327 def TryChange(argv, |
| 327 file_list, | 328 file_list, |
| 328 swallow_exception, | 329 swallow_exception, |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 391 "try, relative to the repository root") | 392 "try, relative to the repository root") |
| 392 group.add_option("--diff", | 393 group.add_option("--diff", |
| 393 help="File containing the diff to try") | 394 help="File containing the diff to try") |
| 394 group.add_option("--url", | 395 group.add_option("--url", |
| 395 help="Url where to grab a patch") | 396 help="Url where to grab a patch") |
| 396 group.add_option("--root", | 397 group.add_option("--root", |
| 397 help="Root to use for the patch; base subdirectory for " | 398 help="Root to use for the patch; base subdirectory for " |
| 398 "patch created in a subdirectory") | 399 "patch created in a subdirectory") |
| 399 group.add_option("--patchlevel", type='int', metavar="LEVEL", | 400 group.add_option("--patchlevel", type='int', metavar="LEVEL", |
| 400 help="Used as -pN parameter to patch") | 401 help="Used as -pN parameter to patch") |
| 402 group.add_option("--sub_rep", action="append", default=["."], |
| 403 help="Subcheckout to use in addition. This is mainly " |
| 404 "useful for gclient-style checkouts.") |
| 401 parser.add_option_group(group) | 405 parser.add_option_group(group) |
| 402 | 406 |
| 403 group = optparse.OptionGroup(parser, "Access the try server by HTTP") | 407 group = optparse.OptionGroup(parser, "Access the try server by HTTP") |
| 404 group.add_option("--use_http", | 408 group.add_option("--use_http", |
| 405 action="store_const", | 409 action="store_const", |
| 406 const=_SendChangeHTTP, | 410 const=_SendChangeHTTP, |
| 407 dest="send_patch", | 411 dest="send_patch", |
| 408 help="Use HTTP to talk to the try server [default]") | 412 help="Use HTTP to talk to the try server [default]") |
| 409 group.add_option("--host", | 413 group.add_option("--host", |
| 410 help="Host address") | 414 help="Host address") |
| (...skipping 22 matching lines...) Expand all Loading... |
| 433 # Switch the default accordingly if there was no default send_patch. | 437 # Switch the default accordingly if there was no default send_patch. |
| 434 if not options.send_patch: | 438 if not options.send_patch: |
| 435 if options.port and options.host: | 439 if options.port and options.host: |
| 436 options.send_patch = _SendChangeHTTP | 440 options.send_patch = _SendChangeHTTP |
| 437 elif options.svn_repo: | 441 elif options.svn_repo: |
| 438 options.send_patch = _SendChangeSVN | 442 options.send_patch = _SendChangeSVN |
| 439 else: | 443 else: |
| 440 parser.error('Please specify an access method.') | 444 parser.error('Please specify an access method.') |
| 441 | 445 |
| 442 try: | 446 try: |
| 447 # Process the VCS in any case at least to retrieve the email address. |
| 448 checkouts = [] |
| 449 for item in options.sub_rep: |
| 450 checkout = GuessVCS(options, item) |
| 451 if checkout.GetLocalRoot() in [c.GetLocalRoot() for c in checkouts]: |
| 452 parser.error('Specified the root %s two times.' % |
| 453 checkout.GetLocalRoot()) |
| 454 checkouts.append(checkout) |
| 455 |
| 443 # Convert options.diff into the content of the diff. | 456 # Convert options.diff into the content of the diff. |
| 444 if options.url: | 457 if options.url: |
| 445 if options.files: | 458 if options.files: |
| 446 parser.error('You cannot specify files and --url at the same time.') | 459 parser.error('You cannot specify files and --url at the same time.') |
| 447 options.diff = urllib.urlopen(options.url).read() | 460 options.diff = urllib.urlopen(options.url).read() |
| 448 elif options.diff: | 461 elif options.diff: |
| 449 if options.files: | 462 if options.files: |
| 450 parser.error('You cannot specify files and --diff at the same time.') | 463 parser.error('You cannot specify files and --diff at the same time.') |
| 451 options.diff = gclient_utils.FileRead(options.diff, 'rb') | 464 options.diff = gclient_utils.FileRead(options.diff, 'rb') |
| 452 # Process the VCS in any case at least to retrieve the email address. | 465 else: |
| 453 try: | 466 # Use this as the base. |
| 454 options.scm = GuessVCS(options) | 467 root = checkouts[0].GetLocalRoot() |
| 455 except NoTryServerAccess, e: | 468 diffs = [] |
| 456 # If we got the diff, we don't care. | 469 for checkout in checkouts: |
| 457 if not options.diff: | 470 diff = checkout.GenerateDiff().splitlines(True) |
| 458 # TODO(maruel): Raise what? | 471 # Munge it. |
| 459 raise | 472 path_diff = gclient_utils.PathDifference(root, checkout.GetLocalRoot()) |
| 473 for i in range(len(diff)): |
| 474 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): |
| 475 diff[i] = diff[i][0:3] + path_diff + diff[i][4:] |
| 476 diffs.extend(diff) |
| 477 options.diff = ''.join(diffs) |
| 460 | 478 |
| 461 # Get try slaves from PRESUBMIT.py files if not specified. | |
| 462 if not options.bot: | 479 if not options.bot: |
| 480 # Get try slaves from PRESUBMIT.py files if not specified. |
| 463 # Even if the diff comes from options.url, use the local checkout for bot | 481 # Even if the diff comes from options.url, use the local checkout for bot |
| 464 # selection. | 482 # selection. |
| 465 try: | 483 try: |
| 466 # Get try slaves from PRESUBMIT.py files if not specified. | |
| 467 import presubmit_support | 484 import presubmit_support |
| 468 root_presubmit = options.scm.GetBots() | 485 root_presubmit = checkouts[0].GetBots() |
| 469 options.bot = presubmit_support.DoGetTrySlaves( | 486 options.bot = presubmit_support.DoGetTrySlaves( |
| 470 options.scm.GetFileNames(), | 487 checkouts[0].GetFileNames(), |
| 471 options.scm.GetLocalRoot(), | 488 checkouts[0].GetLocalRoot(), |
| 472 root_presubmit, | 489 root_presubmit, |
| 473 False, | 490 False, |
| 474 sys.stdout) | 491 sys.stdout) |
| 475 except ImportError: | 492 except ImportError: |
| 476 pass | 493 pass |
| 477 # If no bot is specified, either the default pool will be selected or the | 494 # If no bot is specified, either the default pool will be selected or the |
| 478 # try server will refuse the job. Either case we don't need to interfere. | 495 # try server will refuse the job. Either case we don't need to interfere. |
| 479 | 496 |
| 480 if options.name is None: | 497 if options.name is None: |
| 481 if options.issue: | 498 if options.issue: |
| (...skipping 17 matching lines...) Expand all Loading... |
| 499 except (InvalidScript, NoTryServerAccess), e: | 516 except (InvalidScript, NoTryServerAccess), e: |
| 500 if swallow_exception: | 517 if swallow_exception: |
| 501 return 1 | 518 return 1 |
| 502 print e | 519 print e |
| 503 return 1 | 520 return 1 |
| 504 return 0 | 521 return 0 |
| 505 | 522 |
| 506 | 523 |
| 507 if __name__ == "__main__": | 524 if __name__ == "__main__": |
| 508 sys.exit(TryChange(None, [], False)) | 525 sys.exit(TryChange(None, [], False)) |
| OLD | NEW |