| 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 getpass | 11 import getpass |
| 12 import logging | 12 import logging |
| 13 import optparse | 13 import optparse |
| 14 import os | 14 import os |
| 15 import shutil | 15 import shutil |
| 16 import socket | 16 import socket |
| 17 import subprocess | 17 import subprocess |
| 18 import sys | 18 import sys |
| 19 import tempfile | 19 import tempfile |
| 20 import urllib | 20 import urllib |
| 21 | 21 |
| 22 import breakpad | 22 import breakpad |
| 23 | 23 |
| 24 import gcl | 24 import gcl |
| 25 import gclient_utils | 25 import gclient_utils |
| 26 import scm | 26 import scm |
| 27 import presubmit_support | 27 import presubmit_support |
| 28 import upload | |
| 29 | 28 |
| 30 __version__ = '1.1.2' | 29 __version__ = '1.2' |
| 31 | 30 |
| 32 | 31 |
| 33 # Constants | 32 # Constants |
| 34 HELP_STRING = "Sorry, Tryserver is not available." | 33 HELP_STRING = "Sorry, Tryserver is not available." |
| 35 USAGE = r"""%prog [change_name] [options] | 34 USAGE = r"""%prog [change_name] [options] |
| 36 | 35 |
| 37 Client-side script to send a try job to the try server. It communicates to | 36 Client-side script to send a try job to the try server. It communicates to |
| 38 the try server by either writting to a svn repository or by directly connecting | 37 the try server by either writting to a svn repository or by directly connecting |
| 39 to the server by HTTP. | 38 to the server by HTTP. |
| 40 | 39 |
| (...skipping 18 matching lines...) Expand all Loading... |
| 59 class InvalidScript(Exception): | 58 class InvalidScript(Exception): |
| 60 def __str__(self): | 59 def __str__(self): |
| 61 return self.args[0] + '\n' + HELP_STRING | 60 return self.args[0] + '\n' + HELP_STRING |
| 62 | 61 |
| 63 | 62 |
| 64 class NoTryServerAccess(Exception): | 63 class NoTryServerAccess(Exception): |
| 65 def __str__(self): | 64 def __str__(self): |
| 66 return self.args[0] + '\n' + HELP_STRING | 65 return self.args[0] + '\n' + HELP_STRING |
| 67 | 66 |
| 68 | 67 |
| 69 def PathDifference(root, subpath): | |
| 70 """Returns the difference subpath minus root.""" | |
| 71 if subpath.find(root) != 0: | |
| 72 return None | |
| 73 # If the root does not have a trailing \ or /, we add it so the returned path | |
| 74 # starts immediately after the seperator regardless of whether it is provided. | |
| 75 if not root.endswith(os.sep): | |
| 76 root += os.sep | |
| 77 return subpath[len(root):] | |
| 78 | |
| 79 | |
| 80 def GetSourceRoot(): | |
| 81 """Returns the absolute directory one level up from the repository root.""" | |
| 82 # TODO(maruel): This is odd to assume that '..' is the source root. | |
| 83 return os.path.abspath(os.path.join(gcl.GetRepositoryRoot(), '..')) | |
| 84 | |
| 85 | |
| 86 def GetTryServerSettings(): | 68 def GetTryServerSettings(): |
| 87 """Grab try server settings local to the repository.""" | 69 """Grab try server settings local to the repository.""" |
| 88 def _SafeResolve(host): | 70 def _SafeResolve(host): |
| 89 try: | 71 try: |
| 90 return socket.getaddrinfo(host, None) | 72 return socket.getaddrinfo(host, None) |
| 91 except socket.gaierror: | 73 except socket.gaierror: |
| 92 return None | 74 return None |
| 93 | 75 |
| 94 settings = {} | 76 settings = {} |
| 95 settings['http_port'] = gcl.GetCodeReviewSetting('TRYSERVER_HTTP_PORT') | 77 settings['http_port'] = gcl.GetCodeReviewSetting('TRYSERVER_HTTP_PORT') |
| (...skipping 21 matching lines...) Expand all Loading... |
| 117 | 99 |
| 118 def EscapeDot(name): | 100 def EscapeDot(name): |
| 119 return name.replace('.', '-') | 101 return name.replace('.', '-') |
| 120 | 102 |
| 121 | 103 |
| 122 class SCM(object): | 104 class SCM(object): |
| 123 """Simplistic base class to implement one function: ProcessOptions.""" | 105 """Simplistic base class to implement one function: ProcessOptions.""" |
| 124 def __init__(self, options): | 106 def __init__(self, options): |
| 125 self.options = options | 107 self.options = options |
| 126 | 108 |
| 127 def ProcessOptions(self): | 109 def GetFileNames(self): |
| 128 raise NotImplementedError | 110 """Return the list of files in the diff.""" |
| 111 return self.options.files |
| 129 | 112 |
| 130 | 113 |
| 131 class SVN(SCM): | 114 class SVN(SCM): |
| 132 """Gathers the options and diff for a subversion checkout.""" | 115 """Gathers the options and diff for a subversion checkout.""" |
| 133 def GenerateDiff(self, files, root): | 116 def __init__(self, *args, **kwargs): |
| 117 SCM.__init__(self, *args, **kwargs) |
| 118 self.checkout_root = scm.SVN.GetCheckoutRoot(os.getcwd()) |
| 119 self.options.files |
| 120 if not self.options.diff: |
| 121 # Generate the diff from the scm. |
| 122 self.options.diff = self._GenerateDiff() |
| 123 if not self.options.email: |
| 124 # Assumes the svn credential is an email address. |
| 125 self.options.email = scm.SVN.GetEmail(self.checkout_root) |
| 126 |
| 127 def _GenerateDiff(self): |
| 134 """Returns a string containing the diff for the given file list. | 128 """Returns a string containing the diff for the given file list. |
| 135 | 129 |
| 136 The files in the list should either be absolute paths or relative to the | 130 The files in the list should either be absolute paths or relative to the |
| 137 given root. If no root directory is provided, the repository root will be | 131 given root. |
| 138 used. | |
| 139 """ | 132 """ |
| 140 previous_cwd = os.getcwd() | 133 previous_cwd = os.getcwd() |
| 141 os.chdir(root or scm.SVN.GetCheckoutRoot(previous_cwd)) | 134 os.chdir(self.checkout_root) |
| 142 | 135 if not self.options.files: |
| 136 self.options.files = [f[1] for f in scm.SVN.CaptureStatus(None)] |
| 143 # Directories will return None so filter them out. | 137 # Directories will return None so filter them out. |
| 144 diff = filter(None, [scm.SVN.DiffItem(f) for f in files]) | 138 diff = filter(None, [scm.SVN.DiffItem(f) for f in self.options.files]) |
| 145 os.chdir(previous_cwd) | 139 os.chdir(previous_cwd) |
| 146 return "".join(diff) | 140 return "".join(diff) |
| 147 | 141 |
| 148 def GetFileNames(self): | |
| 149 """Return the list of files in the diff.""" | |
| 150 return self.change_info.GetFileNames() | |
| 151 | |
| 152 def GetLocalRoot(self): | 142 def GetLocalRoot(self): |
| 153 """Return the path of the repository root.""" | 143 """Return the path of the repository root.""" |
| 154 return self.change_info.GetLocalRoot() | 144 return self.checkout_root |
| 155 | |
| 156 def ProcessOptions(self): | |
| 157 checkout_root = None | |
| 158 if not self.options.diff: | |
| 159 # Generate the diff with svn and write it to the submit queue path. The | |
| 160 # files are relative to the repository root, but we need patches relative | |
| 161 # to one level up from there (i.e., 'src'), so adjust both the file | |
| 162 # paths and the root of the diff. | |
| 163 # TODO(maruel): Remove this hack. | |
| 164 source_root = GetSourceRoot() | |
| 165 checkout_root = scm.SVN.GetCheckoutRoot(os.getcwd()) | |
| 166 prefix = PathDifference(source_root, checkout_root) | |
| 167 adjusted_paths = [os.path.join(prefix, x) for x in self.options.files] | |
| 168 self.options.diff = self.GenerateDiff(adjusted_paths, root=source_root) | |
| 169 self.change_info = gcl.LoadChangelistInfoForMultiple(self.options.name, | |
| 170 gcl.GetRepositoryRoot(), True, True) | |
| 171 if not self.options.email: | |
| 172 checkout_root = checkout_root or scm.SVN.GetCheckoutRoot(os.getcwd()) | |
| 173 self.options.email = scm.SVN.GetEmail(checkout_root) | |
| 174 | 145 |
| 175 | 146 |
| 176 class GIT(SCM): | 147 class GIT(SCM): |
| 177 """Gathers the options and diff for a git checkout.""" | 148 """Gathers the options and diff for a git checkout.""" |
| 178 def GenerateDiff(self): | 149 def __init__(self, *args, **kwargs): |
| 150 SCM.__init__(self, *args, **kwargs) |
| 151 self.checkout_root = os.path.abspath( |
| 152 gclient_utils.CheckCall(['git', 'rev-parse', '--show-cdup']).strip()) |
| 153 if not self.options.diff: |
| 154 self.options.diff = self._GenerateDiff() |
| 155 if not self.options.name: |
| 156 self.options.name = self._GetPatchName() |
| 157 if not self.options.email: |
| 158 self.options.email = scm.GIT.GetEmail('.') |
| 159 |
| 160 def _GenerateDiff(self): |
| 179 """Get the diff we'll send to the try server. We ignore the files list.""" | 161 """Get the diff we'll send to the try server. We ignore the files list.""" |
| 180 branch = upload.RunShell(['git', 'cl', 'upstream']).strip() | 162 branch = gclient_utils.CheckCall(['git', 'cl', 'upstream']).strip() |
| 181 diff = upload.RunShell(['git', 'diff-tree', '-p', '--no-prefix', | 163 diff = gclient_utils.CheckCall(['git', 'diff-tree', '-p', '--no-prefix', |
| 182 branch, 'HEAD']).splitlines(True) | 164 branch, 'HEAD']).splitlines(True) |
| 183 for i in range(len(diff)): | 165 for i in range(len(diff)): |
| 184 # In the case of added files, replace /dev/null with the path to the | 166 # In the case of added files, replace /dev/null with the path to the |
| 185 # file being added. | 167 # file being added. |
| 186 if diff[i].startswith('--- /dev/null'): | 168 if diff[i].startswith('--- /dev/null'): |
| 187 diff[i] = '--- %s' % diff[i+1][4:] | 169 diff[i] = '--- %s' % diff[i+1][4:] |
| 188 return ''.join(diff) | 170 return ''.join(diff) |
| 189 | 171 |
| 190 def GetFileNames(self): | 172 def _GetPatchName(self): |
| 191 """Return the list of files in the diff.""" | |
| 192 return self.options.files | |
| 193 | |
| 194 def GetLocalRoot(self): | |
| 195 """Return the path of the repository root.""" | |
| 196 # TODO: check for errors here? | |
| 197 root = upload.RunShell(['git', 'rev-parse', '--show-cdup']).strip() | |
| 198 return os.path.abspath(root) | |
| 199 | |
| 200 def GetPatchName(self): | |
| 201 """Construct a name for this patch.""" | 173 """Construct a name for this patch.""" |
| 202 # TODO: perhaps include the hash of the current commit, to distinguish | 174 # TODO: perhaps include the hash of the current commit, to distinguish |
| 203 # patches? | 175 # patches? |
| 204 branch = upload.RunShell(['git', 'symbolic-ref', 'HEAD']).strip() | 176 branch = gclient_utils.CheckCall(['git', 'symbolic-ref', 'HEAD']).strip() |
| 205 if not branch.startswith('refs/heads/'): | 177 if not branch.startswith('refs/heads/'): |
| 206 # TODO(maruel): Find a better type. | 178 # TODO(maruel): Find a better type. |
| 207 raise NoTryServerAccess("Couldn't figure out branch name") | 179 raise NoTryServerAccess("Couldn't figure out branch name") |
| 208 branch = branch[len('refs/heads/'):] | 180 branch = branch[len('refs/heads/'):] |
| 209 return branch | 181 return branch |
| 210 | 182 |
| 211 def ProcessOptions(self): | 183 def GetLocalRoot(self): |
| 212 if not self.options.diff: | 184 """Return the path of the repository root.""" |
| 213 self.options.diff = self.GenerateDiff() | 185 return self.checkout_root |
| 214 if not self.options.name: | |
| 215 self.options.name = self.GetPatchName() | |
| 216 if not self.options.email: | |
| 217 self.options.email = scm.GIT.GetEmail('.') | |
| 218 | 186 |
| 219 | 187 |
| 220 def _ParseSendChangeOptions(options): | 188 def _ParseSendChangeOptions(options): |
| 221 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" | 189 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" |
| 222 values = {} | 190 values = {} |
| 223 if options.email: | 191 if options.email: |
| 224 values['email'] = options.email | 192 values['email'] = options.email |
| 225 values['user'] = options.user | 193 values['user'] = options.user |
| 226 values['name'] = options.name | 194 values['name'] = options.name |
| 227 if options.bot: | 195 if options.bot: |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 358 """ | 326 """ |
| 359 __pychecker__ = 'no-returnvalues' | 327 __pychecker__ = 'no-returnvalues' |
| 360 # Subversion has a .svn in all working directories. | 328 # Subversion has a .svn in all working directories. |
| 361 if os.path.isdir('.svn'): | 329 if os.path.isdir('.svn'): |
| 362 logging.info("Guessed VCS = Subversion") | 330 logging.info("Guessed VCS = Subversion") |
| 363 return SVN(options) | 331 return SVN(options) |
| 364 | 332 |
| 365 # Git has a command to test if you're in a git tree. | 333 # Git has a command to test if you're in a git tree. |
| 366 # Try running it, but don't die if we don't have git installed. | 334 # Try running it, but don't die if we don't have git installed. |
| 367 try: | 335 try: |
| 368 out, returncode = subprocess.Popen( | 336 gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"]) |
| 369 ["git", "rev-parse", "--is-inside-work-tree"], | 337 logging.info("Guessed VCS = Git") |
| 370 shell=sys.platform.startswith('win'), | 338 return GIT(options) |
| 371 stdout=subprocess.PIPE).communicate()[0] | 339 except gclient_utils.CheckCallError, e: |
| 372 if returncode == 0: | 340 if e.retcode != 2: # ENOENT -- they don't have git installed. |
| 373 logging.info("Guessed VCS = Git") | |
| 374 return GIT(options) | |
| 375 except OSError, (errno, message): | |
| 376 if errno != 2: # ENOENT -- they don't have git installed. | |
| 377 raise | 341 raise |
| 378 | |
| 379 raise NoTryServerAccess("Could not guess version control system. " | 342 raise NoTryServerAccess("Could not guess version control system. " |
| 380 "Are you in a working copy directory?") | 343 "Are you in a working copy directory?") |
| 381 | 344 |
| 382 | 345 |
| 383 def TryChange(argv, | 346 def TryChange(argv, |
| 384 file_list, | 347 file_list, |
| 385 swallow_exception, | 348 swallow_exception, |
| 386 prog=None): | 349 prog=None): |
| 387 """ | 350 """ |
| 388 Args: | 351 Args: |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 513 | 476 |
| 514 try: | 477 try: |
| 515 # Convert options.diff into the content of the diff. | 478 # Convert options.diff into the content of the diff. |
| 516 if options.url: | 479 if options.url: |
| 517 options.diff = urllib.urlopen(options.url).read() | 480 options.diff = urllib.urlopen(options.url).read() |
| 518 elif options.diff: | 481 elif options.diff: |
| 519 options.diff = gclient_utils.FileRead(options.diff, 'rb') | 482 options.diff = gclient_utils.FileRead(options.diff, 'rb') |
| 520 # Process the VCS in any case at least to retrieve the email address. | 483 # Process the VCS in any case at least to retrieve the email address. |
| 521 try: | 484 try: |
| 522 options.scm = GuessVCS(options) | 485 options.scm = GuessVCS(options) |
| 523 options.scm.ProcessOptions() | |
| 524 except NoTryServerAccess, e: | 486 except NoTryServerAccess, e: |
| 525 # If we got the diff, we don't care. | 487 # If we got the diff, we don't care. |
| 526 if not options.diff: | 488 if not options.diff: |
| 527 # TODO(maruel): Raise what? | 489 # TODO(maruel): Raise what? |
| 528 raise | 490 raise |
| 529 | 491 |
| 530 # Get try slaves from PRESUBMIT.py files if not specified. | 492 # Get try slaves from PRESUBMIT.py files if not specified. |
| 531 if not options.bot: | 493 if not options.bot: |
| 532 if options.url: | 494 if options.url: |
| 533 parser.error('You need to specify which bots to use.') | 495 parser.error('You need to specify which bots to use.') |
| (...skipping 23 matching lines...) Expand all Loading... |
| 557 except (InvalidScript, NoTryServerAccess), e: | 519 except (InvalidScript, NoTryServerAccess), e: |
| 558 if swallow_exception: | 520 if swallow_exception: |
| 559 return 1 | 521 return 1 |
| 560 print e | 522 print e |
| 561 return 1 | 523 return 1 |
| 562 return 0 | 524 return 0 |
| 563 | 525 |
| 564 | 526 |
| 565 if __name__ == "__main__": | 527 if __name__ == "__main__": |
| 566 sys.exit(TryChange(None, [], False)) | 528 sys.exit(TryChange(None, [], False)) |
| OLD | NEW |