import argparse
import datetime
import httplib
+import glob
import imp
import json
import os
import re
+import shutil
import subprocess
import sys
import textwrap
from git_recipes import GitRecipesMixin
from git_recipes import GitFailedException
-PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME"
-BRANCHNAME = "BRANCHNAME"
-DOT_GIT_LOCATION = "DOT_GIT_LOCATION"
-VERSION_FILE = "VERSION_FILE"
-CHANGELOG_FILE = "CHANGELOG_FILE"
-CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE"
-COMMITMSG_FILE = "COMMITMSG_FILE"
-PATCH_FILE = "PATCH_FILE"
+VERSION_FILE = os.path.join("src", "version.cc")
+
+# V8 base directory.
+DEFAULT_CWD = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def TextToFile(text, file_name):
# Some commands don't like the pipe, e.g. calling vi from within the script or
# from subscripts like git cl upload.
-def Command(cmd, args="", prefix="", pipe=True):
+def Command(cmd, args="", prefix="", pipe=True, cwd=None):
+ cwd = cwd or os.getcwd()
# TODO(machenbach): Use timeout.
cmd_line = "%s %s %s" % (prefix, cmd, args)
print "Command: %s" % cmd_line
+ print "in %s" % cwd
sys.stdout.flush()
try:
if pipe:
- return subprocess.check_output(cmd_line, shell=True)
+ return subprocess.check_output(cmd_line, shell=True, cwd=cwd)
else:
- return subprocess.check_call(cmd_line, shell=True)
+ return subprocess.check_call(cmd_line, shell=True, cwd=cwd)
except subprocess.CalledProcessError:
return None
finally:
def Call(self, fun, *args, **kwargs):
return fun(*args, **kwargs)
- def Command(self, cmd, args="", prefix="", pipe=True):
- return Command(cmd, args, prefix, pipe)
+ def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
+ return Command(cmd, args, prefix, pipe, cwd=cwd)
def ReadLine(self):
return sys.stdin.readline().strip()
class Step(GitRecipesMixin):
- def __init__(self, text, requires, number, config, state, options, handler):
+ def __init__(self, text, number, config, state, options, handler):
self._text = text
- self._requires = requires
self._number = number
self._config = config
self._state = state
self._options = options
self._side_effect_handler = handler
+
+ # The testing configuration might set a different default cwd.
+ self.default_cwd = self._config.get("DEFAULT_CWD") or DEFAULT_CWD
+
assert self._number >= 0
assert self._config is not None
assert self._state is not None
def Run(self):
# Restore state.
- state_file = "%s-state.json" % self._config[PERSISTFILE_BASENAME]
+ state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
if not self._state and os.path.exists(state_file):
self._state.update(json.loads(FileToText(state_file)))
- # Skip step if requirement is not met.
- if self._requires and not self._state.get(self._requires):
- return
-
print ">>> Step %d: %s" % (self._number, self._text)
try:
return self.RunStep()
got_exception = False
try:
result = cb()
- except NoRetryException, e:
+ except NoRetryException as e:
raise e
- except Exception:
- got_exception = True
+ except Exception as e:
+ got_exception = e
if got_exception or retry_on(result):
if not wait_plan: # pragma: no cover
- raise Exception("Retried too often. Giving up.")
+ raise Exception("Retried too often. Giving up. Reason: %s" %
+ str(got_exception))
wait_time = wait_plan.pop()
print "Waiting for %f seconds." % wait_time
self._side_effect_handler.Sleep(wait_time)
else:
return self._side_effect_handler.ReadLine()
- def Git(self, args="", prefix="", pipe=True, retry_on=None):
- cmd = lambda: self._side_effect_handler.Command("git", args, prefix, pipe)
+ def Command(self, name, args, cwd=None):
+ cmd = lambda: self._side_effect_handler.Command(
+ name, args, "", True, cwd=cwd or self.default_cwd)
+ return self.Retry(cmd, None, [5])
+
+ def Git(self, args="", prefix="", pipe=True, retry_on=None, cwd=None):
+ cmd = lambda: self._side_effect_handler.Command(
+ "git", args, prefix, pipe, cwd=cwd or self.default_cwd)
result = self.Retry(cmd, retry_on, [5, 30])
if result is None:
raise GitFailedException("'git %s' failed." % args)
return result
- def SVN(self, args="", prefix="", pipe=True, retry_on=None):
- cmd = lambda: self._side_effect_handler.Command("svn", args, prefix, pipe)
+ def SVN(self, args="", prefix="", pipe=True, retry_on=None, cwd=None):
+ cmd = lambda: self._side_effect_handler.Command(
+ "svn", args, prefix, pipe, cwd=cwd or self.default_cwd)
return self.Retry(cmd, retry_on, [5, 30])
def Editor(self, args):
if self._options.requires_editor:
- return self._side_effect_handler.Command(os.environ["EDITOR"], args,
- pipe=False)
+ return self._side_effect_handler.Command(
+ os.environ["EDITOR"],
+ args,
+ pipe=False,
+ cwd=self.default_cwd)
def ReadURL(self, url, params=None, retry_on=None, wait_plan=None):
wait_plan = wait_plan or [3, 60, 600]
msg = "Can't continue. Please delete branch %s and try again." % name
self.Die(msg)
- def InitialEnvironmentChecks(self):
+ def InitialEnvironmentChecks(self, cwd):
# Cancel if this is not a git checkout.
- if not os.path.exists(self._config[DOT_GIT_LOCATION]): # pragma: no cover
+ if not os.path.exists(os.path.join(cwd, ".git")): # pragma: no cover
self.Die("This is not a git checkout, this script won't work for you.")
# Cancel if EDITOR is unset or not executable.
if (self._options.requires_editor and (not os.environ.get("EDITOR") or
- Command("which", os.environ["EDITOR"]) is None)): # pragma: no cover
+ self.Command(
+ "which", os.environ["EDITOR"]) is None)): # pragma: no cover
self.Die("Please set your EDITOR environment variable, you'll need it.")
def CommonPrepare(self):
def PrepareBranch(self):
# Delete the branch that will be created later if it exists already.
- self.DeleteBranch(self._config[BRANCHNAME])
+ self.DeleteBranch(self._config["BRANCHNAME"])
def CommonCleanup(self):
self.GitCheckout(self["current_branch"])
- if self._config[BRANCHNAME] != self["current_branch"]:
- self.GitDeleteBranch(self._config[BRANCHNAME])
+ if self._config["BRANCHNAME"] != self["current_branch"]:
+ self.GitDeleteBranch(self._config["BRANCHNAME"])
# Clean up all temporary files.
- Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME])
+ for f in glob.iglob("%s*" % self._config["PERSISTFILE_BASENAME"]):
+ if os.path.isfile(f):
+ os.remove(f)
+ if os.path.isdir(f):
+ shutil.rmtree(f)
def ReadAndPersistVersion(self, prefix=""):
def ReadAndPersist(var_name, def_name):
if match:
value = match.group(1)
self["%s%s" % (prefix, var_name)] = value
- for line in LinesInFile(self._config[VERSION_FILE]):
+ for line in LinesInFile(os.path.join(self.default_cwd, VERSION_FILE)):
for (var_name, def_name) in [("major", "MAJOR_VERSION"),
("minor", "MINOR_VERSION"),
("build", "BUILD_NUMBER"),
except GitFailedException:
self.WaitForResolvingConflicts(patch_file)
- def FindLastTrunkPush(self, parent_hash="", include_patches=False):
+ def FindLastTrunkPush(
+ self, parent_hash="", branch="", include_patches=False):
push_pattern = "^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]*"
if not include_patches:
# Non-patched versions only have three numbers followed by the "(based
# on...) comment."
push_pattern += " (based"
- branch = "" if parent_hash else "svn/trunk"
+ branch = "" if parent_hash else branch or "svn/trunk"
return self.GitLog(n=1, format="%H", grep=push_pattern,
parent_hash=parent_hash, branch=branch)
output += "%s\n" % line
TextToFile(output, version_file)
+ def SVNCommit(self, root, commit_message):
+ patch = self.GitDiff("HEAD^", "HEAD")
+ TextToFile(patch, self._config["PATCH_FILE"])
+ self.Command("svn", "update", cwd=self._options.svn)
+ if self.Command("svn", "status", cwd=self._options.svn) != "":
+ self.Die("SVN checkout not clean.")
+ if not self.Command("patch", "-d %s -p1 -i %s" %
+ (root, self._config["PATCH_FILE"]),
+ cwd=self._options.svn):
+ self.Die("Could not apply patch.")
+ self.Command(
+ "svn",
+ "commit --non-interactive --username=%s --config-dir=%s -m \"%s\"" %
+ (self._options.author, self._options.svn_config, commit_message),
+ cwd=self._options.svn)
+
+
class UploadStep(Step):
MESSAGE = "Upload for code review."
print "Please enter the email address of a V8 reviewer for your patch: ",
self.DieNoManualMode("A reviewer must be specified in forced mode.")
reviewer = self.ReadLine()
- self.GitUpload(reviewer, self._options.author, self._options.force_upload)
+ self.GitUpload(reviewer, self._options.author, self._options.force_upload,
+ bypass_hooks=self._options.bypass_upload_hooks)
class DetermineV8Sheriff(Step):
message = step_class.MESSAGE
except AttributeError:
message = step_class.__name__
- try:
- requires = step_class.REQUIRES
- except AttributeError:
- requires = None
- return step_class(message, requires, number=number, config=config,
+ return step_class(message, number=number, config=config,
state=state, options=options,
handler=side_effect_handler)
class ScriptsBase(object):
# TODO(machenbach): Move static config here.
- def __init__(self, config, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
+ def __init__(self,
+ config=None,
+ side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
state=None):
- self._config = config
+ self._config = config or self._Config()
self._side_effect_handler = side_effect_handler
self._state = state if state is not None else {}
def _Steps(self): # pragma: no cover
raise Exception("Not implemented.")
+ def _Config(self):
+ return {}
+
def MakeOptions(self, args=None):
parser = argparse.ArgumentParser(description=self._Description())
parser.add_argument("-a", "--author", default="",
help="The author email used for rietveld.")
+ parser.add_argument("--dry-run", default=False, action="store_true",
+ help="Perform only read-only actions.")
parser.add_argument("-g", "--googlers-mapping",
help="Path to the script mapping google accounts.")
parser.add_argument("-r", "--reviewer", default="",
help=("Determine current sheriff to review CLs. On "
"success, this will overwrite the reviewer "
"option."))
+ parser.add_argument("--svn",
+ help=("Optional full svn checkout for the commit."
+ "The folder needs to be the svn root."))
+ parser.add_argument("--svn-config",
+ help=("Optional folder used as svn --config-dir."))
parser.add_argument("-s", "--step",
help="Specify the step where to start work. Default: 0.",
default=0, type=int)
-
self._PrepareOptions(parser)
if args is None: # pragma: no cover
print "To determine the current sheriff, requires the googler mapping"
parser.print_help()
return None
+ if options.svn and not options.svn_config:
+ print "Using pure svn for committing requires also --svn-config"
+ parser.print_help()
+ return None
# Defaults for options, common to all scripts.
options.manual = getattr(options, "manual", True)
options.force = getattr(options, "force", False)
+ options.bypass_upload_hooks = False
# Derived options.
options.requires_editor = not options.force
if not options:
return 1
- state_file = "%s-state.json" % self._config[PERSISTFILE_BASENAME]
+ state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
if options.step == 0 and os.path.exists(state_file):
os.remove(state_file)
options, self._side_effect_handler))
for step in steps[options.step:]:
if step.Run():
- return 1
+ return 0
return 0
def Run(self, args=None):