| 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 | 
|---|