| Index: trychange.py
|
| diff --git a/trychange.py b/trychange.py
|
| deleted file mode 100755
|
| index db9bd440fa593158b0e3b86bae78974acda1e604..0000000000000000000000000000000000000000
|
| --- a/trychange.py
|
| +++ /dev/null
|
| @@ -1,1265 +0,0 @@
|
| -#!/usr/bin/env python
|
| -# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -"""Client-side script to send a try job to the try server. It communicates to
|
| -the try server by either writting to a svn/git repository or by directly
|
| -connecting to the server by HTTP.
|
| -"""
|
| -
|
| -import contextlib
|
| -import datetime
|
| -import errno
|
| -import getpass
|
| -import itertools
|
| -import json
|
| -import logging
|
| -import optparse
|
| -import os
|
| -import posixpath
|
| -import re
|
| -import shutil
|
| -import sys
|
| -import tempfile
|
| -import urllib
|
| -import urllib2
|
| -import urlparse
|
| -
|
| -import fix_encoding
|
| -import gcl
|
| -import gclient_utils
|
| -import gerrit_util
|
| -import scm
|
| -import subprocess2
|
| -
|
| -
|
| -__version__ = '1.2'
|
| -
|
| -
|
| -# Constants
|
| -HELP_STRING = "Sorry, Tryserver is not available."
|
| -USAGE = r"""%prog [options]
|
| -
|
| -Client-side script to send a try job to the try server. It communicates to
|
| -the try server by either writting to a svn repository or by directly connecting
|
| -to the server by HTTP."""
|
| -
|
| -EPILOG = """
|
| -Examples:
|
| - Send a patch directly from rietveld:
|
| - %(prog)s -R codereview.chromium.org/1337
|
| - --email recipient@example.com --root src
|
| -
|
| - Try a change against a particular revision:
|
| - %(prog)s -r 123
|
| -
|
| - Try a change including changes to a sub repository:
|
| - %(prog)s -s third_party/WebKit
|
| -
|
| - A git patch off a web site (git inserts a/ and b/) and fix the base dir:
|
| - %(prog)s --url http://url/to/patch.diff --patchlevel 1 --root src
|
| -
|
| - Use svn to store the try job, specify an alternate email address and use a
|
| - premade diff file on the local drive:
|
| - %(prog)s --email user@example.com
|
| - --svn_repo svn://svn.chromium.org/chrome-try/try --diff foo.diff
|
| -
|
| - Running only on a 'mac' slave with revision 123 and clobber first; specify
|
| - manually the 3 source files to use for the try job:
|
| - %(prog)s --bot mac --revision 123 --clobber -f src/a.cc -f src/a.h
|
| - -f include/b.h
|
| -"""
|
| -
|
| -GIT_PATCH_DIR_BASENAME = os.path.join('git-try', 'patches-git')
|
| -GIT_BRANCH_FILE = 'ref'
|
| -_GIT_PUSH_ATTEMPTS = 3
|
| -
|
| -def DieWithError(message):
|
| - print >> sys.stderr, message
|
| - sys.exit(1)
|
| -
|
| -
|
| -def RunCommand(args, error_ok=False, error_message=None, **kwargs):
|
| - try:
|
| - return subprocess2.check_output(args, shell=False, **kwargs)
|
| - except subprocess2.CalledProcessError, e:
|
| - if not error_ok:
|
| - DieWithError(
|
| - 'Command "%s" failed.\n%s' % (
|
| - ' '.join(args), error_message or e.stdout or ''))
|
| - return e.stdout
|
| -
|
| -
|
| -def RunGit(args, **kwargs):
|
| - """Returns stdout."""
|
| - return RunCommand(['git'] + args, **kwargs)
|
| -
|
| -class Error(Exception):
|
| - """An error during a try job submission.
|
| -
|
| - For this error, trychange.py does not display stack trace, only message
|
| - """
|
| -
|
| -class InvalidScript(Error):
|
| - def __str__(self):
|
| - return self.args[0] + '\n' + HELP_STRING
|
| -
|
| -
|
| -class NoTryServerAccess(Error):
|
| - def __str__(self):
|
| - return self.args[0] + '\n' + HELP_STRING
|
| -
|
| -def Escape(name):
|
| - """Escapes characters that could interfere with the file system or try job
|
| - parsing.
|
| - """
|
| - return re.sub(r'[^\w#-]', '_', name)
|
| -
|
| -
|
| -class SCM(object):
|
| - """Simplistic base class to implement one function: ProcessOptions."""
|
| - def __init__(self, options, path, file_list):
|
| - items = path.split('@')
|
| - assert len(items) <= 2
|
| - self.checkout_root = os.path.abspath(items[0])
|
| - items.append(None)
|
| - self.diff_against = items[1]
|
| - self.options = options
|
| - # Lazy-load file list from the SCM unless files were specified in options.
|
| - self._files = None
|
| - self._file_tuples = None
|
| - if file_list:
|
| - self._files = file_list
|
| - self._file_tuples = [('M', f) for f in self.files]
|
| - self.options.files = None
|
| - self.codereview_settings = None
|
| - self.codereview_settings_file = 'codereview.settings'
|
| - self.toplevel_root = None
|
| -
|
| - def GetFileNames(self):
|
| - """Return the list of files in the diff."""
|
| - return self.files
|
| -
|
| - def GetCodeReviewSetting(self, key):
|
| - """Returns a value for the given key for this repository.
|
| -
|
| - Uses gcl-style settings from the repository.
|
| - """
|
| - if gcl:
|
| - gcl_setting = gcl.GetCodeReviewSetting(key)
|
| - if gcl_setting != '':
|
| - return gcl_setting
|
| - if self.codereview_settings is None:
|
| - self.codereview_settings = {}
|
| - settings_file = self.ReadRootFile(self.codereview_settings_file)
|
| - if settings_file:
|
| - for line in settings_file.splitlines():
|
| - if not line or line.lstrip().startswith('#'):
|
| - continue
|
| - k, v = line.split(":", 1)
|
| - self.codereview_settings[k.strip()] = v.strip()
|
| - return self.codereview_settings.get(key, '')
|
| -
|
| - def _GclStyleSettings(self):
|
| - """Set default settings based on the gcl-style settings from the repository.
|
| -
|
| - The settings in the self.options object will only be set if no previous
|
| - value exists (i.e. command line flags to the try command will override the
|
| - settings in codereview.settings).
|
| - """
|
| - settings = {
|
| - 'port': self.GetCodeReviewSetting('TRYSERVER_HTTP_PORT'),
|
| - 'host': self.GetCodeReviewSetting('TRYSERVER_HTTP_HOST'),
|
| - 'svn_repo': self.GetCodeReviewSetting('TRYSERVER_SVN_URL'),
|
| - 'gerrit_url': self.GetCodeReviewSetting('TRYSERVER_GERRIT_URL'),
|
| - 'git_repo': self.GetCodeReviewSetting('TRYSERVER_GIT_URL'),
|
| - 'project': self.GetCodeReviewSetting('TRYSERVER_PROJECT'),
|
| - # Primarily for revision=auto
|
| - 'revision': self.GetCodeReviewSetting('TRYSERVER_REVISION'),
|
| - 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'),
|
| - 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'),
|
| - }
|
| - logging.info('\n'.join(['%s: %s' % (k, v)
|
| - for (k, v) in settings.iteritems() if v]))
|
| - for (k, v) in settings.iteritems():
|
| - # Avoid overwriting options already set using command line flags.
|
| - if v and getattr(self.options, k) is None:
|
| - setattr(self.options, k, v)
|
| -
|
| - def AutomagicalSettings(self):
|
| - """Determines settings based on supported code review and checkout tools.
|
| - """
|
| - # Try to find gclient or repo root first.
|
| - if not self.options.no_search:
|
| - self.toplevel_root = gclient_utils.FindGclientRoot(self.checkout_root)
|
| - if self.toplevel_root:
|
| - logging.info('Found .gclient at %s' % self.toplevel_root)
|
| - else:
|
| - self.toplevel_root = gclient_utils.FindFileUpwards(
|
| - os.path.join('..', '.repo'), self.checkout_root)
|
| - if self.toplevel_root:
|
| - logging.info('Found .repo dir at %s'
|
| - % os.path.dirname(self.toplevel_root))
|
| -
|
| - # Parse TRYSERVER_* settings from codereview.settings before falling back
|
| - # on setting self.options.root manually further down. Otherwise
|
| - # TRYSERVER_ROOT would never be used in codereview.settings.
|
| - self._GclStyleSettings()
|
| -
|
| - if self.toplevel_root and not self.options.root:
|
| - assert os.path.abspath(self.toplevel_root) == self.toplevel_root
|
| - self.options.root = gclient_utils.PathDifference(self.toplevel_root,
|
| - self.checkout_root)
|
| - else:
|
| - self._GclStyleSettings()
|
| -
|
| - def ReadRootFile(self, filename):
|
| - cur = self.checkout_root
|
| - root = self.toplevel_root or self.checkout_root
|
| -
|
| - assert cur.startswith(root), (root, cur)
|
| - while cur.startswith(root):
|
| - filepath = os.path.join(cur, filename)
|
| - if os.path.isfile(filepath):
|
| - logging.info('Found %s at %s' % (filename, cur))
|
| - return gclient_utils.FileRead(filepath)
|
| - cur = os.path.dirname(cur)
|
| - logging.warning('Didn\'t find %s' % filename)
|
| - return None
|
| -
|
| - def _SetFileTuples(self, file_tuples):
|
| - excluded = ['!', '?', 'X', ' ', '~']
|
| - def Excluded(f):
|
| - if f[0][0] in excluded:
|
| - return True
|
| - for r in self.options.exclude:
|
| - if re.search(r, f[1]):
|
| - logging.info('Ignoring "%s"' % f[1])
|
| - return True
|
| - return False
|
| -
|
| - self._file_tuples = [f for f in file_tuples if not Excluded(f)]
|
| - self._files = [f[1] for f in self._file_tuples]
|
| -
|
| - def CaptureStatus(self):
|
| - """Returns the 'svn status' emulated output as an array of (status, file)
|
| - tuples."""
|
| - raise NotImplementedError(
|
| - "abstract method -- subclass %s must override" % self.__class__)
|
| -
|
| - @property
|
| - def files(self):
|
| - if self._files is None:
|
| - self._SetFileTuples(self.CaptureStatus())
|
| - return self._files
|
| -
|
| - @property
|
| - def file_tuples(self):
|
| - if self._file_tuples is None:
|
| - self._SetFileTuples(self.CaptureStatus())
|
| - return self._file_tuples
|
| -
|
| -
|
| -class SVN(SCM):
|
| - """Gathers the options and diff for a subversion checkout."""
|
| - def __init__(self, *args, **kwargs):
|
| - SCM.__init__(self, *args, **kwargs)
|
| - self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root)
|
| - if not self.options.email:
|
| - # Assumes the svn credential is an email address.
|
| - self.options.email = scm.SVN.GetEmail(self.checkout_root)
|
| - logging.info("SVN(%s)" % self.checkout_root)
|
| -
|
| - def ReadRootFile(self, filename):
|
| - data = SCM.ReadRootFile(self, filename)
|
| - if data:
|
| - return data
|
| -
|
| - # Try to search on the subversion repository for the file.
|
| - if not gcl:
|
| - return None
|
| - data = gcl.GetCachedFile(filename)
|
| - logging.debug('%s:\n%s' % (filename, data))
|
| - return data
|
| -
|
| - def CaptureStatus(self):
|
| - return scm.SVN.CaptureStatus(None, self.checkout_root)
|
| -
|
| - def GenerateDiff(self):
|
| - """Returns a string containing the diff for the given file list.
|
| -
|
| - The files in the list should either be absolute paths or relative to the
|
| - given root.
|
| - """
|
| - return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True,
|
| - revision=self.diff_against)
|
| -
|
| -
|
| -class GIT(SCM):
|
| - """Gathers the options and diff for a git checkout."""
|
| - def __init__(self, *args, **kwargs):
|
| - SCM.__init__(self, *args, **kwargs)
|
| - self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root)
|
| - if not self.options.name:
|
| - self.options.name = scm.GIT.GetPatchName(self.checkout_root)
|
| - if not self.options.email:
|
| - self.options.email = scm.GIT.GetEmail(self.checkout_root)
|
| - if not self.diff_against:
|
| - self.diff_against = scm.GIT.GetUpstreamBranch(self.checkout_root)
|
| - if not self.diff_against:
|
| - raise NoTryServerAccess(
|
| - "Unable to determine default branch to diff against. "
|
| - "Verify this branch is set up to track another"
|
| - "(via the --track argument to \"git checkout -b ...\"")
|
| - logging.info("GIT(%s)" % self.checkout_root)
|
| -
|
| - def CaptureStatus(self):
|
| - return scm.GIT.CaptureStatus(
|
| - [],
|
| - self.checkout_root.replace(os.sep, '/'),
|
| - self.diff_against)
|
| -
|
| - def GenerateDiff(self):
|
| - if RunGit(['diff-index', 'HEAD']):
|
| - print 'Cannot try with a dirty tree. You must commit locally first.'
|
| - return None
|
| - return scm.GIT.GenerateDiff(
|
| - self.checkout_root,
|
| - files=self.files,
|
| - full_move=True,
|
| - branch=self.diff_against)
|
| -
|
| -
|
| -def _ParseBotList(botlist, testfilter):
|
| - """Parses bot configurations from a list of strings."""
|
| - bots = []
|
| - if testfilter:
|
| - for bot in itertools.chain.from_iterable(botspec.split(',')
|
| - for botspec in botlist):
|
| - tests = set()
|
| - if ':' in bot:
|
| - if bot.endswith(':compile'):
|
| - tests |= set(['compile'])
|
| - else:
|
| - raise ValueError(
|
| - 'Can\'t use both --testfilter and --bot builder:test formats '
|
| - 'at the same time')
|
| -
|
| - bots.append((bot, tests))
|
| - else:
|
| - for botspec in botlist:
|
| - botname = botspec.split(':')[0]
|
| - tests = set()
|
| - if ':' in botspec:
|
| - tests |= set(filter(None, botspec.split(':')[1].split(',')))
|
| - bots.append((botname, tests))
|
| - return bots
|
| -
|
| -
|
| -def _ApplyTestFilter(testfilter, bot_spec):
|
| - """Applies testfilter from CLI.
|
| -
|
| - Specifying a testfilter strips off any builder-specified tests (except for
|
| - compile).
|
| - """
|
| - if testfilter:
|
| - return [(botname, set(testfilter) | (tests & set(['compile'])))
|
| - for botname, tests in bot_spec]
|
| - else:
|
| - return bot_spec
|
| -
|
| -
|
| -def _GenTSBotSpec(checkouts, change, changed_files, options):
|
| - bot_spec = []
|
| - # Get try slaves from PRESUBMIT.py files if not specified.
|
| - # Even if the diff comes from options.url, use the local checkout for bot
|
| - # selection.
|
| - try:
|
| - import presubmit_support
|
| - root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py')
|
| - if not change:
|
| - if not changed_files:
|
| - changed_files = checkouts[0].file_tuples
|
| - change = presubmit_support.Change(options.name,
|
| - '',
|
| - checkouts[0].checkout_root,
|
| - changed_files,
|
| - options.issue,
|
| - options.patchset,
|
| - options.email)
|
| - masters = presubmit_support.DoGetTryMasters(
|
| - change,
|
| - checkouts[0].GetFileNames(),
|
| - checkouts[0].checkout_root,
|
| - root_presubmit,
|
| - options.project,
|
| - options.verbose,
|
| - sys.stdout)
|
| -
|
| - # Compatibility for old checkouts and bots that were on tryserver.chromium.
|
| - trybots = masters.get('tryserver.chromium', [])
|
| -
|
| - # Compatibility for checkouts that are not using tryserver.chromium
|
| - # but are stuck with git-try or gcl-try.
|
| - if not trybots and len(masters) == 1:
|
| - trybots = masters.values()[0]
|
| -
|
| - if trybots:
|
| - old_style = filter(lambda x: isinstance(x, basestring), trybots)
|
| - new_style = filter(lambda x: isinstance(x, tuple), trybots)
|
| -
|
| - # _ParseBotList's testfilter is set to None otherwise it will complain.
|
| - bot_spec = _ApplyTestFilter(options.testfilter,
|
| - _ParseBotList(old_style, None))
|
| -
|
| - bot_spec.extend(_ApplyTestFilter(options.testfilter, new_style))
|
| -
|
| - except ImportError:
|
| - pass
|
| -
|
| - return bot_spec
|
| -
|
| -
|
| -def _ParseSendChangeOptions(bot_spec, options):
|
| - """Parse common options passed to _SendChangeHTTP, _SendChangeSVN and
|
| - _SendChangeGit.
|
| - """
|
| - values = [
|
| - ('user', options.user),
|
| - ('name', options.name),
|
| - ]
|
| - # A list of options to copy.
|
| - optional_values = (
|
| - 'email',
|
| - 'revision',
|
| - 'root',
|
| - 'patchlevel',
|
| - 'issue',
|
| - 'patchset',
|
| - 'target',
|
| - 'project',
|
| - )
|
| - for option_name in optional_values:
|
| - value = getattr(options, option_name)
|
| - if value:
|
| - values.append((option_name, value))
|
| -
|
| - # Not putting clobber to optional_names
|
| - # because it used to have lower-case 'true'.
|
| - if options.clobber:
|
| - values.append(('clobber', 'true'))
|
| -
|
| - for bot, tests in bot_spec:
|
| - values.append(('bot', ('%s:%s' % (bot, ','.join(tests)))))
|
| -
|
| - return values
|
| -
|
| -
|
| -def _SendChangeHTTP(bot_spec, options):
|
| - """Send a change to the try server using the HTTP protocol."""
|
| - if not options.host:
|
| - raise NoTryServerAccess('Please use the --host option to specify the try '
|
| - 'server host to connect to.')
|
| - if not options.port:
|
| - raise NoTryServerAccess('Please use the --port option to specify the try '
|
| - 'server port to connect to.')
|
| -
|
| - values = _ParseSendChangeOptions(bot_spec, options)
|
| - values.append(('patch', options.diff))
|
| -
|
| - url = 'http://%s:%s/send_try_patch' % (options.host, options.port)
|
| -
|
| - logging.info('Sending by HTTP')
|
| - logging.info(''.join("%s=%s\n" % (k, v) for k, v in values))
|
| - logging.info(url)
|
| - logging.info(options.diff)
|
| - if options.dry_run:
|
| - return
|
| -
|
| - try:
|
| - logging.info('Opening connection...')
|
| - connection = urllib2.urlopen(url, urllib.urlencode(values))
|
| - logging.info('Done')
|
| - except IOError, e:
|
| - logging.info(str(e))
|
| - if bot_spec and len(e.args) > 2 and e.args[2] == 'got a bad status line':
|
| - raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url)
|
| - else:
|
| - raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url,
|
| - str(e.args)))
|
| - if not connection:
|
| - raise NoTryServerAccess('%s is unaccessible.' % url)
|
| - logging.info('Reading response...')
|
| - response = connection.read()
|
| - logging.info('Done')
|
| - if response != 'OK':
|
| - raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response))
|
| -
|
| - PrintSuccess(bot_spec, options)
|
| -
|
| -@contextlib.contextmanager
|
| -def _TempFilename(name, contents=None):
|
| - """Create a temporary directory, append the specified name and yield.
|
| -
|
| - In contrast to NamedTemporaryFile, does not keep the file open.
|
| - Deletes the file on __exit__.
|
| - """
|
| - temp_dir = tempfile.mkdtemp(prefix=name)
|
| - try:
|
| - path = os.path.join(temp_dir, name)
|
| - if contents is not None:
|
| - with open(path, 'wb') as f:
|
| - f.write(contents)
|
| - yield path
|
| - finally:
|
| - shutil.rmtree(temp_dir, True)
|
| -
|
| -
|
| -@contextlib.contextmanager
|
| -def _PrepareDescriptionAndPatchFiles(description, options):
|
| - """Creates temporary files with description and patch.
|
| -
|
| - __enter__ called on the return value returns a tuple of patch_filename and
|
| - description_filename.
|
| -
|
| - Args:
|
| - description: contents of description file.
|
| - options: patchset options object. Must have attributes: user,
|
| - name (of patch) and diff (contents of patch).
|
| - """
|
| - current_time = str(datetime.datetime.now()).replace(':', '.')
|
| - patch_basename = '%s.%s.%s.diff' % (Escape(options.user),
|
| - Escape(options.name), current_time)
|
| - with _TempFilename('description', description) as description_filename:
|
| - with _TempFilename(patch_basename, options.diff) as patch_filename:
|
| - yield patch_filename, description_filename
|
| -
|
| -
|
| -def _SendChangeSVN(bot_spec, options):
|
| - """Send a change to the try server by committing a diff file on a subversion
|
| - server."""
|
| - if not options.svn_repo:
|
| - raise NoTryServerAccess('Please use the --svn_repo option to specify the'
|
| - ' try server svn repository to connect to.')
|
| -
|
| - values = _ParseSendChangeOptions(bot_spec, options)
|
| - description = ''.join("%s=%s\n" % (k, v) for k, v in values)
|
| - logging.info('Sending by SVN')
|
| - logging.info(description)
|
| - logging.info(options.svn_repo)
|
| - logging.info(options.diff)
|
| - if options.dry_run:
|
| - return
|
| -
|
| - with _PrepareDescriptionAndPatchFiles(description, options) as (
|
| - patch_filename, description_filename):
|
| - if sys.platform == "cygwin":
|
| - # Small chromium-specific issue here:
|
| - # git-try uses /usr/bin/python on cygwin but svn.bat will be used
|
| - # instead of /usr/bin/svn by default. That causes bad things(tm) since
|
| - # Windows' svn.exe has no clue about cygwin paths. Hence force to use
|
| - # the cygwin version in this particular context.
|
| - exe = "/usr/bin/svn"
|
| - else:
|
| - exe = "svn"
|
| - patch_dir = os.path.dirname(patch_filename)
|
| - command = [exe, 'import', '-q', patch_dir, options.svn_repo, '--file',
|
| - description_filename]
|
| - if scm.SVN.AssertVersion("1.5")[0]:
|
| - command.append('--no-ignore')
|
| -
|
| - try:
|
| - subprocess2.check_call(command)
|
| - except subprocess2.CalledProcessError, e:
|
| - raise NoTryServerAccess(str(e))
|
| -
|
| - PrintSuccess(bot_spec, options)
|
| -
|
| -def _GetPatchGitRepo(git_url):
|
| - """Gets a path to a Git repo with patches.
|
| -
|
| - Stores patches in .git/git-try/patches-git directory, a git repo. If it
|
| - doesn't exist yet or its origin URL is different, cleans up and clones it.
|
| - If it existed before, then pulls changes.
|
| -
|
| - Does not support SVN repo.
|
| -
|
| - Returns a path to the directory with patches.
|
| - """
|
| - git_dir = scm.GIT.GetGitDir(os.getcwd())
|
| - patch_dir = os.path.join(git_dir, GIT_PATCH_DIR_BASENAME)
|
| -
|
| - logging.info('Looking for git repo for patches')
|
| - # Is there already a repo with the expected url or should we clone?
|
| - clone = True
|
| - if os.path.exists(patch_dir) and scm.GIT.IsInsideWorkTree(patch_dir):
|
| - existing_url = scm.GIT.Capture(
|
| - ['config', '--local', 'remote.origin.url'],
|
| - cwd=patch_dir)
|
| - clone = existing_url != git_url
|
| -
|
| - if clone:
|
| - if os.path.exists(patch_dir):
|
| - logging.info('Cleaning up')
|
| - shutil.rmtree(patch_dir, True)
|
| - logging.info('Cloning patch repo')
|
| - scm.GIT.Capture(['clone', git_url, GIT_PATCH_DIR_BASENAME], cwd=git_dir)
|
| - email = scm.GIT.GetEmail(cwd=os.getcwd())
|
| - scm.GIT.Capture(['config', '--local', 'user.email', email], cwd=patch_dir)
|
| - else:
|
| - if scm.GIT.IsWorkTreeDirty(patch_dir):
|
| - logging.info('Work dir is dirty: hard reset!')
|
| - scm.GIT.Capture(['reset', '--hard'], cwd=patch_dir)
|
| - logging.info('Updating patch repo')
|
| - scm.GIT.Capture(['pull', 'origin', 'master'], cwd=patch_dir)
|
| -
|
| - return os.path.abspath(patch_dir)
|
| -
|
| -
|
| -def _SendChangeGit(bot_spec, options):
|
| - """Sends a change to the try server by committing a diff file to a GIT repo.
|
| -
|
| - Creates a temp orphan branch, commits patch.diff, creates a ref pointing to
|
| - that commit, deletes the temp branch, checks master out, adds 'ref' file
|
| - containing the name of the new ref, pushes master and the ref to the origin.
|
| -
|
| - TODO: instead of creating a temp branch, use git-commit-tree.
|
| - """
|
| -
|
| - if not options.git_repo:
|
| - raise NoTryServerAccess('Please use the --git_repo option to specify the '
|
| - 'try server git repository to connect to.')
|
| -
|
| - values = _ParseSendChangeOptions(bot_spec, options)
|
| - comment_subject = '%s.%s' % (options.user, options.name)
|
| - comment_body = ''.join("%s=%s\n" % (k, v) for k, v in values)
|
| - description = '%s\n\n%s' % (comment_subject, comment_body)
|
| - logging.info('Sending by GIT')
|
| - logging.info(description)
|
| - logging.info(options.git_repo)
|
| - logging.info(options.diff)
|
| - if options.dry_run:
|
| - return
|
| -
|
| - patch_dir = _GetPatchGitRepo(options.git_repo)
|
| - def patch_git(*args):
|
| - return scm.GIT.Capture(list(args), cwd=patch_dir)
|
| - def add_and_commit(filename, comment_filename):
|
| - patch_git('add', filename)
|
| - patch_git('commit', '-F', comment_filename)
|
| -
|
| - assert scm.GIT.IsInsideWorkTree(patch_dir)
|
| - assert not scm.GIT.IsWorkTreeDirty(patch_dir)
|
| -
|
| - with _PrepareDescriptionAndPatchFiles(description, options) as (
|
| - patch_filename, description_filename):
|
| - logging.info('Committing patch')
|
| -
|
| - temp_branch = 'tmp_patch'
|
| - target_ref = 'refs/patches/%s/%s' % (
|
| - Escape(options.user),
|
| - os.path.basename(patch_filename).replace(' ','_'))
|
| - target_filename = os.path.join(patch_dir, 'patch.diff')
|
| - branch_file = os.path.join(patch_dir, GIT_BRANCH_FILE)
|
| -
|
| - patch_git('checkout', 'master')
|
| - try:
|
| - # Try deleting an existing temp branch, if any.
|
| - try:
|
| - patch_git('branch', '-D', temp_branch)
|
| - logging.debug('Deleted an existing temp branch.')
|
| - except subprocess2.CalledProcessError:
|
| - pass
|
| - # Create a new branch and put the patch there.
|
| - patch_git('checkout', '--orphan', temp_branch)
|
| - patch_git('reset')
|
| - patch_git('clean', '-f')
|
| - shutil.copyfile(patch_filename, target_filename)
|
| - add_and_commit(target_filename, description_filename)
|
| - assert not scm.GIT.IsWorkTreeDirty(patch_dir)
|
| -
|
| - # Create a ref and point it to the commit referenced by temp_branch.
|
| - patch_git('update-ref', target_ref, temp_branch)
|
| -
|
| - # Delete the temp ref.
|
| - patch_git('checkout', 'master')
|
| - patch_git('branch', '-D', temp_branch)
|
| -
|
| - # Update the branch file in the master.
|
| - def update_branch():
|
| - with open(branch_file, 'w') as f:
|
| - f.write(target_ref)
|
| - add_and_commit(branch_file, description_filename)
|
| -
|
| - update_branch()
|
| -
|
| - # Push master and target_ref to origin.
|
| - logging.info('Pushing patch')
|
| - for attempt in xrange(_GIT_PUSH_ATTEMPTS):
|
| - try:
|
| - patch_git('push', 'origin', 'master', target_ref)
|
| - except subprocess2.CalledProcessError as e:
|
| - is_last = attempt == _GIT_PUSH_ATTEMPTS - 1
|
| - if is_last:
|
| - raise NoTryServerAccess(str(e))
|
| - # Fetch, reset, update branch file again.
|
| - patch_git('fetch', 'origin')
|
| - patch_git('reset', '--hard', 'origin/master')
|
| - update_branch()
|
| - except subprocess2.CalledProcessError, e:
|
| - # Restore state.
|
| - patch_git('checkout', 'master')
|
| - patch_git('reset', '--hard', 'origin/master')
|
| - raise
|
| -
|
| - PrintSuccess(bot_spec, options)
|
| -
|
| -def _SendChangeGerrit(bot_spec, options):
|
| - """Posts a try job to a Gerrit change.
|
| -
|
| - Reads Change-Id from the HEAD commit, resolves the current revision, checks
|
| - that local revision matches the uploaded one, posts a try job in form of a
|
| - message, sets Tryjob-Request label to 1.
|
| -
|
| - Gerrit message format: starts with !tryjob, optionally followed by a tryjob
|
| - definition in JSON format:
|
| - buildNames: list of strings specifying build names.
|
| - build_properties: a dict of build properties.
|
| - """
|
| -
|
| - logging.info('Sending by Gerrit')
|
| - if not options.gerrit_url:
|
| - raise NoTryServerAccess('Please use --gerrit_url option to specify the '
|
| - 'Gerrit instance url to connect to')
|
| - gerrit_host = urlparse.urlparse(options.gerrit_url).hostname
|
| - logging.debug('Gerrit host: %s' % gerrit_host)
|
| -
|
| - def GetChangeId(commmitish):
|
| - """Finds Change-ID of the HEAD commit."""
|
| - CHANGE_ID_RGX = '^Change-Id: (I[a-f0-9]{10,})'
|
| - comment = scm.GIT.Capture(['log', '-1', commmitish, '--format=%b'],
|
| - cwd=os.getcwd())
|
| - change_id_match = re.search(CHANGE_ID_RGX, comment, re.I | re.M)
|
| - if not change_id_match:
|
| - raise Error('Change-Id was not found in the HEAD commit. Make sure you '
|
| - 'have a Git hook installed that generates and inserts a '
|
| - 'Change-Id into a commit message automatically.')
|
| - change_id = change_id_match.group(1)
|
| - return change_id
|
| -
|
| - def FormatMessage():
|
| - # Build job definition.
|
| - job_def = {}
|
| - build_properties = {}
|
| - if options.testfilter:
|
| - build_properties['testfilter'] = options.testfilter
|
| - builderNames = [builder for builder, _ in bot_spec]
|
| - if builderNames:
|
| - job_def['builderNames'] = builderNames
|
| - if build_properties:
|
| - job_def['build_properties'] = build_properties
|
| -
|
| - # Format message.
|
| - msg = '!tryjob'
|
| - if job_def:
|
| - msg = '%s %s' % (msg, json.dumps(job_def, sort_keys=True))
|
| - return msg
|
| -
|
| - def PostTryjob(message):
|
| - logging.info('Posting gerrit message: %s' % message)
|
| - if not options.dry_run:
|
| - # Post a message and set TryJob=1 label.
|
| - try:
|
| - gerrit_util.SetReview(gerrit_host, change_id, msg=message,
|
| - labels={'Tryjob-Request': 1})
|
| - except gerrit_util.GerritError, e:
|
| - if e.http_status == 400:
|
| - raise Error(e.message)
|
| - else:
|
| - raise
|
| -
|
| - head_sha = scm.GIT.Capture(['log', '-1', '--format=%H'], cwd=os.getcwd())
|
| -
|
| - change_id = GetChangeId(head_sha)
|
| -
|
| - try:
|
| - # Check that the uploaded revision matches the local one.
|
| - changes = gerrit_util.GetChangeCurrentRevision(gerrit_host, change_id)
|
| - except gerrit_util.GerritAuthenticationError, e:
|
| - raise NoTryServerAccess(e.message)
|
| -
|
| - assert len(changes) <= 1, 'Multiple changes with id %s' % change_id
|
| - if not changes:
|
| - raise Error('A change %s was not found on the server. Was it uploaded?' %
|
| - change_id)
|
| - logging.debug('Found Gerrit change: %s' % changes[0])
|
| - if changes[0]['current_revision'] != head_sha:
|
| - raise Error('Please upload your latest local changes to Gerrit.')
|
| -
|
| - # Post a try job.
|
| - message = FormatMessage()
|
| - PostTryjob(message)
|
| - change_url = urlparse.urljoin(options.gerrit_url,
|
| - '/#/c/%s' % changes[0]['_number'])
|
| - print('A tryjob was posted on change %s' % change_url)
|
| -
|
| -def PrintSuccess(bot_spec, options):
|
| - if not options.dry_run:
|
| - text = 'Patch \'%s\' sent to try server' % options.name
|
| - if bot_spec:
|
| - text += ': %s' % ', '.join(
|
| - '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec)
|
| - print(text)
|
| -
|
| -
|
| -def GuessVCS(options, path, file_list):
|
| - """Helper to guess the version control system.
|
| -
|
| - NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't
|
| - support it yet.
|
| -
|
| - This examines the path directory, guesses which SCM we're using, and
|
| - returns an instance of the appropriate class. Exit with an error if we can't
|
| - figure it out.
|
| -
|
| - Returns:
|
| - A SCM instance. Exits if the SCM can't be guessed.
|
| - """
|
| - __pychecker__ = 'no-returnvalues'
|
| - real_path = path.split('@')[0]
|
| - logging.info("GuessVCS(%s)" % path)
|
| - # Subversion has a .svn in all working directories.
|
| - if os.path.isdir(os.path.join(real_path, '.svn')):
|
| - return SVN(options, path, file_list)
|
| -
|
| - # Git has a command to test if you're in a git tree.
|
| - # Try running it, but don't die if we don't have git installed.
|
| - try:
|
| - subprocess2.check_output(
|
| - ['git', 'rev-parse', '--is-inside-work-tree'], cwd=real_path,
|
| - stderr=subprocess2.VOID)
|
| - return GIT(options, path, file_list)
|
| - except OSError, e:
|
| - if e.errno != errno.ENOENT:
|
| - raise
|
| - except subprocess2.CalledProcessError, e:
|
| - if e.returncode != errno.ENOENT and e.returncode != 128:
|
| - # ENOENT == 2 = they don't have git installed.
|
| - # 128 = git error code when not in a repo.
|
| - logging.warning('Unexpected error code: %s' % e.returncode)
|
| - raise
|
| - raise NoTryServerAccess(
|
| - ( 'Could not guess version control system for %s.\n'
|
| - 'Are you in a working copy directory?') % path)
|
| -
|
| -
|
| -def GetMungedDiff(path_diff, diff):
|
| - # Munge paths to match svn.
|
| - changed_files = []
|
| - for i in range(len(diff)):
|
| - if diff[i].startswith('--- ') or diff[i].startswith('+++ '):
|
| - new_file = posixpath.join(path_diff, diff[i][4:]).replace('\\', '/')
|
| - if diff[i].startswith('--- '):
|
| - file_path = new_file.split('\t')[0].strip()
|
| - if file_path.startswith('a/'):
|
| - file_path = file_path[2:]
|
| - changed_files.append(('M', file_path))
|
| - diff[i] = diff[i][0:4] + new_file
|
| - return (diff, changed_files)
|
| -
|
| -
|
| -class OptionParser(optparse.OptionParser):
|
| - def format_epilog(self, _):
|
| - """Removes epilog formatting."""
|
| - return self.epilog or ''
|
| -
|
| -
|
| -def gen_parser(prog):
|
| - # Parse argv
|
| - parser = OptionParser(usage=USAGE, version=__version__, prog=prog)
|
| - parser.add_option("-v", "--verbose", action="count", default=0,
|
| - help="Prints debugging infos")
|
| - group = optparse.OptionGroup(parser, "Result and status")
|
| - group.add_option("-u", "--user", default=getpass.getuser(),
|
| - help="Owner user name [default: %default]")
|
| - group.add_option("-e", "--email",
|
| - default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS',
|
| - os.environ.get('EMAIL_ADDRESS')),
|
| - help="Email address where to send the results. Use either "
|
| - "the TRYBOT_RESULTS_EMAIL_ADDRESS environment "
|
| - "variable or EMAIL_ADDRESS to set the email address "
|
| - "the try bots report results to [default: %default]")
|
| - group.add_option("-n", "--name",
|
| - help="Descriptive name of the try job")
|
| - group.add_option("--issue", type='int',
|
| - help="Update rietveld issue try job status")
|
| - group.add_option("--patchset", type='int',
|
| - help="Update rietveld issue try job status. This is "
|
| - "optional if --issue is used, In that case, the "
|
| - "latest patchset will be used.")
|
| - group.add_option("--dry_run", action='store_true',
|
| - help="Don't send the try job. This implies --verbose, so "
|
| - "it will print the diff.")
|
| - parser.add_option_group(group)
|
| -
|
| - group = optparse.OptionGroup(parser, "Try job options")
|
| - group.add_option(
|
| - "-b", "--bot", action="append",
|
| - help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple "
|
| - "times to specify multiple builders. ex: "
|
| - "'-bwin_rel:ui_tests,webkit_unit_tests -bwin_layout'. See "
|
| - "the try server waterfall for the builders name and the tests "
|
| - "available. Can also be used to specify gtest_filter, e.g. "
|
| - "-bwin_rel:base_unittests:ValuesTest.*Value"))
|
| - group.add_option("-B", "--print_bots", action="store_true",
|
| - help="Print bots we would use (e.g. from PRESUBMIT.py)"
|
| - " and exit. Do not send patch. Like --dry_run"
|
| - " but less verbose.")
|
| - group.add_option("-r", "--revision",
|
| - help="Revision to use for the try job. If 'auto' is "
|
| - "specified, it is resolved to the revision a patch is "
|
| - "generated against (Git only). Default: the "
|
| - "revision will be determined by the try server; see "
|
| - "its waterfall for more info")
|
| - group.add_option("-c", "--clobber", action="store_true",
|
| - help="Force a clobber before building; e.g. don't do an "
|
| - "incremental build")
|
| - # TODO(maruel): help="Select a specific configuration, usually 'debug' or "
|
| - # "'release'"
|
| - group.add_option("--target", help=optparse.SUPPRESS_HELP)
|
| -
|
| - group.add_option("--project",
|
| - help="Override which project to use. Projects are defined "
|
| - "server-side to define what default bot set to use")
|
| -
|
| - group.add_option(
|
| - "-t", "--testfilter", action="append", default=[],
|
| - help=("Apply a testfilter to all the selected builders. Unless the "
|
| - "builders configurations are similar, use multiple "
|
| - "--bot <builder>:<test> arguments."))
|
| -
|
| - parser.add_option_group(group)
|
| -
|
| - group = optparse.OptionGroup(parser, "Patch to run")
|
| - group.add_option("-f", "--file", default=[], dest="files",
|
| - metavar="FILE", action="append",
|
| - help="Use many times to list the files to include in the "
|
| - "try, relative to the repository root")
|
| - group.add_option("--diff",
|
| - help="File containing the diff to try")
|
| - group.add_option("--url",
|
| - help="Url where to grab a patch, e.g. "
|
| - "http://example.com/x.diff")
|
| - group.add_option("-R", "--rietveld_url", default="codereview.chromium.org",
|
| - metavar="URL",
|
| - help="Has 2 usages, both refer to the rietveld instance: "
|
| - "Specify which code review patch to use as the try job "
|
| - "or rietveld instance to update the try job results "
|
| - "Default:%default")
|
| - group.add_option("--root",
|
| - help="Root to use for the patch; base subdirectory for "
|
| - "patch created in a subdirectory")
|
| - group.add_option("-p", "--patchlevel", type='int', metavar="LEVEL",
|
| - help="Used as -pN parameter to patch")
|
| - group.add_option("-s", "--sub_rep", action="append", default=[],
|
| - help="Subcheckout to use in addition. This is mainly "
|
| - "useful for gclient-style checkouts. In git, checkout "
|
| - "the branch with changes first. Use @rev or "
|
| - "@branch to specify the "
|
| - "revision/branch to diff against. If no @branch is "
|
| - "given the diff will be against the upstream branch. "
|
| - "If @branch then the diff is branch..HEAD. "
|
| - "All edits must be checked in.")
|
| - group.add_option("--no_search", action="store_true",
|
| - help=("Disable automatic search for gclient or repo "
|
| - "checkout root."))
|
| - group.add_option("-E", "--exclude", action="append",
|
| - default=['ChangeLog'], metavar='REGEXP',
|
| - help="Regexp patterns to exclude files. Default: %default")
|
| - group.add_option("--upstream_branch", action="store",
|
| - help="Specify the upstream branch to diff against in the "
|
| - "main checkout")
|
| - parser.add_option_group(group)
|
| -
|
| - group = optparse.OptionGroup(parser, "Access the try server by HTTP")
|
| - group.add_option("--use_http",
|
| - action="store_const",
|
| - const=_SendChangeHTTP,
|
| - dest="send_patch",
|
| - help="Use HTTP to talk to the try server [default]")
|
| - group.add_option("-H", "--host",
|
| - help="Host address")
|
| - group.add_option("-P", "--port", type="int",
|
| - help="HTTP port")
|
| - parser.add_option_group(group)
|
| -
|
| - group = optparse.OptionGroup(parser, "Access the try server with SVN")
|
| - group.add_option("--use_svn",
|
| - action="store_const",
|
| - const=_SendChangeSVN,
|
| - dest="send_patch",
|
| - help="Use SVN to talk to the try server")
|
| - group.add_option("-S", "--svn_repo",
|
| - metavar="SVN_URL",
|
| - help="SVN url to use to write the changes in; --use_svn is "
|
| - "implied when using --svn_repo")
|
| - parser.add_option_group(group)
|
| -
|
| - group = optparse.OptionGroup(parser, "Access the try server with Git")
|
| - group.add_option("--use_git",
|
| - action="store_const",
|
| - const=_SendChangeGit,
|
| - dest="send_patch",
|
| - help="Use GIT to talk to the try server")
|
| - group.add_option("-G", "--git_repo",
|
| - metavar="GIT_URL",
|
| - help="GIT url to use to write the changes in; --use_git is "
|
| - "implied when using --git_repo")
|
| - parser.add_option_group(group)
|
| -
|
| - group = optparse.OptionGroup(parser, "Access the try server with Gerrit")
|
| - group.add_option("--use_gerrit",
|
| - action="store_const",
|
| - const=_SendChangeGerrit,
|
| - dest="send_patch",
|
| - help="Use Gerrit to talk to the try server")
|
| - group.add_option("--gerrit_url",
|
| - metavar="GERRIT_URL",
|
| - help="Gerrit url to post a tryjob to; --use_gerrit is "
|
| - "implied when using --gerrit_url")
|
| - parser.add_option_group(group)
|
| -
|
| - return parser
|
| -
|
| -
|
| -def TryChange(argv,
|
| - change,
|
| - swallow_exception,
|
| - prog=None,
|
| - extra_epilog=None):
|
| - """
|
| - Args:
|
| - argv: Arguments and options.
|
| - change: Change instance corresponding to the CL.
|
| - swallow_exception: Whether we raise or swallow exceptions.
|
| - """
|
| - parser = gen_parser(prog)
|
| - epilog = EPILOG % { 'prog': prog }
|
| - if extra_epilog:
|
| - epilog += extra_epilog
|
| - parser.epilog = epilog
|
| -
|
| - options, args = parser.parse_args(argv)
|
| -
|
| - # If they've asked for help, give it to them
|
| - if len(args) == 1 and args[0] == 'help':
|
| - parser.print_help()
|
| - return 0
|
| -
|
| - # If they've said something confusing, don't spawn a try job until you
|
| - # understand what they want.
|
| - if args:
|
| - parser.error('Extra argument(s) "%s" not understood' % ' '.join(args))
|
| -
|
| - if options.dry_run:
|
| - options.verbose += 1
|
| -
|
| - LOG_FORMAT = '%(levelname)s %(filename)s(%(lineno)d): %(message)s'
|
| - if not swallow_exception:
|
| - if options.verbose == 0:
|
| - logging.basicConfig(level=logging.WARNING, format=LOG_FORMAT)
|
| - elif options.verbose == 1:
|
| - logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
|
| - elif options.verbose > 1:
|
| - logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
|
| -
|
| - logging.debug(argv)
|
| -
|
| - if (options.patchlevel is not None and
|
| - (options.patchlevel < 0 or options.patchlevel > 10)):
|
| - parser.error(
|
| - 'Have you tried --port instead? You probably confused -p and -P.')
|
| -
|
| - # Strip off any @ in the user, otherwise svn gets confused.
|
| - options.user = options.user.split('@', 1)[0]
|
| -
|
| - if options.rietveld_url:
|
| - # Try to extract the review number if possible and fix the protocol.
|
| - if not '://' in options.rietveld_url:
|
| - options.rietveld_url = 'http://' + options.rietveld_url
|
| - match = re.match(r'^(.*)/(\d+)/?$', options.rietveld_url)
|
| - if match:
|
| - if options.issue or options.patchset:
|
| - parser.error('Cannot use both --issue and use a review number url')
|
| - options.issue = int(match.group(2))
|
| - options.rietveld_url = match.group(1)
|
| -
|
| - try:
|
| - changed_files = None
|
| - # Always include os.getcwd() in the checkout settings.
|
| - path = os.getcwd()
|
| -
|
| - file_list = []
|
| - if options.files:
|
| - file_list = options.files
|
| - elif change:
|
| - file_list = [f.LocalPath() for f in change.AffectedFiles()]
|
| -
|
| - if options.upstream_branch:
|
| - path += '@' + options.upstream_branch
|
| - # Clear file list so that the correct list will be retrieved from the
|
| - # upstream branch.
|
| - file_list = []
|
| -
|
| - current_vcs = GuessVCS(options, path, file_list)
|
| - current_vcs.AutomagicalSettings()
|
| - options = current_vcs.options
|
| - vcs_is_git = type(current_vcs) is GIT
|
| -
|
| - # So far, git_repo doesn't work with SVN
|
| - if options.git_repo and not vcs_is_git:
|
| - parser.error('--git_repo option is supported only for GIT repositories')
|
| -
|
| - # If revision==auto, resolve it
|
| - if options.revision and options.revision.lower() == 'auto':
|
| - if not vcs_is_git:
|
| - parser.error('--revision=auto is supported only for GIT repositories')
|
| - options.revision = scm.GIT.Capture(
|
| - ['rev-parse', current_vcs.diff_against],
|
| - cwd=path)
|
| -
|
| - checkouts = [current_vcs]
|
| - for item in options.sub_rep:
|
| - # Pass file_list=None because we don't know the sub repo's file list.
|
| - checkout = GuessVCS(options,
|
| - os.path.join(current_vcs.checkout_root, item),
|
| - None)
|
| - if checkout.checkout_root in [c.checkout_root for c in checkouts]:
|
| - parser.error('Specified the root %s two times.' %
|
| - checkout.checkout_root)
|
| - checkouts.append(checkout)
|
| -
|
| - can_http = options.port and options.host
|
| - can_svn = options.svn_repo
|
| - can_git = options.git_repo
|
| - can_gerrit = options.gerrit_url
|
| - can_something = can_http or can_svn or can_git or can_gerrit
|
| - # If there was no transport selected yet, now we must have enough data to
|
| - # select one.
|
| - if not options.send_patch and not can_something:
|
| - parser.error('Please specify an access method.')
|
| -
|
| - # Convert options.diff into the content of the diff.
|
| - if options.url:
|
| - if options.files:
|
| - parser.error('You cannot specify files and --url at the same time.')
|
| - options.diff = urllib2.urlopen(options.url).read()
|
| - elif options.diff:
|
| - if options.files:
|
| - parser.error('You cannot specify files and --diff at the same time.')
|
| - options.diff = gclient_utils.FileRead(options.diff, 'rb')
|
| - elif options.issue and options.patchset is None:
|
| - # Retrieve the patch from rietveld when the diff is not specified.
|
| - # When patchset is specified, it's because it's done by gcl/git-try.
|
| - api_url = '%s/api/%d' % (options.rietveld_url, options.issue)
|
| - logging.debug(api_url)
|
| - contents = json.loads(urllib2.urlopen(api_url).read())
|
| - options.patchset = contents['patchsets'][-1]
|
| - diff_url = ('%s/download/issue%d_%d.diff' %
|
| - (options.rietveld_url, options.issue, options.patchset))
|
| - diff = GetMungedDiff('', urllib2.urlopen(diff_url).readlines())
|
| - options.diff = ''.join(diff[0])
|
| - changed_files = diff[1]
|
| - else:
|
| - # Use this as the base.
|
| - root = checkouts[0].checkout_root
|
| - diffs = []
|
| - for checkout in checkouts:
|
| - raw_diff = checkout.GenerateDiff()
|
| - if not raw_diff:
|
| - continue
|
| - diff = raw_diff.splitlines(True)
|
| - path_diff = gclient_utils.PathDifference(root, checkout.checkout_root)
|
| - # Munge it.
|
| - diffs.extend(GetMungedDiff(path_diff, diff)[0])
|
| - if not diffs:
|
| - logging.error('Empty or non-existant diff, exiting.')
|
| - return 1
|
| - options.diff = ''.join(diffs)
|
| -
|
| - if not options.name:
|
| - if options.issue:
|
| - options.name = 'Issue %s' % options.issue
|
| - else:
|
| - options.name = 'Unnamed'
|
| - print('Note: use --name NAME to change the try job name.')
|
| -
|
| - if not options.email:
|
| - parser.error('Using an anonymous checkout. Please use --email or set '
|
| - 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.')
|
| - print('Results will be emailed to: ' + options.email)
|
| -
|
| - if options.bot:
|
| - bot_spec = _ApplyTestFilter(
|
| - options.testfilter, _ParseBotList(options.bot, options.testfilter))
|
| - else:
|
| - bot_spec = _GenTSBotSpec(checkouts, change, changed_files, options)
|
| -
|
| - if options.testfilter:
|
| - bot_spec = _ApplyTestFilter(options.testfilter, bot_spec)
|
| -
|
| - if any('triggered' in b[0] for b in bot_spec):
|
| - print >> sys.stderr, (
|
| - 'ERROR You are trying to send a job to a triggered bot. This type of'
|
| - ' bot requires an\ninitial job from a parent (usually a builder). '
|
| - 'Instead send your job to the parent.\nBot list: %s' % bot_spec)
|
| - return 1
|
| -
|
| - if options.print_bots:
|
| - print 'Bots which would be used:'
|
| - for bot in bot_spec:
|
| - if bot[1]:
|
| - print ' %s:%s' % (bot[0], ','.join(bot[1]))
|
| - else:
|
| - print ' %s' % (bot[0])
|
| - return 0
|
| -
|
| - # Determine sending protocol
|
| - if options.send_patch:
|
| - # If forced.
|
| - senders = [options.send_patch]
|
| - else:
|
| - # Try sending patch using avaialble protocols
|
| - all_senders = [
|
| - (_SendChangeHTTP, can_http),
|
| - (_SendChangeSVN, can_svn),
|
| - (_SendChangeGerrit, can_gerrit),
|
| - (_SendChangeGit, can_git),
|
| - ]
|
| - senders = [sender for sender, can in all_senders if can]
|
| -
|
| - # Send the patch.
|
| - for sender in senders:
|
| - try:
|
| - sender(bot_spec, options)
|
| - return 0
|
| - except NoTryServerAccess:
|
| - is_last = sender == senders[-1]
|
| - if is_last:
|
| - raise
|
| - assert False, "Unreachable code"
|
| - except Error, e:
|
| - if swallow_exception:
|
| - return 1
|
| - print >> sys.stderr, e
|
| - return 1
|
| - except (gclient_utils.Error, subprocess2.CalledProcessError), e:
|
| - print >> sys.stderr, e
|
| - return 1
|
| - return 0
|
| -
|
| -
|
| -if __name__ == "__main__":
|
| - fix_encoding.fix_encoding()
|
| - sys.exit(TryChange(None, None, False))
|
|
|