| 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 socket |
| 18 import subprocess | 18 import subprocess |
| 19 import sys | 19 import sys |
| 20 import tempfile | 20 import tempfile |
| 21 import traceback | |
| 22 import urllib | 21 import urllib |
| 23 | 22 |
| 24 import gcl | 23 import gcl |
| 25 import gclient | |
| 26 import gclient_scm | 24 import gclient_scm |
| 27 import presubmit_support | 25 import presubmit_support |
| 28 import upload | 26 import upload |
| 29 | 27 |
| 30 __version__ = '1.1.1' | 28 __version__ = '1.1.1' |
| 31 | 29 |
| 32 | 30 |
| 33 # Constants | 31 # Constants |
| 34 HELP_STRING = "Sorry, Tryserver is not available." | 32 HELP_STRING = "Sorry, Tryserver is not available." |
| 35 USAGE = r"""%prog [change_name] [options] | 33 USAGE = r"""%prog [change_name] [options] |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 127 raise NoTryServerAccess(' '.join(command) + '\nOuput:\n' + output) | 125 raise NoTryServerAccess(' '.join(command) + '\nOuput:\n' + output) |
| 128 return output | 126 return output |
| 129 | 127 |
| 130 | 128 |
| 131 class SCM(object): | 129 class SCM(object): |
| 132 """Simplistic base class to implement one function: ProcessOptions.""" | 130 """Simplistic base class to implement one function: ProcessOptions.""" |
| 133 def __init__(self, options): | 131 def __init__(self, options): |
| 134 self.options = options | 132 self.options = options |
| 135 | 133 |
| 136 def ProcessOptions(self): | 134 def ProcessOptions(self): |
| 137 raise Unimplemented | 135 raise NotImplementedError |
| 138 | 136 |
| 139 | 137 |
| 140 class SVN(SCM): | 138 class SVN(SCM): |
| 141 """Gathers the options and diff for a subversion checkout.""" | 139 """Gathers the options and diff for a subversion checkout.""" |
| 142 def GenerateDiff(self, files, root): | 140 def GenerateDiff(self, files, root): |
| 143 """Returns a string containing the diff for the given file list. | 141 """Returns a string containing the diff for the given file list. |
| 144 | 142 |
| 145 The files in the list should either be absolute paths or relative to the | 143 The files in the list should either be absolute paths or relative to the |
| 146 given root. If no root directory is provided, the repository root will be | 144 given root. If no root directory is provided, the repository root will be |
| 147 used. | 145 used. |
| 148 """ | 146 """ |
| 149 previous_cwd = os.getcwd() | 147 previous_cwd = os.getcwd() |
| 150 if root is None: | 148 if root is None: |
| 151 os.chdir(gcl.GetRepositoryRoot()) | 149 os.chdir(gcl.GetRepositoryRoot()) |
| 152 else: | 150 else: |
| 153 os.chdir(root) | 151 os.chdir(root) |
| 154 | 152 |
| 155 diff = [] | 153 diff = [] |
| 156 for file in files: | 154 for filename in files: |
| 157 # Use svn info output instead of os.path.isdir because the latter fails | 155 # Use svn info output instead of os.path.isdir because the latter fails |
| 158 # when the file is deleted. | 156 # when the file is deleted. |
| 159 if gclient_scm.CaptureSVNInfo(file).get("Node Kind") in ("dir", | 157 if gclient_scm.CaptureSVNInfo(filename).get("Node Kind") in ( |
| 160 "directory"): | 158 "dir", "directory"): |
| 161 continue | 159 continue |
| 162 # If the user specified a custom diff command in their svn config file, | 160 # If the user specified a custom diff command in their svn config file, |
| 163 # then it'll be used when we do svn diff, which we don't want to happen | 161 # then it'll be used when we do svn diff, which we don't want to happen |
| 164 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 162 # since we want the unified diff. Using --diff-cmd=diff doesn't always |
| 165 # work, since they can have another diff executable in their path that | 163 # work, since they can have another diff executable in their path that |
| 166 # gives different line endings. So we use a bogus temp directory as the | 164 # gives different line endings. So we use a bogus temp directory as the |
| 167 # config directory, which gets around these problems. | 165 # config directory, which gets around these problems. |
| 168 if sys.platform.startswith("win"): | 166 if sys.platform.startswith("win"): |
| 169 parent_dir = tempfile.gettempdir() | 167 parent_dir = tempfile.gettempdir() |
| 170 else: | 168 else: |
| 171 parent_dir = sys.path[0] # tempdir is not secure. | 169 parent_dir = sys.path[0] # tempdir is not secure. |
| 172 bogus_dir = os.path.join(parent_dir, "temp_svn_config") | 170 bogus_dir = os.path.join(parent_dir, "temp_svn_config") |
| 173 if not os.path.exists(bogus_dir): | 171 if not os.path.exists(bogus_dir): |
| 174 os.mkdir(bogus_dir) | 172 os.mkdir(bogus_dir) |
| 175 # Grabs the diff data. | 173 # Grabs the diff data. |
| 176 data = gcl.RunShell(["svn", "diff", "--config-dir", bogus_dir, file]) | 174 data = gcl.RunShell(["svn", "diff", "--config-dir", bogus_dir, filename]) |
| 177 | 175 |
| 178 # We know the diff will be incorrectly formatted. Fix it. | 176 # We know the diff will be incorrectly formatted. Fix it. |
| 179 if gcl.IsSVNMoved(file): | 177 if gcl.IsSVNMoved(filename): |
| 180 # The file is "new" in the patch sense. Generate a homebrew diff. | 178 # The file is "new" in the patch sense. Generate a homebrew diff. |
| 181 # We can't use ReadFile() since it's not using binary mode. | 179 # We can't use ReadFile() since it's not using binary mode. |
| 182 file_handle = open(file, 'rb') | 180 file_handle = open(filename, 'rb') |
| 183 file_content = file_handle.read() | 181 file_content = file_handle.read() |
| 184 file_handle.close() | 182 file_handle.close() |
| 185 # Prepend '+' to every lines. | 183 # Prepend '+' to every lines. |
| 186 file_content = ['+' + i for i in file_content.splitlines(True)] | 184 file_content = ['+' + i for i in file_content.splitlines(True)] |
| 187 nb_lines = len(file_content) | 185 nb_lines = len(file_content) |
| 188 # We need to use / since patch on unix will fail otherwise. | 186 # We need to use / since patch on unix will fail otherwise. |
| 189 file = file.replace('\\', '/') | 187 filename = filename.replace('\\', '/') |
| 190 data = "Index: %s\n" % file | 188 data = "Index: %s\n" % filename |
| 191 data += ("=============================================================" | 189 data += ("=============================================================" |
| 192 "======\n") | 190 "======\n") |
| 193 # Note: Should we use /dev/null instead? | 191 # Note: Should we use /dev/null instead? |
| 194 data += "--- %s\n" % file | 192 data += "--- %s\n" % filename |
| 195 data += "+++ %s\n" % file | 193 data += "+++ %s\n" % filename |
| 196 data += "@@ -0,0 +1,%d @@\n" % nb_lines | 194 data += "@@ -0,0 +1,%d @@\n" % nb_lines |
| 197 data += ''.join(file_content) | 195 data += ''.join(file_content) |
| 198 diff.append(data) | 196 diff.append(data) |
| 199 os.chdir(previous_cwd) | 197 os.chdir(previous_cwd) |
| 200 return "".join(diff) | 198 return "".join(diff) |
| 201 | 199 |
| 202 def GetFileNames(self): | 200 def GetFileNames(self): |
| 203 """Return the list of files in the diff.""" | 201 """Return the list of files in the diff.""" |
| 204 return self.change_info.GetFileNames() | 202 return self.change_info.GetFileNames() |
| 205 | 203 |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 248 # TODO: check for errors here? | 246 # TODO: check for errors here? |
| 249 root = upload.RunShell(['git', 'rev-parse', '--show-cdup']).strip() | 247 root = upload.RunShell(['git', 'rev-parse', '--show-cdup']).strip() |
| 250 return os.path.abspath(root) | 248 return os.path.abspath(root) |
| 251 | 249 |
| 252 def GetPatchName(self): | 250 def GetPatchName(self): |
| 253 """Construct a name for this patch.""" | 251 """Construct a name for this patch.""" |
| 254 # TODO: perhaps include the hash of the current commit, to distinguish | 252 # TODO: perhaps include the hash of the current commit, to distinguish |
| 255 # patches? | 253 # patches? |
| 256 branch = upload.RunShell(['git', 'symbolic-ref', 'HEAD']).strip() | 254 branch = upload.RunShell(['git', 'symbolic-ref', 'HEAD']).strip() |
| 257 if not branch.startswith('refs/heads/'): | 255 if not branch.startswith('refs/heads/'): |
| 258 raise "Couldn't figure out branch name" | 256 # TODO(maruel): Find a better type. |
| 257 raise NoTryServerAccess("Couldn't figure out branch name") |
| 259 branch = branch[len('refs/heads/'):] | 258 branch = branch[len('refs/heads/'):] |
| 260 return branch | 259 return branch |
| 261 | 260 |
| 262 def ProcessOptions(self): | 261 def ProcessOptions(self): |
| 263 if not self.options.diff: | 262 if not self.options.diff: |
| 264 self.options.diff = self.GenerateDiff() | 263 self.options.diff = self.GenerateDiff() |
| 265 if not self.options.name: | 264 if not self.options.name: |
| 266 self.options.name = self.GetPatchName() | 265 self.options.name = self.GetPatchName() |
| 267 if not self.options.email: | 266 if not self.options.email: |
| 268 self.options.email = self.GetEmail() | 267 self.options.email = self.GetEmail() |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 370 try: | 369 try: |
| 371 RunCommand(['svn', 'ls', full_url]) | 370 RunCommand(['svn', 'ls', full_url]) |
| 372 file_found = True | 371 file_found = True |
| 373 except NoTryServerAccess: | 372 except NoTryServerAccess: |
| 374 pass | 373 pass |
| 375 if file_found: | 374 if file_found: |
| 376 # The file already exists in the repo. Note that commiting a file is a | 375 # The file already exists in the repo. Note that commiting a file is a |
| 377 # no-op if the file's content (the diff) is not modified. This is why the | 376 # no-op if the file's content (the diff) is not modified. This is why the |
| 378 # file name contains the date and time. | 377 # file name contains the date and time. |
| 379 RunCommand(['svn', 'update', full_path]) | 378 RunCommand(['svn', 'update', full_path]) |
| 380 file = open(full_path, 'wb') | 379 f = open(full_path, 'wb') |
| 381 file.write(options.diff) | 380 f.write(options.diff) |
| 382 file.close() | 381 f.close() |
| 383 else: | 382 else: |
| 384 # Add the file to the repo | 383 # Add the file to the repo |
| 385 file = open(full_path, 'wb') | 384 f = open(full_path, 'wb') |
| 386 file.write(options.diff) | 385 f.write(options.diff) |
| 387 file.close() | 386 f.close() |
| 388 RunCommand(["svn", "add", full_path]) | 387 RunCommand(["svn", "add", full_path]) |
| 389 temp_file.write(description) | 388 temp_file.write(description) |
| 390 temp_file.flush() | 389 temp_file.flush() |
| 391 RunCommand(["svn", "commit", full_path, '--file', | 390 RunCommand(["svn", "commit", full_path, '--file', |
| 392 temp_file_name]) | 391 temp_file_name]) |
| 393 finally: | 392 finally: |
| 394 temp_file.close() | 393 temp_file.close() |
| 395 shutil.rmtree(temp_dir, True) | 394 shutil.rmtree(temp_dir, True) |
| 396 | 395 |
| 397 | 396 |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 565 options.diff = urllib.urlopen(options.url).read() | 564 options.diff = urllib.urlopen(options.url).read() |
| 566 elif options.diff: | 565 elif options.diff: |
| 567 options.diff = gcl.ReadFile(options.diff) | 566 options.diff = gcl.ReadFile(options.diff) |
| 568 # Process the VCS in any case at least to retrieve the email address. | 567 # Process the VCS in any case at least to retrieve the email address. |
| 569 try: | 568 try: |
| 570 options.scm = GuessVCS(options) | 569 options.scm = GuessVCS(options) |
| 571 options.scm.ProcessOptions() | 570 options.scm.ProcessOptions() |
| 572 except NoTryServerAccess, e: | 571 except NoTryServerAccess, e: |
| 573 # If we got the diff, we don't care. | 572 # If we got the diff, we don't care. |
| 574 if not options.diff: | 573 if not options.diff: |
| 574 # TODO(maruel): Raise what? |
| 575 raise | 575 raise |
| 576 | 576 |
| 577 # Get try slaves from PRESUBMIT.py files if not specified. | 577 # Get try slaves from PRESUBMIT.py files if not specified. |
| 578 if not options.bot: | 578 if not options.bot: |
| 579 if options.url: | 579 if options.url: |
| 580 print('You need to specify which bots to use.') | 580 print('You need to specify which bots to use.') |
| 581 return 1 | 581 return 1 |
| 582 root_presubmit = gcl.GetCachedFile('PRESUBMIT.py', use_root=True) | 582 root_presubmit = gcl.GetCachedFile('PRESUBMIT.py', use_root=True) |
| 583 options.bot = presubmit_support.DoGetTrySlaves(options.scm.GetFileNames(), | 583 options.bot = presubmit_support.DoGetTrySlaves(options.scm.GetFileNames(), |
| 584 options.scm.GetLocalRoot(), | 584 options.scm.GetLocalRoot(), |
| 585 root_presubmit, | 585 root_presubmit, |
| 586 False, | 586 False, |
| 587 sys.stdout) | 587 sys.stdout) |
| 588 | 588 |
| 589 if options.name is None: | 589 if options.name is None: |
| 590 if options.issue: | 590 if options.issue: |
| 591 patch_name = 'Issue %s' % options.issue | 591 options.name = 'Issue %s' % options.issue |
| 592 else: | 592 else: |
| 593 options.name = 'Unnamed' | 593 options.name = 'Unnamed' |
| 594 print('Note: use --name NAME to change the try job name.') | 594 print('Note: use --name NAME to change the try job name.') |
| 595 if not options.email: | 595 if not options.email: |
| 596 print('Warning: TRYBOT_RESULTS_EMAIL_ADDRESS is not set. Try server ' | 596 print('Warning: TRYBOT_RESULTS_EMAIL_ADDRESS is not set. Try server ' |
| 597 'results might\ngo to: %s@google.com.\n' % options.user) | 597 'results might\ngo to: %s@google.com.\n' % options.user) |
| 598 else: | 598 else: |
| 599 print('Results will be emailed to: ' + options.email) | 599 print('Results will be emailed to: ' + options.email) |
| 600 | 600 |
| 601 # Send the patch. | 601 # Send the patch. |
| 602 options.send_patch(options) | 602 options.send_patch(options) |
| 603 print 'Patch \'%s\' sent to try server: %s' % (options.name, | 603 print 'Patch \'%s\' sent to try server: %s' % (options.name, |
| 604 ', '.join(options.bot)) | 604 ', '.join(options.bot)) |
| 605 except (InvalidScript, NoTryServerAccess), e: | 605 except (InvalidScript, NoTryServerAccess), e: |
| 606 if swallow_exception: | 606 if swallow_exception: |
| 607 return 1 | 607 return 1 |
| 608 print e | 608 print e |
| 609 return 1 | 609 return 1 |
| 610 return 0 | 610 return 0 |
| 611 | 611 |
| 612 | 612 |
| 613 if __name__ == "__main__": | 613 if __name__ == "__main__": |
| 614 sys.exit(TryChange(None, None, False)) | 614 sys.exit(TryChange(None, None, False)) |
| OLD | NEW |