Index: trychange.py |
diff --git a/trychange.py b/trychange.py |
index abae97182eba3c657bbeeee400158215cf2ce5eb..47d670c08605fd45151a8bd5f43aa726724b8bf7 100755 |
--- a/trychange.py |
+++ b/trychange.py |
@@ -12,6 +12,7 @@ import getpass |
import logging |
import optparse |
import os |
+import posixpath |
import shutil |
import socket |
import subprocess |
@@ -32,13 +33,12 @@ __version__ = '1.2' |
# Constants |
HELP_STRING = "Sorry, Tryserver is not available." |
-USAGE = r"""%prog [change_name] [options] |
+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. |
- |
Examples: |
Try a change against a particular revision: |
%prog change_name -r 123 |
@@ -54,7 +54,10 @@ Examples: |
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 --bot mac --revision 123 --clobber -f src/a.cc -f src/a.h |
- -f include/b.h""" |
+ -f include/b.h |
+ |
+ When called from gcl, use the format gcl try <change_name>. |
+""" |
class InvalidScript(Exception): |
def __str__(self): |
@@ -77,11 +80,57 @@ class SCM(object): |
self.options = options |
self.files = self.options.files |
self.options.files = None |
+ self.codereview_settings = None |
+ self.codereview_settings_file = 'codereview.settings' |
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 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.""" |
+ settings = { |
+ 'port': self.GetCodeReviewSetting('TRYSERVER_HTTP_PORT'), |
+ 'host': self.GetCodeReviewSetting('TRYSERVER_HTTP_HOST'), |
+ 'svn_repo': self.GetCodeReviewSetting('TRYSERVER_SVN_URL'), |
+ 'project': self.GetCodeReviewSetting('TRYSERVER_PROJECT'), |
+ 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'), |
+ 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'), |
+ } |
+ for (k, v) in settings.iteritems(): |
+ if v and getattr(self.options, k) is None: |
+ setattr(self.options, k, v) |
+ |
+ def GclientStyleSettings(self): |
+ """Find the root, assuming a gclient-style checkout.""" |
+ if not self.options.no_gclient and not self.options.root: |
+ root = self.GetLocalRoot() |
+ gclient_root = gclient_utils.FindGclientRoot(root) |
+ if gclient_root: |
+ self.options.root = gclient_utils.PathDifference(gclient_root, root) |
+ |
+ def AutomagicalSettings(self): |
+ """Determines settings based on supported code review and checkout tools. |
+ """ |
+ self.GclStyleSettings() |
+ self.GclientStyleSettings() |
+ |
class SVN(SCM): |
"""Gathers the options and diff for a subversion checkout.""" |
@@ -92,6 +141,23 @@ class SVN(SCM): |
# Assumes the svn credential is an email address. |
self.options.email = scm.SVN.GetEmail(self.checkout_root) |
+ def ReadRootFile(self, filename): |
+ try: |
+ # Try to search on the subversion repository for the file. |
+ import gcl |
+ data = gcl.GetCachedFile(filename, use_root=True) |
+ logging.debug('%s:\n%s' % (filename, data)) |
+ return data |
+ except ImportError: |
+ try: |
+ data = gclient_utils.FileRead(os.path.join(self.checkout_root, |
+ filename)) |
+ logging.debug('%s:\n%s' % (filename, data)) |
+ return data |
+ except (IOError, OSError): |
+ logging.debug('%s:\nNone' % filename) |
+ return None |
+ |
def GenerateDiff(self): |
"""Returns a string containing the diff for the given file list. |
@@ -113,18 +179,6 @@ class SVN(SCM): |
"""Return the path of the repository root.""" |
return self.checkout_root |
- def GetBots(self): |
- try: |
- # Try to search on the subversion repository for the file. |
- import gcl |
- return gcl.GetCachedFile('PRESUBMIT.py', use_root=True) |
- except ImportError: |
- try: |
- return gclient_utils.FileRead(os.path.join(self.checkout_root, |
- 'PRESUBMIT.py')) |
- except (IOError, OSError): |
- return None |
- |
class GIT(SCM): |
"""Gathers the options and diff for a git checkout.""" |
@@ -136,6 +190,16 @@ class GIT(SCM): |
if not self.options.email: |
self.options.email = scm.GIT.GetEmail(self.checkout_root) |
+ def ReadRootFile(self, filename): |
+ try: |
+ # A git checkout is always a full checkout. |
+ data = gclient_utils.FileRead(os.path.join(self.checkout_root, filename)) |
+ logging.debug('%s:\n%s' % (filename, data)) |
+ return data |
+ except (IOError, OSError): |
+ logging.debug('%s:\nNone' % filename) |
+ return None |
+ |
def GetLocalRoot(self): |
"""Return the path of the repository root.""" |
return self.checkout_root |
@@ -144,14 +208,6 @@ class GIT(SCM): |
# For now, ignores self.files |
return scm.GIT.GenerateDiff(self.checkout_root, full_move=True) |
- def GetBots(self): |
- try: |
- # A git checkout is always a full checkout. |
- return gclient_utils.FileRead(os.path.join(self.checkout_root, |
- 'PRESUBMIT.py')) |
- except (IOError, OSError): |
- return None |
- |
def _ParseSendChangeOptions(options): |
"""Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" |
@@ -193,6 +249,7 @@ def _SendChangeHTTP(options): |
'server port to connect to.') |
values = _ParseSendChangeOptions(options) |
+ description = ''.join("%s=%s\n" % (k,v) for (k,v) in values.iteritems()) |
values['patch'] = options.diff |
url = 'http://%s:%s/send_try_patch' % (options.host, options.port) |
@@ -204,17 +261,16 @@ def _SendChangeHTTP(options): |
else: |
proxies = {'http': options.proxy, 'https': options.proxy} |
+ logging.info(description) |
+ logging.info(url) |
+ logging.info(options.diff) |
if options.dry_run: |
- # Last minute fake. |
- for (k,v) in values.iteritems(): |
- if k != 'patch': |
- print("%s=%s" % (k,v)) |
- print values['patch'] |
return |
try: |
connection = urllib.urlopen(url, urllib.urlencode(values), proxies=proxies) |
except IOError, e: |
+ logging.warning(str(e)) |
if (values.get('bot') and len(e.args) > 2 and |
e.args[2] == 'got a bad status line'): |
raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url) |
@@ -236,14 +292,11 @@ def _SendChangeSVN(options): |
' try server svn repository to connect to.') |
values = _ParseSendChangeOptions(options) |
- description = '' |
- for (k,v) in values.iteritems(): |
- description += "%s=%s\n" % (k,v) |
- |
+ description = ''.join("%s=%s\n" % (k,v) for (k,v) in values.iteritems()) |
+ logging.info(description) |
+ logging.info(options.svn_repo) |
+ logging.info(options.diff) |
if options.dry_run: |
- # Last minute fake. |
- print str(descriptions) |
- print diff |
return |
# Do an empty checkout. |
@@ -308,14 +361,14 @@ def GuessVCS(options, cwd): |
__pychecker__ = 'no-returnvalues' |
# Subversion has a .svn in all working directories. |
if os.path.isdir(os.path.join(cwd, '.svn')): |
- logging.info("Guessed VCS = Subversion") |
+ logging.info("GuessVCS(%s) = Subversion" % cwd) |
return SVN(options, cwd) |
# 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: |
gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"], cwd) |
- logging.info("Guessed VCS = Git") |
+ logging.info("GuessVCS(%s) = Git" % cwd) |
return GIT(options, cwd) |
except gclient_utils.CheckCallError, e: |
if e.retcode != 2: # ENOENT -- they don't have git installed. |
@@ -338,7 +391,8 @@ def TryChange(argv, |
parser = optparse.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]") |
@@ -399,9 +453,11 @@ def TryChange(argv, |
"patch created in a subdirectory") |
group.add_option("--patchlevel", type='int', metavar="LEVEL", |
help="Used as -pN parameter to patch") |
- group.add_option("--sub_rep", action="append", default=["."], |
+ group.add_option("--sub_rep", action="append", default=[], |
help="Subcheckout to use in addition. This is mainly " |
"useful for gclient-style checkouts.") |
+ group.add_option("--no_gclient", action="store_true", |
+ help="Disable automatic search for gclient checkout.") |
parser.add_option_group(group) |
group = optparse.OptionGroup(parser, "Access the try server by HTTP") |
@@ -434,18 +490,20 @@ def TryChange(argv, |
if len(args) == 1 and args[0] == 'help': |
parser.print_help() |
- # Switch the default accordingly if there was no default send_patch. |
- if not options.send_patch: |
- if options.port and options.host: |
- options.send_patch = _SendChangeHTTP |
- elif options.svn_repo: |
- options.send_patch = _SendChangeSVN |
- else: |
- parser.error('Please specify an access method.') |
+ if options.verbose == 0: |
+ logging.basicConfig(level=logging.ERROR) |
+ elif options.verbose == 1: |
+ logging.basicConfig(level=logging.WARNING) |
+ elif options.verbose == 2: |
+ logging.basicConfig(level=logging.INFO) |
+ elif options.verbose > 2: |
+ logging.basicConfig(level=logging.DEBUG) |
try: |
- # Process the VCS in any case at least to retrieve the email address. |
+ # Always include os.getcwd() in the checkout settings. |
checkouts = [] |
+ checkouts.append(GuessVCS(options, os.getcwd())) |
+ checkouts[0].AutomagicalSettings() |
for item in options.sub_rep: |
checkout = GuessVCS(options, item) |
if checkout.GetLocalRoot() in [c.GetLocalRoot() for c in checkouts]: |
@@ -453,6 +511,16 @@ def TryChange(argv, |
checkout.GetLocalRoot()) |
checkouts.append(checkout) |
+ # If there was no transport selected yet, now we must have enough data to |
+ # select one. |
+ if not options.send_patch: |
+ if options.port and options.host: |
+ options.send_patch = _SendChangeHTTP |
+ elif options.svn_repo: |
+ options.send_patch = _SendChangeSVN |
+ else: |
+ parser.error('Please specify an access method.') |
+ |
# Convert options.diff into the content of the diff. |
if options.url: |
if options.files: |
@@ -472,7 +540,7 @@ def TryChange(argv, |
path_diff = gclient_utils.PathDifference(root, checkout.GetLocalRoot()) |
for i in range(len(diff)): |
if diff[i].startswith('--- ') or diff[i].startswith('+++ '): |
- diff[i] = diff[i][0:3] + path_diff + diff[i][4:] |
+ diff[i] = diff[i][0:4] + posixpath.join(path_diff, diff[i][4:]) |
diffs.extend(diff) |
options.diff = ''.join(diffs) |
@@ -482,7 +550,7 @@ def TryChange(argv, |
# selection. |
try: |
import presubmit_support |
- root_presubmit = checkouts[0].GetBots() |
+ root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py') |
options.bot = presubmit_support.DoGetTrySlaves( |
checkouts[0].GetFileNames(), |
checkouts[0].GetLocalRoot(), |