Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 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 | 5 |
| 6 """Client-side script to send a try job to the try server. It communicates to | 6 """Client-side script to send a try job to the try server. It communicates to |
| 7 the try server by either writting to a svn/git repository or by directly | 7 the try server by either writting to a svn/git repository or by directly |
| 8 connecting to the server by HTTP. | 8 connecting to the server by HTTP. |
| 9 """ | 9 """ |
| 10 | 10 |
| 11 import contextlib | 11 import contextlib |
| 12 import datetime | 12 import datetime |
| 13 import errno | 13 import errno |
| 14 import getpass | 14 import getpass |
| 15 import itertools | 15 import itertools |
| 16 import json | 16 import json |
| 17 import logging | 17 import logging |
| 18 import optparse | 18 import optparse |
| 19 import os | 19 import os |
| 20 import posixpath | 20 import posixpath |
| 21 import re | 21 import re |
| 22 import shutil | 22 import shutil |
| 23 import sys | 23 import sys |
| 24 import tempfile | 24 import tempfile |
| 25 import urllib | 25 import urllib |
| 26 import urllib2 | 26 import urllib2 |
| 27 import urlparse | 27 import urlparse |
| 28 | 28 |
| 29 import breakpad # pylint: disable=W0611 | |
| 30 | |
| 31 import fix_encoding | |
| 32 import gcl | |
| 33 import gclient_utils | 29 import gclient_utils |
| 34 import gerrit_util | 30 import git_cl |
| 35 import scm | 31 import scm |
| 36 import subprocess2 | 32 import subprocess2 |
| 37 | 33 |
| 38 | 34 |
| 39 __version__ = '1.2' | 35 __version__ = '1.2' |
| 40 | 36 |
| 41 | 37 |
| 42 # Constants | 38 # Constants |
| 43 HELP_STRING = "Sorry, Tryserver is not available." | 39 HELP_STRING = "Sorry, Tryserver is not available." |
| 44 USAGE = r"""%prog [options] | 40 USAGE = r"""%prog [options] |
| 45 | 41 |
| 46 Client-side script to send a try job to the try server. It communicates to | 42 Client-side script to send a try job to the try server. It communicates to |
| 47 the try server by either writting to a svn repository or by directly connecting | 43 the try server by writting to a svn repository.""" |
| 48 to the server by HTTP.""" | |
| 49 | 44 |
| 50 EPILOG = """ | 45 EPILOG = """ |
| 51 Examples: | 46 Examples: |
| 52 Send a patch directly from rietveld: | 47 Send a patch directly from rietveld: |
| 53 %(prog)s -R codereview.chromium.org/1337 | 48 %(prog)s -R codereview.chromium.org/1337 |
| 54 --email recipient@example.com --root src | 49 --email recipient@example.com --root src |
| 55 | 50 |
| 56 Try a change against a particular revision: | 51 Try a change against a particular revision: |
| 57 %(prog)s -r 123 | 52 %(prog)s -r 123 |
| 58 | 53 |
| 59 Try a change including changes to a sub repository: | 54 Try a change including changes to a sub repository: |
| 60 %(prog)s -s third_party/WebKit | 55 %(prog)s -s third_party/WebKit |
| 61 | 56 |
| 62 A git patch off a web site (git inserts a/ and b/) and fix the base dir: | 57 A git patch off a web site (git inserts a/ and b/) and fix the base dir: |
| 63 %(prog)s --url http://url/to/patch.diff --patchlevel 1 --root src | 58 %(prog)s --url http://url/to/patch.diff --patchlevel 1 --root src |
| 64 | 59 |
| 65 Use svn to store the try job, specify an alternate email address and use a | 60 Use svn to store the try job, specify an alternate email address and use a |
| 66 premade diff file on the local drive: | 61 premade diff file on the local drive: |
| 67 %(prog)s --email user@example.com | 62 %(prog)s --email user@example.com |
| 68 --svn_repo svn://svn.chromium.org/chrome-try/try --diff foo.diff | 63 --svn_repo svn://svn.chromium.org/chrome-try/try --diff foo.diff |
| 69 | 64 |
| 70 Running only on a 'mac' slave with revision 123 and clobber first; specify | 65 Running only on a 'mac' slave with revision 123 and clobber first; specify |
| 71 manually the 3 source files to use for the try job: | 66 manually the 3 source files to use for the try job: |
| 72 %(prog)s --bot mac --revision 123 --clobber -f src/a.cc -f src/a.h | 67 %(prog)s --bot mac --revision 123 --clobber -f src/a.cc -f src/a.h |
| 73 -f include/b.h | 68 -f include/b.h |
| 74 """ | 69 """ |
| 75 | 70 |
| 76 GIT_PATCH_DIR_BASENAME = os.path.join('git-try', 'patches-git') | 71 GIT_PATCH_DIR_BASENAME = os.path.join('git-try', 'patches-git') |
| 77 GIT_BRANCH_FILE = 'ref' | 72 GIT_BRANCH_FILE = 'ref' |
| 78 _GIT_PUSH_ATTEMPTS = 3 | 73 |
| 79 | 74 |
| 80 def DieWithError(message): | 75 def DieWithError(message): |
| 81 print >> sys.stderr, message | 76 print >> sys.stderr, message |
| 82 sys.exit(1) | 77 sys.exit(1) |
| 83 | 78 |
| 84 | 79 |
| 85 def RunCommand(args, error_ok=False, error_message=None, **kwargs): | 80 def RunCommand(args, error_ok=False, error_message=None, **kwargs): |
| 86 try: | 81 try: |
| 87 return subprocess2.check_output(args, shell=False, **kwargs) | 82 return subprocess2.check_output(args, shell=False, **kwargs) |
| 88 except subprocess2.CalledProcessError, e: | 83 except subprocess2.CalledProcessError, e: |
| 89 if not error_ok: | 84 if not error_ok: |
| 90 DieWithError( | 85 DieWithError( |
| 91 'Command "%s" failed.\n%s' % ( | 86 'Command "%s" failed.\n%s' % ( |
| 92 ' '.join(args), error_message or e.stdout or '')) | 87 ' '.join(args), error_message or e.stdout or '')) |
| 93 return e.stdout | 88 return e.stdout |
| 94 | 89 |
| 95 | 90 |
| 96 def RunGit(args, **kwargs): | 91 def RunGit(args, **kwargs): |
| 97 """Returns stdout.""" | 92 """Returns stdout.""" |
| 98 return RunCommand(['git'] + args, **kwargs) | 93 return RunCommand(['git'] + args, **kwargs) |
| 99 | 94 |
| 95 | |
| 100 class Error(Exception): | 96 class Error(Exception): |
| 101 """An error during a try job submission. | 97 """An error during a try job submission. |
| 102 | 98 |
| 103 For this error, trychange.py does not display stack trace, only message | 99 For this error, trychange.py does not display stack trace, only message |
| 104 """ | 100 """ |
| 105 | 101 |
| 106 class InvalidScript(Error): | |
| 107 def __str__(self): | |
| 108 return self.args[0] + '\n' + HELP_STRING | |
| 109 | |
| 110 | 102 |
| 111 class NoTryServerAccess(Error): | 103 class NoTryServerAccess(Error): |
| 112 def __str__(self): | 104 def __str__(self): |
| 113 return self.args[0] + '\n' + HELP_STRING | 105 return self.args[0] + '\n' + HELP_STRING |
| 114 | 106 |
| 107 | |
| 115 def Escape(name): | 108 def Escape(name): |
| 116 """Escapes characters that could interfere with the file system or try job | 109 """Escapes characters that could interfere with the file system or try job |
| 117 parsing. | 110 parsing. |
| 118 """ | 111 """ |
| 119 return re.sub(r'[^\w#-]', '_', name) | 112 return re.sub(r'[^\w#-]', '_', name) |
| 120 | 113 |
| 121 | 114 |
| 122 class SCM(object): | 115 class GIT(object): |
| 123 """Simplistic base class to implement one function: ProcessOptions.""" | 116 |
| 117 """Gathers the options and diff for a git checkout.""" | |
| 124 def __init__(self, options, path, file_list): | 118 def __init__(self, options, path, file_list): |
| 125 items = path.split('@') | 119 items = path.split('@') |
| 126 assert len(items) <= 2 | 120 assert len(items) <= 2 |
| 127 self.checkout_root = os.path.abspath(items[0]) | 121 self.checkout_root = os.path.abspath(items[0]) |
| 128 items.append(None) | 122 items.append(None) |
| 129 self.diff_against = items[1] | 123 self.diff_against = items[1] |
| 130 self.options = options | 124 self.options = options |
| 131 # Lazy-load file list from the SCM unless files were specified in options. | 125 # Lazy-load file list from the SCM unless files were specified in options. |
| 132 self._files = None | 126 self._files = None |
| 133 self._file_tuples = None | 127 self._file_tuples = None |
| 134 if file_list: | 128 if file_list: |
| 135 self._files = file_list | 129 self._files = file_list |
| 136 self._file_tuples = [('M', f) for f in self.files] | 130 self._file_tuples = [('M', f) for f in self.files] |
| 137 self.options.files = None | 131 self.options.files = None |
| 138 self.codereview_settings = None | 132 self.codereview_settings = None |
| 139 self.codereview_settings_file = 'codereview.settings' | 133 self.codereview_settings_file = 'codereview.settings' |
| 140 self.toplevel_root = None | 134 self.toplevel_root = None |
| 141 | 135 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) |
| 142 def GetFileNames(self): | 136 if not self.options.name: |
| 143 """Return the list of files in the diff.""" | 137 self.options.name = scm.GIT.GetPatchName(self.checkout_root) |
| 144 return self.files | 138 if not self.options.email: |
| 139 self.options.email = scm.GIT.GetEmail(self.checkout_root) | |
| 140 if not self.diff_against: | |
| 141 self.diff_against = scm.GIT.GetUpstreamBranch(self.checkout_root) | |
| 142 if not self.diff_against: | |
| 143 raise NoTryServerAccess( | |
| 144 "Unable to determine default branch to diff against. " | |
| 145 "Verify this branch is set up to track another" | |
| 146 "(via the --track argument to \"git checkout -b ...\"") | |
| 147 logging.info("GIT(%s)" % self.checkout_root) | |
| 145 | 148 |
| 146 def GetCodeReviewSetting(self, key): | 149 def GetCodeReviewSetting(self, key): |
| 147 """Returns a value for the given key for this repository. | |
| 148 | |
| 149 Uses gcl-style settings from the repository. | |
| 150 """ | |
| 151 if gcl: | |
| 152 gcl_setting = gcl.GetCodeReviewSetting(key) | |
| 153 if gcl_setting != '': | |
| 154 return gcl_setting | |
| 155 if self.codereview_settings is None: | 150 if self.codereview_settings is None: |
| 156 self.codereview_settings = {} | 151 self.codereview_settings = {} |
| 157 settings_file = self.ReadRootFile(self.codereview_settings_file) | 152 settings_file = self.ReadRootFile(self.codereview_settings_file) |
| 158 if settings_file: | 153 if settings_file: |
| 159 for line in settings_file.splitlines(): | 154 for line in settings_file.splitlines(): |
| 160 if not line or line.lstrip().startswith('#'): | 155 if not line or line.lstrip().startswith('#'): |
| 161 continue | 156 continue |
| 162 k, v = line.split(":", 1) | 157 k, v = line.split(":", 1) |
| 163 self.codereview_settings[k.strip()] = v.strip() | 158 self.codereview_settings[k.strip()] = v.strip() |
| 164 return self.codereview_settings.get(key, '') | 159 return self.codereview_settings.get(key, '') |
| 165 | 160 |
| 166 def _GclStyleSettings(self): | 161 def _GclStyleSettings(self): |
| 167 """Set default settings based on the gcl-style settings from the repository. | 162 """Set default settings based on the gcl-style settings from the repository. |
| 168 | 163 |
| 169 The settings in the self.options object will only be set if no previous | 164 The settings in the self.options object will only be set if no previous |
| 170 value exists (i.e. command line flags to the try command will override the | 165 value exists (i.e. command line flags to the try command will override the |
| 171 settings in codereview.settings). | 166 settings in codereview.settings). |
| 172 """ | 167 """ |
| 173 settings = { | 168 settings = { |
| 174 'port': self.GetCodeReviewSetting('TRYSERVER_HTTP_PORT'), | |
| 175 'host': self.GetCodeReviewSetting('TRYSERVER_HTTP_HOST'), | |
| 176 'svn_repo': self.GetCodeReviewSetting('TRYSERVER_SVN_URL'), | 169 'svn_repo': self.GetCodeReviewSetting('TRYSERVER_SVN_URL'), |
| 177 'gerrit_url': self.GetCodeReviewSetting('TRYSERVER_GERRIT_URL'), | |
| 178 'git_repo': self.GetCodeReviewSetting('TRYSERVER_GIT_URL'), | |
| 179 'project': self.GetCodeReviewSetting('TRYSERVER_PROJECT'), | 170 'project': self.GetCodeReviewSetting('TRYSERVER_PROJECT'), |
| 180 # Primarily for revision=auto | 171 # Primarily for revision=auto |
| 181 'revision': self.GetCodeReviewSetting('TRYSERVER_REVISION'), | 172 'revision': self.GetCodeReviewSetting('TRYSERVER_REVISION'), |
| 182 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'), | 173 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'), |
| 183 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'), | 174 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'), |
| 184 } | 175 } |
| 185 logging.info('\n'.join(['%s: %s' % (k, v) | 176 logging.info('\n'.join(['%s: %s' % (k, v) |
| 186 for (k, v) in settings.iteritems() if v])) | 177 for (k, v) in settings.iteritems() if v])) |
| 187 for (k, v) in settings.iteritems(): | 178 for (k, v) in settings.iteritems(): |
| 188 # Avoid overwriting options already set using command line flags. | 179 # Avoid overwriting options already set using command line flags. |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 237 return True | 228 return True |
| 238 for r in self.options.exclude: | 229 for r in self.options.exclude: |
| 239 if re.search(r, f[1]): | 230 if re.search(r, f[1]): |
| 240 logging.info('Ignoring "%s"' % f[1]) | 231 logging.info('Ignoring "%s"' % f[1]) |
| 241 return True | 232 return True |
| 242 return False | 233 return False |
| 243 | 234 |
| 244 self._file_tuples = [f for f in file_tuples if not Excluded(f)] | 235 self._file_tuples = [f for f in file_tuples if not Excluded(f)] |
| 245 self._files = [f[1] for f in self._file_tuples] | 236 self._files = [f[1] for f in self._file_tuples] |
| 246 | 237 |
| 247 def CaptureStatus(self): | |
| 248 """Returns the 'svn status' emulated output as an array of (status, file) | |
| 249 tuples.""" | |
| 250 raise NotImplementedError( | |
| 251 "abstract method -- subclass %s must override" % self.__class__) | |
| 252 | |
| 253 @property | 238 @property |
| 254 def files(self): | 239 def files(self): |
| 255 if self._files is None: | 240 if self._files is None: |
| 256 self._SetFileTuples(self.CaptureStatus()) | 241 self._SetFileTuples(self.CaptureStatus()) |
| 257 return self._files | 242 return self._files |
| 258 | 243 |
| 259 @property | 244 @property |
| 260 def file_tuples(self): | 245 def file_tuples(self): |
| 261 if self._file_tuples is None: | 246 if self._file_tuples is None: |
| 262 self._SetFileTuples(self.CaptureStatus()) | 247 self._SetFileTuples(self.CaptureStatus()) |
| 263 return self._file_tuples | 248 return self._file_tuples |
| 264 | 249 |
| 265 | |
| 266 class SVN(SCM): | |
| 267 """Gathers the options and diff for a subversion checkout.""" | |
| 268 def __init__(self, *args, **kwargs): | |
| 269 SCM.__init__(self, *args, **kwargs) | |
| 270 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) | |
| 271 if not self.options.email: | |
| 272 # Assumes the svn credential is an email address. | |
| 273 self.options.email = scm.SVN.GetEmail(self.checkout_root) | |
| 274 logging.info("SVN(%s)" % self.checkout_root) | |
| 275 | |
| 276 def ReadRootFile(self, filename): | |
| 277 data = SCM.ReadRootFile(self, filename) | |
| 278 if data: | |
| 279 return data | |
| 280 | |
| 281 # Try to search on the subversion repository for the file. | |
| 282 if not gcl: | |
| 283 return None | |
| 284 data = gcl.GetCachedFile(filename) | |
| 285 logging.debug('%s:\n%s' % (filename, data)) | |
| 286 return data | |
| 287 | |
| 288 def CaptureStatus(self): | |
| 289 return scm.SVN.CaptureStatus(None, self.checkout_root) | |
| 290 | |
| 291 def GenerateDiff(self): | |
| 292 """Returns a string containing the diff for the given file list. | |
| 293 | |
| 294 The files in the list should either be absolute paths or relative to the | |
| 295 given root. | |
| 296 """ | |
| 297 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True, | |
| 298 revision=self.diff_against) | |
| 299 | |
| 300 | |
| 301 class GIT(SCM): | |
| 302 """Gathers the options and diff for a git checkout.""" | |
| 303 def __init__(self, *args, **kwargs): | |
| 304 SCM.__init__(self, *args, **kwargs) | |
| 305 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) | |
| 306 if not self.options.name: | |
| 307 self.options.name = scm.GIT.GetPatchName(self.checkout_root) | |
| 308 if not self.options.email: | |
| 309 self.options.email = scm.GIT.GetEmail(self.checkout_root) | |
| 310 if not self.diff_against: | |
| 311 self.diff_against = scm.GIT.GetUpstreamBranch(self.checkout_root) | |
| 312 if not self.diff_against: | |
| 313 raise NoTryServerAccess( | |
| 314 "Unable to determine default branch to diff against. " | |
| 315 "Verify this branch is set up to track another" | |
| 316 "(via the --track argument to \"git checkout -b ...\"") | |
| 317 logging.info("GIT(%s)" % self.checkout_root) | |
| 318 | |
| 319 def CaptureStatus(self): | 250 def CaptureStatus(self): |
| 320 return scm.GIT.CaptureStatus( | 251 return scm.GIT.CaptureStatus( |
| 321 [], | 252 [], |
| 322 self.checkout_root.replace(os.sep, '/'), | 253 self.checkout_root.replace(os.sep, '/'), |
| 323 self.diff_against) | 254 self.diff_against) |
| 324 | 255 |
| 325 def GenerateDiff(self): | 256 def GenerateDiff(self): |
| 326 if RunGit(['diff-index', 'HEAD']): | 257 if RunGit(['diff-index', 'HEAD']): |
| 327 print 'Cannot try with a dirty tree. You must commit locally first.' | 258 print 'Cannot try with a dirty tree. You must commit locally first.' |
| 328 return None | 259 return None |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 385 changed_files = checkouts[0].file_tuples | 316 changed_files = checkouts[0].file_tuples |
| 386 change = presubmit_support.Change(options.name, | 317 change = presubmit_support.Change(options.name, |
| 387 '', | 318 '', |
| 388 checkouts[0].checkout_root, | 319 checkouts[0].checkout_root, |
| 389 changed_files, | 320 changed_files, |
| 390 options.issue, | 321 options.issue, |
| 391 options.patchset, | 322 options.patchset, |
| 392 options.email) | 323 options.email) |
| 393 masters = presubmit_support.DoGetTryMasters( | 324 masters = presubmit_support.DoGetTryMasters( |
| 394 change, | 325 change, |
| 395 checkouts[0].GetFileNames(), | 326 checkouts[0].files, |
| 396 checkouts[0].checkout_root, | 327 checkouts[0].checkout_root, |
| 397 root_presubmit, | 328 root_presubmit, |
| 398 options.project, | 329 options.project, |
| 399 options.verbose, | 330 options.verbose, |
| 400 sys.stdout) | 331 sys.stdout) |
| 401 | 332 |
| 402 # Compatibility for old checkouts and bots that were on tryserver.chromium. | 333 # Compatibility for old checkouts and bots that were on tryserver.chromium. |
| 403 trybots = masters.get('tryserver.chromium', []) | 334 trybots = masters.get('tryserver.chromium', []) |
| 404 | 335 |
| 405 # Compatibility for checkouts that are not using tryserver.chromium | 336 # Compatibility for checkouts that are not using tryserver.chromium |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 451 # because it used to have lower-case 'true'. | 382 # because it used to have lower-case 'true'. |
| 452 if options.clobber: | 383 if options.clobber: |
| 453 values.append(('clobber', 'true')) | 384 values.append(('clobber', 'true')) |
| 454 | 385 |
| 455 for bot, tests in bot_spec: | 386 for bot, tests in bot_spec: |
| 456 values.append(('bot', ('%s:%s' % (bot, ','.join(tests))))) | 387 values.append(('bot', ('%s:%s' % (bot, ','.join(tests))))) |
| 457 | 388 |
| 458 return values | 389 return values |
| 459 | 390 |
| 460 | 391 |
| 461 def _SendChangeHTTP(bot_spec, options): | |
| 462 """Send a change to the try server using the HTTP protocol.""" | |
| 463 if not options.host: | |
| 464 raise NoTryServerAccess('Please use the --host option to specify the try ' | |
| 465 'server host to connect to.') | |
| 466 if not options.port: | |
| 467 raise NoTryServerAccess('Please use the --port option to specify the try ' | |
| 468 'server port to connect to.') | |
| 469 | |
| 470 values = _ParseSendChangeOptions(bot_spec, options) | |
| 471 values.append(('patch', options.diff)) | |
| 472 | |
| 473 url = 'http://%s:%s/send_try_patch' % (options.host, options.port) | |
| 474 | |
| 475 logging.info('Sending by HTTP') | |
| 476 logging.info(''.join("%s=%s\n" % (k, v) for k, v in values)) | |
| 477 logging.info(url) | |
| 478 logging.info(options.diff) | |
| 479 if options.dry_run: | |
| 480 return | |
| 481 | |
| 482 try: | |
| 483 logging.info('Opening connection...') | |
| 484 connection = urllib2.urlopen(url, urllib.urlencode(values)) | |
| 485 logging.info('Done') | |
| 486 except IOError, e: | |
| 487 logging.info(str(e)) | |
| 488 if bot_spec and len(e.args) > 2 and e.args[2] == 'got a bad status line': | |
| 489 raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url) | |
| 490 else: | |
| 491 raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url, | |
| 492 str(e.args))) | |
| 493 if not connection: | |
| 494 raise NoTryServerAccess('%s is unaccessible.' % url) | |
| 495 logging.info('Reading response...') | |
| 496 response = connection.read() | |
| 497 logging.info('Done') | |
| 498 if response != 'OK': | |
| 499 raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response)) | |
| 500 | |
| 501 PrintSuccess(bot_spec, options) | |
| 502 | |
| 503 @contextlib.contextmanager | 392 @contextlib.contextmanager |
| 504 def _TempFilename(name, contents=None): | 393 def _TempFilename(name, contents=None): |
| 505 """Create a temporary directory, append the specified name and yield. | 394 """Create a temporary directory, append the specified name and yield. |
| 506 | 395 |
| 507 In contrast to NamedTemporaryFile, does not keep the file open. | 396 In contrast to NamedTemporaryFile, does not keep the file open. |
| 508 Deletes the file on __exit__. | 397 Deletes the file on __exit__. |
| 509 """ | 398 """ |
| 510 temp_dir = tempfile.mkdtemp(prefix=name) | 399 temp_dir = tempfile.mkdtemp(prefix=name) |
| 511 try: | 400 try: |
| 512 path = os.path.join(temp_dir, name) | 401 path = os.path.join(temp_dir, name) |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 531 name (of patch) and diff (contents of patch). | 420 name (of patch) and diff (contents of patch). |
| 532 """ | 421 """ |
| 533 current_time = str(datetime.datetime.now()).replace(':', '.') | 422 current_time = str(datetime.datetime.now()).replace(':', '.') |
| 534 patch_basename = '%s.%s.%s.diff' % (Escape(options.user), | 423 patch_basename = '%s.%s.%s.diff' % (Escape(options.user), |
| 535 Escape(options.name), current_time) | 424 Escape(options.name), current_time) |
| 536 with _TempFilename('description', description) as description_filename: | 425 with _TempFilename('description', description) as description_filename: |
| 537 with _TempFilename(patch_basename, options.diff) as patch_filename: | 426 with _TempFilename(patch_basename, options.diff) as patch_filename: |
| 538 yield patch_filename, description_filename | 427 yield patch_filename, description_filename |
| 539 | 428 |
| 540 | 429 |
| 541 def _SendChangeSVN(bot_spec, options): | 430 def _SendChangeSVN(bot_spec, options): |
|
iannucci
2015/03/16 23:33:49
I would take the opportunity to break this out as
| |
| 542 """Send a change to the try server by committing a diff file on a subversion | 431 """Send a change to the try server by committing a diff file on a subversion |
| 543 server.""" | 432 server.""" |
| 544 if not options.svn_repo: | 433 if not options.svn_repo: |
| 545 raise NoTryServerAccess('Please use the --svn_repo option to specify the' | 434 raise NoTryServerAccess('Please use the --svn_repo option to specify the' |
| 546 ' try server svn repository to connect to.') | 435 ' try server svn repository to connect to.') |
| 547 | 436 |
| 548 values = _ParseSendChangeOptions(bot_spec, options) | 437 values = _ParseSendChangeOptions(bot_spec, options) |
| 549 description = ''.join("%s=%s\n" % (k, v) for k, v in values) | 438 description = ''.join("%s=%s\n" % (k, v) for k, v in values) |
| 550 logging.info('Sending by SVN') | 439 logging.info('Sending by SVN') |
| 551 logging.info(description) | 440 logging.info(description) |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 571 if scm.SVN.AssertVersion("1.5")[0]: | 460 if scm.SVN.AssertVersion("1.5")[0]: |
| 572 command.append('--no-ignore') | 461 command.append('--no-ignore') |
| 573 | 462 |
| 574 try: | 463 try: |
| 575 subprocess2.check_call(command) | 464 subprocess2.check_call(command) |
| 576 except subprocess2.CalledProcessError, e: | 465 except subprocess2.CalledProcessError, e: |
| 577 raise NoTryServerAccess(str(e)) | 466 raise NoTryServerAccess(str(e)) |
| 578 | 467 |
| 579 PrintSuccess(bot_spec, options) | 468 PrintSuccess(bot_spec, options) |
| 580 | 469 |
| 470 | |
| 581 def _GetPatchGitRepo(git_url): | 471 def _GetPatchGitRepo(git_url): |
| 582 """Gets a path to a Git repo with patches. | 472 """Gets a path to a Git repo with patches. |
| 583 | 473 |
| 584 Stores patches in .git/git-try/patches-git directory, a git repo. If it | 474 Stores patches in .git/git-try/patches-git directory, a git repo. If it |
| 585 doesn't exist yet or its origin URL is different, cleans up and clones it. | 475 doesn't exist yet or its origin URL is different, cleans up and clones it. |
| 586 If it existed before, then pulls changes. | 476 If it existed before, then pulls changes. |
| 587 | 477 |
| 588 Does not support SVN repo. | 478 Does not support SVN repo. |
| 589 | 479 |
| 590 Returns a path to the directory with patches. | 480 Returns a path to the directory with patches. |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 612 else: | 502 else: |
| 613 if scm.GIT.IsWorkTreeDirty(patch_dir): | 503 if scm.GIT.IsWorkTreeDirty(patch_dir): |
| 614 logging.info('Work dir is dirty: hard reset!') | 504 logging.info('Work dir is dirty: hard reset!') |
| 615 scm.GIT.Capture(['reset', '--hard'], cwd=patch_dir) | 505 scm.GIT.Capture(['reset', '--hard'], cwd=patch_dir) |
| 616 logging.info('Updating patch repo') | 506 logging.info('Updating patch repo') |
| 617 scm.GIT.Capture(['pull', 'origin', 'master'], cwd=patch_dir) | 507 scm.GIT.Capture(['pull', 'origin', 'master'], cwd=patch_dir) |
| 618 | 508 |
| 619 return os.path.abspath(patch_dir) | 509 return os.path.abspath(patch_dir) |
| 620 | 510 |
| 621 | 511 |
| 622 def _SendChangeGit(bot_spec, options): | |
| 623 """Sends a change to the try server by committing a diff file to a GIT repo. | |
| 624 | |
| 625 Creates a temp orphan branch, commits patch.diff, creates a ref pointing to | |
| 626 that commit, deletes the temp branch, checks master out, adds 'ref' file | |
| 627 containing the name of the new ref, pushes master and the ref to the origin. | |
| 628 | |
| 629 TODO: instead of creating a temp branch, use git-commit-tree. | |
| 630 """ | |
| 631 | |
| 632 if not options.git_repo: | |
| 633 raise NoTryServerAccess('Please use the --git_repo option to specify the ' | |
| 634 'try server git repository to connect to.') | |
| 635 | |
| 636 values = _ParseSendChangeOptions(bot_spec, options) | |
| 637 comment_subject = '%s.%s' % (options.user, options.name) | |
| 638 comment_body = ''.join("%s=%s\n" % (k, v) for k, v in values) | |
| 639 description = '%s\n\n%s' % (comment_subject, comment_body) | |
| 640 logging.info('Sending by GIT') | |
| 641 logging.info(description) | |
| 642 logging.info(options.git_repo) | |
| 643 logging.info(options.diff) | |
| 644 if options.dry_run: | |
| 645 return | |
| 646 | |
| 647 patch_dir = _GetPatchGitRepo(options.git_repo) | |
| 648 def patch_git(*args): | |
| 649 return scm.GIT.Capture(list(args), cwd=patch_dir) | |
| 650 def add_and_commit(filename, comment_filename): | |
| 651 patch_git('add', filename) | |
| 652 patch_git('commit', '-F', comment_filename) | |
| 653 | |
| 654 assert scm.GIT.IsInsideWorkTree(patch_dir) | |
| 655 assert not scm.GIT.IsWorkTreeDirty(patch_dir) | |
| 656 | |
| 657 with _PrepareDescriptionAndPatchFiles(description, options) as ( | |
| 658 patch_filename, description_filename): | |
| 659 logging.info('Committing patch') | |
| 660 | |
| 661 temp_branch = 'tmp_patch' | |
| 662 target_ref = 'refs/patches/%s/%s' % ( | |
| 663 Escape(options.user), | |
| 664 os.path.basename(patch_filename).replace(' ','_')) | |
| 665 target_filename = os.path.join(patch_dir, 'patch.diff') | |
| 666 branch_file = os.path.join(patch_dir, GIT_BRANCH_FILE) | |
| 667 | |
| 668 patch_git('checkout', 'master') | |
| 669 try: | |
| 670 # Try deleting an existing temp branch, if any. | |
| 671 try: | |
| 672 patch_git('branch', '-D', temp_branch) | |
| 673 logging.debug('Deleted an existing temp branch.') | |
| 674 except subprocess2.CalledProcessError: | |
| 675 pass | |
| 676 # Create a new branch and put the patch there. | |
| 677 patch_git('checkout', '--orphan', temp_branch) | |
| 678 patch_git('reset') | |
| 679 patch_git('clean', '-f') | |
| 680 shutil.copyfile(patch_filename, target_filename) | |
| 681 add_and_commit(target_filename, description_filename) | |
| 682 assert not scm.GIT.IsWorkTreeDirty(patch_dir) | |
| 683 | |
| 684 # Create a ref and point it to the commit referenced by temp_branch. | |
| 685 patch_git('update-ref', target_ref, temp_branch) | |
| 686 | |
| 687 # Delete the temp ref. | |
| 688 patch_git('checkout', 'master') | |
| 689 patch_git('branch', '-D', temp_branch) | |
| 690 | |
| 691 # Update the branch file in the master. | |
| 692 def update_branch(): | |
| 693 with open(branch_file, 'w') as f: | |
| 694 f.write(target_ref) | |
| 695 add_and_commit(branch_file, description_filename) | |
| 696 | |
| 697 update_branch() | |
| 698 | |
| 699 # Push master and target_ref to origin. | |
| 700 logging.info('Pushing patch') | |
| 701 for attempt in xrange(_GIT_PUSH_ATTEMPTS): | |
| 702 try: | |
| 703 patch_git('push', 'origin', 'master', target_ref) | |
| 704 except subprocess2.CalledProcessError as e: | |
| 705 is_last = attempt == _GIT_PUSH_ATTEMPTS - 1 | |
| 706 if is_last: | |
| 707 raise NoTryServerAccess(str(e)) | |
| 708 # Fetch, reset, update branch file again. | |
| 709 patch_git('fetch', 'origin') | |
| 710 patch_git('reset', '--hard', 'origin/master') | |
| 711 update_branch() | |
| 712 except subprocess2.CalledProcessError, e: | |
| 713 # Restore state. | |
| 714 patch_git('checkout', 'master') | |
| 715 patch_git('reset', '--hard', 'origin/master') | |
| 716 raise | |
| 717 | |
| 718 PrintSuccess(bot_spec, options) | |
| 719 | |
| 720 def _SendChangeGerrit(bot_spec, options): | |
| 721 """Posts a try job to a Gerrit change. | |
| 722 | |
| 723 Reads Change-Id from the HEAD commit, resolves the current revision, checks | |
| 724 that local revision matches the uploaded one, posts a try job in form of a | |
| 725 message, sets Tryjob-Request label to 1. | |
| 726 | |
| 727 Gerrit message format: starts with !tryjob, optionally followed by a tryjob | |
| 728 definition in JSON format: | |
| 729 buildNames: list of strings specifying build names. | |
| 730 """ | |
| 731 | |
| 732 logging.info('Sending by Gerrit') | |
| 733 if not options.gerrit_url: | |
| 734 raise NoTryServerAccess('Please use --gerrit_url option to specify the ' | |
| 735 'Gerrit instance url to connect to') | |
| 736 gerrit_host = urlparse.urlparse(options.gerrit_url).hostname | |
| 737 logging.debug('Gerrit host: %s' % gerrit_host) | |
| 738 | |
| 739 def GetChangeId(commmitish): | |
| 740 """Finds Change-ID of the HEAD commit.""" | |
| 741 CHANGE_ID_RGX = '^Change-Id: (I[a-f0-9]{10,})' | |
| 742 comment = scm.GIT.Capture(['log', '-1', commmitish, '--format=%b'], | |
| 743 cwd=os.getcwd()) | |
| 744 change_id_match = re.search(CHANGE_ID_RGX, comment, re.I | re.M) | |
| 745 if not change_id_match: | |
| 746 raise Error('Change-Id was not found in the HEAD commit. Make sure you ' | |
| 747 'have a Git hook installed that generates and inserts a ' | |
| 748 'Change-Id into a commit message automatically.') | |
| 749 change_id = change_id_match.group(1) | |
| 750 return change_id | |
| 751 | |
| 752 def FormatMessage(): | |
| 753 # Build job definition. | |
| 754 job_def = {} | |
| 755 builderNames = [builder for builder, _ in bot_spec] | |
| 756 if builderNames: | |
| 757 job_def['builderNames'] = builderNames | |
| 758 | |
| 759 # Format message. | |
| 760 msg = '!tryjob' | |
| 761 if job_def: | |
| 762 msg = '%s %s' % (msg, json.dumps(job_def, sort_keys=True)) | |
| 763 return msg | |
| 764 | |
| 765 def PostTryjob(message): | |
| 766 logging.info('Posting gerrit message: %s' % message) | |
| 767 if not options.dry_run: | |
| 768 # Post a message and set TryJob=1 label. | |
| 769 try: | |
| 770 gerrit_util.SetReview(gerrit_host, change_id, msg=message, | |
| 771 labels={'Tryjob-Request': 1}) | |
| 772 except gerrit_util.GerritError, e: | |
| 773 if e.http_status == 400: | |
| 774 raise Error(e.message) | |
| 775 else: | |
| 776 raise | |
| 777 | |
| 778 head_sha = scm.GIT.Capture(['log', '-1', '--format=%H'], cwd=os.getcwd()) | |
| 779 | |
| 780 change_id = GetChangeId(head_sha) | |
| 781 | |
| 782 try: | |
| 783 # Check that the uploaded revision matches the local one. | |
| 784 changes = gerrit_util.GetChangeCurrentRevision(gerrit_host, change_id) | |
| 785 except gerrit_util.GerritAuthenticationError, e: | |
| 786 raise NoTryServerAccess(e.message) | |
| 787 | |
| 788 assert len(changes) <= 1, 'Multiple changes with id %s' % change_id | |
| 789 if not changes: | |
| 790 raise Error('A change %s was not found on the server. Was it uploaded?' % | |
| 791 change_id) | |
| 792 logging.debug('Found Gerrit change: %s' % changes[0]) | |
| 793 if changes[0]['current_revision'] != head_sha: | |
| 794 raise Error('Please upload your latest local changes to Gerrit.') | |
| 795 | |
| 796 # Post a try job. | |
| 797 message = FormatMessage() | |
| 798 PostTryjob(message) | |
| 799 change_url = urlparse.urljoin(options.gerrit_url, | |
| 800 '/#/c/%s' % changes[0]['_number']) | |
| 801 print('A tryjob was posted on change %s' % change_url) | |
| 802 | |
| 803 def PrintSuccess(bot_spec, options): | 512 def PrintSuccess(bot_spec, options): |
| 804 if not options.dry_run: | 513 if not options.dry_run: |
| 805 text = 'Patch \'%s\' sent to try server' % options.name | 514 text = 'Patch \'%s\' sent to try server' % options.name |
| 806 if bot_spec: | 515 if bot_spec: |
| 807 text += ': %s' % ', '.join( | 516 text += ': %s' % ', '.join( |
| 808 '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec) | 517 '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec) |
| 809 print(text) | 518 print(text) |
| 810 | 519 |
| 811 | 520 |
| 812 def GuessVCS(options, path, file_list): | 521 def DetectGit(options, path, file_list): |
| 813 """Helper to guess the version control system. | 522 """Instantiates the GIT class if a git repo is detected. |
| 814 | |
| 815 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't | |
| 816 support it yet. | |
| 817 | |
| 818 This examines the path directory, guesses which SCM we're using, and | |
| 819 returns an instance of the appropriate class. Exit with an error if we can't | |
| 820 figure it out. | |
| 821 | 523 |
| 822 Returns: | 524 Returns: |
| 823 A SCM instance. Exits if the SCM can't be guessed. | 525 A GIT instance. None if repo not found. |
| 824 """ | 526 """ |
| 825 __pychecker__ = 'no-returnvalues' | 527 __pychecker__ = 'no-returnvalues' |
| 826 real_path = path.split('@')[0] | 528 real_path = path.split('@')[0] |
| 827 logging.info("GuessVCS(%s)" % path) | 529 logging.info("DetectGit(%s)" % path) |
| 828 # Subversion has a .svn in all working directories. | |
| 829 if os.path.isdir(os.path.join(real_path, '.svn')): | |
| 830 return SVN(options, path, file_list) | |
| 831 | 530 |
| 832 # Git has a command to test if you're in a git tree. | 531 # Git has a command to test if you're in a git tree. |
| 833 # Try running it, but don't die if we don't have git installed. | 532 # Try running it, but don't die if we don't have git installed. |
| 834 try: | 533 try: |
| 835 subprocess2.check_output( | 534 subprocess2.check_output( |
| 836 ['git', 'rev-parse', '--is-inside-work-tree'], cwd=real_path, | 535 ['git', 'rev-parse', '--is-inside-work-tree'], cwd=real_path, |
| 837 stderr=subprocess2.VOID) | 536 stderr=subprocess2.VOID) |
| 838 return GIT(options, path, file_list) | 537 return GIT(options, path, file_list) |
| 839 except OSError, e: | 538 except OSError, e: |
| 840 if e.errno != errno.ENOENT: | 539 if e.errno != errno.ENOENT: |
| 841 raise | 540 raise |
| 842 except subprocess2.CalledProcessError, e: | 541 except subprocess2.CalledProcessError, e: |
| 843 if e.returncode != errno.ENOENT and e.returncode != 128: | 542 if e.returncode != errno.ENOENT and e.returncode != 128: |
| 844 # ENOENT == 2 = they don't have git installed. | 543 # ENOENT == 2 = they don't have git installed. |
| 845 # 128 = git error code when not in a repo. | 544 # 128 = git error code when not in a repo. |
| 846 logging.warning('Unexpected error code: %s' % e.returncode) | 545 logging.warning('Unexpected error code: %s' % e.returncode) |
| 847 raise | 546 raise |
| 848 raise NoTryServerAccess( | 547 return None |
| 849 ( 'Could not guess version control system for %s.\n' | |
| 850 'Are you in a working copy directory?') % path) | |
| 851 | 548 |
| 852 | 549 |
| 853 def GetMungedDiff(path_diff, diff): | 550 def GetMungedDiff(path_diff, diff): |
| 854 # Munge paths to match svn. | 551 # Munge paths to match svn. |
| 855 changed_files = [] | 552 changed_files = [] |
| 856 for i in range(len(diff)): | 553 for i in range(len(diff)): |
| 857 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): | 554 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): |
| 858 new_file = posixpath.join(path_diff, diff[i][4:]).replace('\\', '/') | 555 new_file = posixpath.join(path_diff, diff[i][4:]).replace('\\', '/') |
| 859 if diff[i].startswith('--- '): | 556 if diff[i].startswith('--- '): |
| 860 file_path = new_file.split('\t')[0].strip() | 557 file_path = new_file.split('\t')[0].strip() |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 971 help=("Disable automatic search for gclient or repo " | 668 help=("Disable automatic search for gclient or repo " |
| 972 "checkout root.")) | 669 "checkout root.")) |
| 973 group.add_option("-E", "--exclude", action="append", | 670 group.add_option("-E", "--exclude", action="append", |
| 974 default=['ChangeLog'], metavar='REGEXP', | 671 default=['ChangeLog'], metavar='REGEXP', |
| 975 help="Regexp patterns to exclude files. Default: %default") | 672 help="Regexp patterns to exclude files. Default: %default") |
| 976 group.add_option("--upstream_branch", action="store", | 673 group.add_option("--upstream_branch", action="store", |
| 977 help="Specify the upstream branch to diff against in the " | 674 help="Specify the upstream branch to diff against in the " |
| 978 "main checkout") | 675 "main checkout") |
| 979 parser.add_option_group(group) | 676 parser.add_option_group(group) |
| 980 | 677 |
| 981 group = optparse.OptionGroup(parser, "Access the try server by HTTP") | |
| 982 group.add_option("--use_http", | |
| 983 action="store_const", | |
| 984 const=_SendChangeHTTP, | |
| 985 dest="send_patch", | |
| 986 help="Use HTTP to talk to the try server [default]") | |
| 987 group.add_option("-H", "--host", | |
| 988 help="Host address") | |
| 989 group.add_option("-P", "--port", type="int", | |
| 990 help="HTTP port") | |
| 991 parser.add_option_group(group) | |
| 992 | |
| 993 group = optparse.OptionGroup(parser, "Access the try server with SVN") | 678 group = optparse.OptionGroup(parser, "Access the try server with SVN") |
| 994 group.add_option("--use_svn", | |
| 995 action="store_const", | |
| 996 const=_SendChangeSVN, | |
| 997 dest="send_patch", | |
| 998 help="Use SVN to talk to the try server") | |
| 999 group.add_option("-S", "--svn_repo", | 679 group.add_option("-S", "--svn_repo", |
| 1000 metavar="SVN_URL", | 680 metavar="SVN_URL", |
| 1001 help="SVN url to use to write the changes in; --use_svn is " | 681 help="SVN url to use to write the changes in; --use_svn is " |
| 1002 "implied when using --svn_repo") | 682 "implied when using --svn_repo") |
| 1003 parser.add_option_group(group) | 683 parser.add_option_group(group) |
| 1004 | 684 |
| 1005 group = optparse.OptionGroup(parser, "Access the try server with Git") | |
| 1006 group.add_option("--use_git", | |
| 1007 action="store_const", | |
| 1008 const=_SendChangeGit, | |
| 1009 dest="send_patch", | |
| 1010 help="Use GIT to talk to the try server") | |
| 1011 group.add_option("-G", "--git_repo", | |
| 1012 metavar="GIT_URL", | |
| 1013 help="GIT url to use to write the changes in; --use_git is " | |
| 1014 "implied when using --git_repo") | |
| 1015 parser.add_option_group(group) | |
| 1016 | |
| 1017 group = optparse.OptionGroup(parser, "Access the try server with Gerrit") | |
| 1018 group.add_option("--use_gerrit", | |
| 1019 action="store_const", | |
| 1020 const=_SendChangeGerrit, | |
| 1021 dest="send_patch", | |
| 1022 help="Use Gerrit to talk to the try server") | |
| 1023 group.add_option("--gerrit_url", | |
| 1024 metavar="GERRIT_URL", | |
| 1025 help="Gerrit url to post a tryjob to; --use_gerrit is " | |
| 1026 "implied when using --gerrit_url") | |
| 1027 parser.add_option_group(group) | |
| 1028 | |
| 1029 return parser | 685 return parser |
| 1030 | 686 |
| 1031 | 687 |
| 1032 def TryChange(argv, | 688 def TryChange_Git(argv, |
| 1033 change, | |
| 1034 swallow_exception, | 689 swallow_exception, |
| 1035 prog=None, | 690 prog=None, |
| 1036 extra_epilog=None): | 691 extra_epilog=None): |
| 1037 """ | 692 """ |
| 1038 Args: | 693 Args: |
| 1039 argv: Arguments and options. | 694 argv: Arguments and options. |
| 1040 change: Change instance corresponding to the CL. | 695 change: Change instance corresponding to the CL. |
| 1041 swallow_exception: Whether we raise or swallow exceptions. | 696 swallow_exception: Whether we raise or swallow exceptions. |
| 1042 """ | 697 """ |
| 1043 parser = gen_parser(prog) | 698 parser = gen_parser(prog) |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1092 options.rietveld_url = match.group(1) | 747 options.rietveld_url = match.group(1) |
| 1093 | 748 |
| 1094 try: | 749 try: |
| 1095 changed_files = None | 750 changed_files = None |
| 1096 # Always include os.getcwd() in the checkout settings. | 751 # Always include os.getcwd() in the checkout settings. |
| 1097 path = os.getcwd() | 752 path = os.getcwd() |
| 1098 | 753 |
| 1099 file_list = [] | 754 file_list = [] |
| 1100 if options.files: | 755 if options.files: |
| 1101 file_list = options.files | 756 file_list = options.files |
| 1102 elif change: | 757 elif (not options.email or not options.diff or not options.revision |
| 758 or options.revision.lower() == 'auto'): | |
| 759 changelist = git_cl.Changelist() | |
| 760 change = changelist.GetChange(changelist.GetUpstreamBranch(), None) | |
| 1103 file_list = [f.LocalPath() for f in change.AffectedFiles()] | 761 file_list = [f.LocalPath() for f in change.AffectedFiles()] |
| 1104 | 762 |
| 1105 if options.upstream_branch: | 763 if options.upstream_branch: |
| 1106 path += '@' + options.upstream_branch | 764 path += '@' + options.upstream_branch |
| 1107 # Clear file list so that the correct list will be retrieved from the | 765 # Clear file list so that the correct list will be retrieved from the |
| 1108 # upstream branch. | 766 # upstream branch. |
| 1109 file_list = [] | 767 file_list = [] |
| 1110 | 768 |
| 1111 current_vcs = GuessVCS(options, path, file_list) | 769 detected_git = DetectGit(options, path, file_list) |
| 1112 current_vcs.AutomagicalSettings() | 770 if detected_git: |
| 1113 options = current_vcs.options | 771 detected_git.AutomagicalSettings() |
| 1114 vcs_is_git = type(current_vcs) is GIT | 772 options = detected_git.options |
| 1115 | |
| 1116 # So far, git_repo doesn't work with SVN | |
| 1117 if options.git_repo and not vcs_is_git: | |
| 1118 parser.error('--git_repo option is supported only for GIT repositories') | |
| 1119 | 773 |
| 1120 # If revision==auto, resolve it | 774 # If revision==auto, resolve it |
| 1121 if options.revision and options.revision.lower() == 'auto': | 775 if options.revision and options.revision.lower() == 'auto': |
| 1122 if not vcs_is_git: | |
| 1123 parser.error('--revision=auto is supported only for GIT repositories') | |
| 1124 options.revision = scm.GIT.Capture( | 776 options.revision = scm.GIT.Capture( |
| 1125 ['rev-parse', current_vcs.diff_against], | 777 ['rev-parse', detected_git.diff_against], |
| 1126 cwd=path) | 778 cwd=path) |
| 1127 | 779 |
| 1128 checkouts = [current_vcs] | 780 checkouts = [] |
| 781 if detected_git: | |
| 782 checkouts.append(detected_git) | |
| 1129 for item in options.sub_rep: | 783 for item in options.sub_rep: |
| 1130 # Pass file_list=None because we don't know the sub repo's file list. | 784 # Pass file_list=None because we don't know the sub repo's file list. |
| 1131 checkout = GuessVCS(options, | 785 checkout = DetectGit(options, |
| 1132 os.path.join(current_vcs.checkout_root, item), | 786 os.path.join(detected_git.checkout_root, item), |
| 1133 None) | 787 None) |
| 1134 if checkout.checkout_root in [c.checkout_root for c in checkouts]: | 788 if checkout.checkout_root in [c.checkout_root for c in checkouts]: |
| 1135 parser.error('Specified the root %s two times.' % | 789 parser.error('Specified the root %s two times.' % |
| 1136 checkout.checkout_root) | 790 checkout.checkout_root) |
| 1137 checkouts.append(checkout) | 791 checkouts.append(checkout) |
| 1138 | 792 |
| 1139 can_http = options.port and options.host | 793 if not options.svn_repo: |
| 1140 can_svn = options.svn_repo | 794 parser.error('A SVN repo is required to send the job.') |
| 1141 can_git = options.git_repo | |
| 1142 can_gerrit = options.gerrit_url | |
| 1143 can_something = can_http or can_svn or can_git or can_gerrit | |
| 1144 # If there was no transport selected yet, now we must have enough data to | |
| 1145 # select one. | |
| 1146 if not options.send_patch and not can_something: | |
| 1147 parser.error('Please specify an access method.') | |
| 1148 | 795 |
| 1149 # Convert options.diff into the content of the diff. | 796 # Convert options.diff into the content of the diff. |
| 1150 if options.url: | 797 if options.url: |
| 1151 if options.files: | 798 if options.files: |
| 1152 parser.error('You cannot specify files and --url at the same time.') | 799 parser.error('You cannot specify files and --url at the same time.') |
| 1153 options.diff = urllib2.urlopen(options.url).read() | 800 options.diff = urllib2.urlopen(options.url).read() |
| 1154 elif options.diff: | 801 elif options.diff: |
| 1155 if options.files: | 802 if options.files: |
| 1156 parser.error('You cannot specify files and --diff at the same time.') | 803 parser.error('You cannot specify files and --diff at the same time.') |
| 1157 options.diff = gclient_utils.FileRead(options.diff, 'rb') | 804 options.diff = gclient_utils.FileRead(options.diff, 'rb') |
| 1158 elif options.issue and options.patchset is None: | 805 elif options.issue and options.patchset is None: |
| 1159 # Retrieve the patch from rietveld when the diff is not specified. | 806 # Retrieve the patch from rietveld when the diff is not specified. |
| 1160 # When patchset is specified, it's because it's done by gcl/git-try. | 807 # When patchset is specified, it's because it's done by gcl/git-try. |
| 1161 api_url = '%s/api/%d' % (options.rietveld_url, options.issue) | 808 api_url = '%s/api/%d' % (options.rietveld_url, options.issue) |
| 1162 logging.debug(api_url) | 809 logging.debug(api_url) |
| 1163 contents = json.loads(urllib2.urlopen(api_url).read()) | 810 contents = json.loads(urllib2.urlopen(api_url).read()) |
| 1164 options.patchset = contents['patchsets'][-1] | 811 options.patchset = contents['patchsets'][-1] |
| 1165 diff_url = ('%s/download/issue%d_%d.diff' % | 812 diff_url = ('%s/download/issue%d_%d.diff' % |
| 1166 (options.rietveld_url, options.issue, options.patchset)) | 813 (options.rietveld_url, options.issue, options.patchset)) |
| 1167 diff = GetMungedDiff('', urllib2.urlopen(diff_url).readlines()) | 814 diff = GetMungedDiff('', urllib2.urlopen(diff_url).readlines()) |
| 1168 options.diff = ''.join(diff[0]) | 815 options.diff = ''.join(diff[0]) |
| 1169 changed_files = diff[1] | 816 changed_files = diff[1] |
| 1170 else: | 817 elif checkouts: |
| 1171 # Use this as the base. | 818 # Use this as the base. |
| 1172 root = checkouts[0].checkout_root | 819 root = checkouts[0].checkout_root |
| 1173 diffs = [] | 820 diffs = [] |
| 1174 for checkout in checkouts: | 821 for checkout in checkouts: |
| 1175 raw_diff = checkout.GenerateDiff() | 822 raw_diff = checkout.GenerateDiff() |
| 1176 if not raw_diff: | 823 if not raw_diff: |
| 1177 continue | 824 continue |
| 1178 diff = raw_diff.splitlines(True) | 825 diff = raw_diff.splitlines(True) |
| 1179 path_diff = gclient_utils.PathDifference(root, checkout.checkout_root) | 826 path_diff = gclient_utils.PathDifference(root, checkout.checkout_root) |
| 1180 # Munge it. | 827 # Munge it. |
| 1181 diffs.extend(GetMungedDiff(path_diff, diff)[0]) | 828 diffs.extend(GetMungedDiff(path_diff, diff)[0]) |
| 1182 if not diffs: | 829 if not diffs: |
| 1183 logging.error('Empty or non-existant diff, exiting.') | 830 logging.error('Empty or non-existant diff, exiting.') |
| 1184 return 1 | 831 return 1 |
| 1185 options.diff = ''.join(diffs) | 832 options.diff = ''.join(diffs) |
| 833 else: | |
| 834 raise RuntimeError('Nothing to send') | |
| 1186 | 835 |
| 1187 if not options.name: | 836 if not options.name: |
| 1188 if options.issue: | 837 if options.issue: |
| 1189 options.name = 'Issue %s' % options.issue | 838 options.name = 'Issue %s' % options.issue |
| 1190 else: | 839 else: |
| 1191 options.name = 'Unnamed' | 840 options.name = 'Unnamed' |
| 1192 print('Note: use --name NAME to change the try job name.') | 841 print('Note: use --name NAME to change the try job name.') |
| 1193 | 842 |
| 1194 if not options.email: | 843 if not options.email: |
| 1195 parser.error('Using an anonymous checkout. Please use --email or set ' | 844 parser.error('Using an anonymous checkout. Please use --email or set ' |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 1214 | 863 |
| 1215 if options.print_bots: | 864 if options.print_bots: |
| 1216 print 'Bots which would be used:' | 865 print 'Bots which would be used:' |
| 1217 for bot in bot_spec: | 866 for bot in bot_spec: |
| 1218 if bot[1]: | 867 if bot[1]: |
| 1219 print ' %s:%s' % (bot[0], ','.join(bot[1])) | 868 print ' %s:%s' % (bot[0], ','.join(bot[1])) |
| 1220 else: | 869 else: |
| 1221 print ' %s' % (bot[0]) | 870 print ' %s' % (bot[0]) |
| 1222 return 0 | 871 return 0 |
| 1223 | 872 |
| 1224 # Determine sending protocol | 873 _SendChangeSVN(bot_spec, options) |
| 1225 if options.send_patch: | |
| 1226 # If forced. | |
| 1227 senders = [options.send_patch] | |
| 1228 else: | |
| 1229 # Try sending patch using avaialble protocols | |
| 1230 all_senders = [ | |
| 1231 (_SendChangeHTTP, can_http), | |
| 1232 (_SendChangeSVN, can_svn), | |
| 1233 (_SendChangeGerrit, can_gerrit), | |
| 1234 (_SendChangeGit, can_git), | |
| 1235 ] | |
| 1236 senders = [sender for sender, can in all_senders if can] | |
| 1237 | |
| 1238 # Send the patch. | |
| 1239 for sender in senders: | |
| 1240 try: | |
| 1241 sender(bot_spec, options) | |
| 1242 return 0 | |
| 1243 except NoTryServerAccess: | |
| 1244 is_last = sender == senders[-1] | |
| 1245 if is_last: | |
| 1246 raise | |
| 1247 assert False, "Unreachable code" | |
| 1248 except Error, e: | 874 except Error, e: |
| 1249 if swallow_exception: | 875 if swallow_exception: |
| 1250 return 1 | 876 return 1 |
| 1251 print >> sys.stderr, e | 877 print >> sys.stderr, e |
| 1252 return 1 | 878 return 1 |
| 1253 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 879 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
| 1254 print >> sys.stderr, e | 880 print >> sys.stderr, e |
| 1255 return 1 | 881 return 1 |
| 1256 return 0 | 882 return 0 |
| 1257 | |
| 1258 | |
| 1259 if __name__ == "__main__": | |
| 1260 fix_encoding.fix_encoding() | |
| 1261 sys.exit(TryChange(None, None, False)) | |
| OLD | NEW |