| Index: tools/push-to-trunk/common_includes.py
|
| diff --git a/tools/push-to-trunk/common_includes.py b/tools/push-to-trunk/common_includes.py
|
| deleted file mode 100644
|
| index 40c47b2871e40faac6b75daa8cbfe6ddfd30efc1..0000000000000000000000000000000000000000
|
| --- a/tools/push-to-trunk/common_includes.py
|
| +++ /dev/null
|
| @@ -1,813 +0,0 @@
|
| -#!/usr/bin/env python
|
| -# Copyright 2013 the V8 project authors. All rights reserved.
|
| -# Redistribution and use in source and binary forms, with or without
|
| -# modification, are permitted provided that the following conditions are
|
| -# met:
|
| -#
|
| -# * Redistributions of source code must retain the above copyright
|
| -# notice, this list of conditions and the following disclaimer.
|
| -# * Redistributions in binary form must reproduce the above
|
| -# copyright notice, this list of conditions and the following
|
| -# disclaimer in the documentation and/or other materials provided
|
| -# with the distribution.
|
| -# * Neither the name of Google Inc. nor the names of its
|
| -# contributors may be used to endorse or promote products derived
|
| -# from this software without specific prior written permission.
|
| -#
|
| -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| -
|
| -import argparse
|
| -import datetime
|
| -import httplib
|
| -import glob
|
| -import imp
|
| -import json
|
| -import os
|
| -import re
|
| -import shutil
|
| -import subprocess
|
| -import sys
|
| -import textwrap
|
| -import time
|
| -import urllib
|
| -import urllib2
|
| -
|
| -from git_recipes import GitRecipesMixin
|
| -from git_recipes import GitFailedException
|
| -
|
| -CHANGELOG_FILE = "ChangeLog"
|
| -VERSION_FILE = os.path.join("src", "version.cc")
|
| -
|
| -# V8 base directory.
|
| -V8_BASE = os.path.dirname(
|
| - os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| -
|
| -
|
| -def TextToFile(text, file_name):
|
| - with open(file_name, "w") as f:
|
| - f.write(text)
|
| -
|
| -
|
| -def AppendToFile(text, file_name):
|
| - with open(file_name, "a") as f:
|
| - f.write(text)
|
| -
|
| -
|
| -def LinesInFile(file_name):
|
| - with open(file_name) as f:
|
| - for line in f:
|
| - yield line
|
| -
|
| -
|
| -def FileToText(file_name):
|
| - with open(file_name) as f:
|
| - return f.read()
|
| -
|
| -
|
| -def MSub(rexp, replacement, text):
|
| - return re.sub(rexp, replacement, text, flags=re.MULTILINE)
|
| -
|
| -
|
| -def Fill80(line):
|
| - # Replace tabs and remove surrounding space.
|
| - line = re.sub(r"\t", r" ", line.strip())
|
| -
|
| - # Format with 8 characters indentation and line width 80.
|
| - return textwrap.fill(line, width=80, initial_indent=" ",
|
| - subsequent_indent=" ")
|
| -
|
| -
|
| -def MakeComment(text):
|
| - return MSub(r"^( ?)", "#", text)
|
| -
|
| -
|
| -def StripComments(text):
|
| - # Use split not splitlines to keep terminal newlines.
|
| - return "\n".join(filter(lambda x: not x.startswith("#"), text.split("\n")))
|
| -
|
| -
|
| -def MakeChangeLogBody(commit_messages, auto_format=False):
|
| - result = ""
|
| - added_titles = set()
|
| - for (title, body, author) in commit_messages:
|
| - # TODO(machenbach): Better check for reverts. A revert should remove the
|
| - # original CL from the actual log entry.
|
| - title = title.strip()
|
| - if auto_format:
|
| - # Only add commits that set the LOG flag correctly.
|
| - log_exp = r"^[ \t]*LOG[ \t]*=[ \t]*(?:(?:Y(?:ES)?)|TRUE)"
|
| - if not re.search(log_exp, body, flags=re.I | re.M):
|
| - continue
|
| - # Never include reverts.
|
| - if title.startswith("Revert "):
|
| - continue
|
| - # Don't include duplicates.
|
| - if title in added_titles:
|
| - continue
|
| -
|
| - # Add and format the commit's title and bug reference. Move dot to the end.
|
| - added_titles.add(title)
|
| - raw_title = re.sub(r"(\.|\?|!)$", "", title)
|
| - bug_reference = MakeChangeLogBugReference(body)
|
| - space = " " if bug_reference else ""
|
| - result += "%s\n" % Fill80("%s%s%s." % (raw_title, space, bug_reference))
|
| -
|
| - # Append the commit's author for reference if not in auto-format mode.
|
| - if not auto_format:
|
| - result += "%s\n" % Fill80("(%s)" % author.strip())
|
| -
|
| - result += "\n"
|
| - return result
|
| -
|
| -
|
| -def MakeChangeLogBugReference(body):
|
| - """Grep for "BUG=xxxx" lines in the commit message and convert them to
|
| - "(issue xxxx)".
|
| - """
|
| - crbugs = []
|
| - v8bugs = []
|
| -
|
| - def AddIssues(text):
|
| - ref = re.match(r"^BUG[ \t]*=[ \t]*(.+)$", text.strip())
|
| - if not ref:
|
| - return
|
| - for bug in ref.group(1).split(","):
|
| - bug = bug.strip()
|
| - match = re.match(r"^v8:(\d+)$", bug)
|
| - if match: v8bugs.append(int(match.group(1)))
|
| - else:
|
| - match = re.match(r"^(?:chromium:)?(\d+)$", bug)
|
| - if match: crbugs.append(int(match.group(1)))
|
| -
|
| - # Add issues to crbugs and v8bugs.
|
| - map(AddIssues, body.splitlines())
|
| -
|
| - # Filter duplicates, sort, stringify.
|
| - crbugs = map(str, sorted(set(crbugs)))
|
| - v8bugs = map(str, sorted(set(v8bugs)))
|
| -
|
| - bug_groups = []
|
| - def FormatIssues(prefix, bugs):
|
| - if len(bugs) > 0:
|
| - plural = "s" if len(bugs) > 1 else ""
|
| - bug_groups.append("%sissue%s %s" % (prefix, plural, ", ".join(bugs)))
|
| -
|
| - FormatIssues("", v8bugs)
|
| - FormatIssues("Chromium ", crbugs)
|
| -
|
| - if len(bug_groups) > 0:
|
| - return "(%s)" % ", ".join(bug_groups)
|
| - else:
|
| - return ""
|
| -
|
| -
|
| -def SortingKey(version):
|
| - """Key for sorting version number strings: '3.11' > '3.2.1.1'"""
|
| - version_keys = map(int, version.split("."))
|
| - # Fill up to full version numbers to normalize comparison.
|
| - while len(version_keys) < 4: # pragma: no cover
|
| - version_keys.append(0)
|
| - # Fill digits.
|
| - return ".".join(map("{0:04d}".format, version_keys))
|
| -
|
| -
|
| -# 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, 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, cwd=cwd)
|
| - else:
|
| - return subprocess.check_call(cmd_line, shell=True, cwd=cwd)
|
| - except subprocess.CalledProcessError:
|
| - return None
|
| - finally:
|
| - sys.stdout.flush()
|
| - sys.stderr.flush()
|
| -
|
| -
|
| -# Wrapper for side effects.
|
| -class SideEffectHandler(object): # pragma: no cover
|
| - def Call(self, fun, *args, **kwargs):
|
| - return fun(*args, **kwargs)
|
| -
|
| - 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()
|
| -
|
| - def ReadURL(self, url, params=None):
|
| - # pylint: disable=E1121
|
| - url_fh = urllib2.urlopen(url, params, 60)
|
| - try:
|
| - return url_fh.read()
|
| - finally:
|
| - url_fh.close()
|
| -
|
| - def ReadClusterFuzzAPI(self, api_key, **params):
|
| - params["api_key"] = api_key.strip()
|
| - params = urllib.urlencode(params)
|
| -
|
| - headers = {"Content-type": "application/x-www-form-urlencoded"}
|
| -
|
| - conn = httplib.HTTPSConnection("backend-dot-cluster-fuzz.appspot.com")
|
| - conn.request("POST", "/_api/", params, headers)
|
| -
|
| - response = conn.getresponse()
|
| - data = response.read()
|
| -
|
| - try:
|
| - return json.loads(data)
|
| - except:
|
| - print data
|
| - print "ERROR: Could not read response. Is your key valid?"
|
| - raise
|
| -
|
| - def Sleep(self, seconds):
|
| - time.sleep(seconds)
|
| -
|
| - def GetDate(self):
|
| - return datetime.date.today().strftime("%Y-%m-%d")
|
| -
|
| - def GetUTCStamp(self):
|
| - return time.mktime(datetime.datetime.utcnow().timetuple())
|
| -
|
| -DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler()
|
| -
|
| -
|
| -class NoRetryException(Exception):
|
| - pass
|
| -
|
| -
|
| -class VCInterface(object):
|
| - def InjectStep(self, step):
|
| - self.step=step
|
| -
|
| - def Pull(self):
|
| - raise NotImplementedError()
|
| -
|
| - def Fetch(self):
|
| - raise NotImplementedError()
|
| -
|
| - def GetTags(self):
|
| - raise NotImplementedError()
|
| -
|
| - def GetBranches(self):
|
| - raise NotImplementedError()
|
| -
|
| - def MasterBranch(self):
|
| - raise NotImplementedError()
|
| -
|
| - def CandidateBranch(self):
|
| - raise NotImplementedError()
|
| -
|
| - def RemoteMasterBranch(self):
|
| - raise NotImplementedError()
|
| -
|
| - def RemoteCandidateBranch(self):
|
| - raise NotImplementedError()
|
| -
|
| - def RemoteBranch(self, name):
|
| - raise NotImplementedError()
|
| -
|
| - def CLLand(self):
|
| - raise NotImplementedError()
|
| -
|
| - def Tag(self, tag, remote, message):
|
| - """Sets a tag for the current commit.
|
| -
|
| - Assumptions: The commit already landed and the commit message is unique.
|
| - """
|
| - raise NotImplementedError()
|
| -
|
| -
|
| -class GitInterface(VCInterface):
|
| - def Pull(self):
|
| - self.step.GitPull()
|
| -
|
| - def Fetch(self):
|
| - self.step.Git("fetch")
|
| -
|
| - def GetTags(self):
|
| - return self.step.Git("tag").strip().splitlines()
|
| -
|
| - def GetBranches(self):
|
| - # Get relevant remote branches, e.g. "branch-heads/3.25".
|
| - branches = filter(
|
| - lambda s: re.match(r"^branch\-heads/\d+\.\d+$", s),
|
| - self.step.GitRemotes())
|
| - # Remove 'branch-heads/' prefix.
|
| - return map(lambda s: s[13:], branches)
|
| -
|
| - def MasterBranch(self):
|
| - return "master"
|
| -
|
| - def CandidateBranch(self):
|
| - return "candidates"
|
| -
|
| - def RemoteMasterBranch(self):
|
| - return "origin/master"
|
| -
|
| - def RemoteCandidateBranch(self):
|
| - return "origin/candidates"
|
| -
|
| - def RemoteBranch(self, name):
|
| - if name in ["candidates", "master"]:
|
| - return "origin/%s" % name
|
| - return "branch-heads/%s" % name
|
| -
|
| - def Tag(self, tag, remote, message):
|
| - # Wait for the commit to appear. Assumes unique commit message titles (this
|
| - # is the case for all automated merge and push commits - also no title is
|
| - # the prefix of another title).
|
| - commit = None
|
| - for wait_interval in [3, 7, 15, 35, 45, 60]:
|
| - self.step.Git("fetch")
|
| - commit = self.step.GitLog(n=1, format="%H", grep=message, branch=remote)
|
| - if commit:
|
| - break
|
| - print("The commit has not replicated to git. Waiting for %s seconds." %
|
| - wait_interval)
|
| - self.step._side_effect_handler.Sleep(wait_interval)
|
| - else:
|
| - self.step.Die("Couldn't determine commit for setting the tag. Maybe the "
|
| - "git updater is lagging behind?")
|
| -
|
| - self.step.Git("tag %s %s" % (tag, commit))
|
| - self.step.Git("push origin %s" % tag)
|
| -
|
| - def CLLand(self):
|
| - self.step.GitCLLand()
|
| -
|
| -
|
| -class Step(GitRecipesMixin):
|
| - def __init__(self, text, number, config, state, options, handler):
|
| - self._text = text
|
| - self._number = number
|
| - self._config = config
|
| - self._state = state
|
| - self._options = options
|
| - self._side_effect_handler = handler
|
| - self.vc = GitInterface()
|
| - self.vc.InjectStep(self)
|
| -
|
| - # The testing configuration might set a different default cwd.
|
| - self.default_cwd = (self._config.get("DEFAULT_CWD") or
|
| - os.path.join(self._options.work_dir, "v8"))
|
| -
|
| - assert self._number >= 0
|
| - assert self._config is not None
|
| - assert self._state is not None
|
| - assert self._side_effect_handler is not None
|
| -
|
| - def __getitem__(self, key):
|
| - # Convenience method to allow direct [] access on step classes for
|
| - # manipulating the backed state dict.
|
| - return self._state[key]
|
| -
|
| - def __setitem__(self, key, value):
|
| - # Convenience method to allow direct [] access on step classes for
|
| - # manipulating the backed state dict.
|
| - self._state[key] = value
|
| -
|
| - def Config(self, key):
|
| - return self._config[key]
|
| -
|
| - def Run(self):
|
| - # Restore state.
|
| - 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)))
|
| -
|
| - print ">>> Step %d: %s" % (self._number, self._text)
|
| - try:
|
| - return self.RunStep()
|
| - finally:
|
| - # Persist state.
|
| - TextToFile(json.dumps(self._state), state_file)
|
| -
|
| - def RunStep(self): # pragma: no cover
|
| - raise NotImplementedError
|
| -
|
| - def Retry(self, cb, retry_on=None, wait_plan=None):
|
| - """ Retry a function.
|
| - Params:
|
| - cb: The function to retry.
|
| - retry_on: A callback that takes the result of the function and returns
|
| - True if the function should be retried. A function throwing an
|
| - exception is always retried.
|
| - wait_plan: A list of waiting delays between retries in seconds. The
|
| - maximum number of retries is len(wait_plan).
|
| - """
|
| - retry_on = retry_on or (lambda x: False)
|
| - wait_plan = list(wait_plan or [])
|
| - wait_plan.reverse()
|
| - while True:
|
| - got_exception = False
|
| - try:
|
| - result = cb()
|
| - except NoRetryException as e:
|
| - raise e
|
| - 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. Reason: %s" %
|
| - str(got_exception))
|
| - wait_time = wait_plan.pop()
|
| - print "Waiting for %f seconds." % wait_time
|
| - self._side_effect_handler.Sleep(wait_time)
|
| - print "Retrying..."
|
| - else:
|
| - return result
|
| -
|
| - def ReadLine(self, default=None):
|
| - # Don't prompt in forced mode.
|
| - if self._options.force_readline_defaults and default is not None:
|
| - print "%s (forced)" % default
|
| - return default
|
| - else:
|
| - return self._side_effect_handler.ReadLine()
|
| -
|
| - 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 Editor(self, args):
|
| - if self._options.requires_editor:
|
| - 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]
|
| - cmd = lambda: self._side_effect_handler.ReadURL(url, params)
|
| - return self.Retry(cmd, retry_on, wait_plan)
|
| -
|
| - def GetDate(self):
|
| - return self._side_effect_handler.GetDate()
|
| -
|
| - def Die(self, msg=""):
|
| - if msg != "":
|
| - print "Error: %s" % msg
|
| - print "Exiting"
|
| - raise Exception(msg)
|
| -
|
| - def DieNoManualMode(self, msg=""):
|
| - if not self._options.manual: # pragma: no cover
|
| - msg = msg or "Only available in manual mode."
|
| - self.Die(msg)
|
| -
|
| - def Confirm(self, msg):
|
| - print "%s [Y/n] " % msg,
|
| - answer = self.ReadLine(default="Y")
|
| - return answer == "" or answer == "Y" or answer == "y"
|
| -
|
| - def DeleteBranch(self, name):
|
| - for line in self.GitBranch().splitlines():
|
| - if re.match(r"\*?\s*%s$" % re.escape(name), line):
|
| - msg = "Branch %s exists, do you want to delete it?" % name
|
| - if self.Confirm(msg):
|
| - self.GitDeleteBranch(name)
|
| - print "Branch %s deleted." % name
|
| - else:
|
| - msg = "Can't continue. Please delete branch %s and try again." % name
|
| - self.Die(msg)
|
| -
|
| - def InitialEnvironmentChecks(self, cwd):
|
| - # Cancel if this is not a git checkout.
|
| - 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
|
| - 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):
|
| - # Check for a clean workdir.
|
| - if not self.GitIsWorkdirClean(): # pragma: no cover
|
| - self.Die("Workspace is not clean. Please commit or undo your changes.")
|
| -
|
| - # Persist current branch.
|
| - self["current_branch"] = self.GitCurrentBranch()
|
| -
|
| - # Fetch unfetched revisions.
|
| - self.vc.Fetch()
|
| -
|
| - def PrepareBranch(self):
|
| - # Delete the branch that will be created later if it exists already.
|
| - self.DeleteBranch(self._config["BRANCHNAME"])
|
| -
|
| - def CommonCleanup(self):
|
| - if ' ' in self["current_branch"]:
|
| - self.GitCheckout('master')
|
| - else:
|
| - self.GitCheckout(self["current_branch"])
|
| - if self._config["BRANCHNAME"] != self["current_branch"]:
|
| - self.GitDeleteBranch(self._config["BRANCHNAME"])
|
| -
|
| - # Clean up all temporary files.
|
| - 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):
|
| - match = re.match(r"^#define %s\s+(\d*)" % def_name, line)
|
| - if match:
|
| - value = match.group(1)
|
| - self["%s%s" % (prefix, var_name)] = value
|
| - 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"),
|
| - ("patch", "PATCH_LEVEL")]:
|
| - ReadAndPersist(var_name, def_name)
|
| -
|
| - def WaitForLGTM(self):
|
| - print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit "
|
| - "your change. (If you need to iterate on the patch or double check "
|
| - "that it's sane, do so in another shell, but remember to not "
|
| - "change the headline of the uploaded CL.")
|
| - answer = ""
|
| - while answer != "LGTM":
|
| - print "> ",
|
| - answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM")
|
| - if answer != "LGTM":
|
| - print "That was not 'LGTM'."
|
| -
|
| - def WaitForResolvingConflicts(self, patch_file):
|
| - print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", "
|
| - "or resolve the conflicts, stage *all* touched files with "
|
| - "'git add', and type \"RESOLVED<Return>\"")
|
| - self.DieNoManualMode()
|
| - answer = ""
|
| - while answer != "RESOLVED":
|
| - if answer == "ABORT":
|
| - self.Die("Applying the patch failed.")
|
| - if answer != "":
|
| - print "That was not 'RESOLVED' or 'ABORT'."
|
| - print "> ",
|
| - answer = self.ReadLine()
|
| -
|
| - # Takes a file containing the patch to apply as first argument.
|
| - def ApplyPatch(self, patch_file, revert=False):
|
| - try:
|
| - self.GitApplyPatch(patch_file, revert)
|
| - except GitFailedException:
|
| - self.WaitForResolvingConflicts(patch_file)
|
| -
|
| - def FindLastCandidatesPush(
|
| - 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 branch or self.vc.RemoteCandidateBranch()
|
| - return self.GitLog(n=1, format="%H", grep=push_pattern,
|
| - parent_hash=parent_hash, branch=branch)
|
| -
|
| - def ArrayToVersion(self, prefix):
|
| - return ".".join([self[prefix + "major"],
|
| - self[prefix + "minor"],
|
| - self[prefix + "build"],
|
| - self[prefix + "patch"]])
|
| -
|
| - def StoreVersion(self, version, prefix):
|
| - version_parts = version.split(".")
|
| - if len(version_parts) == 3:
|
| - version_parts.append("0")
|
| - major, minor, build, patch = version_parts
|
| - self[prefix + "major"] = major
|
| - self[prefix + "minor"] = minor
|
| - self[prefix + "build"] = build
|
| - self[prefix + "patch"] = patch
|
| -
|
| - def SetVersion(self, version_file, prefix):
|
| - output = ""
|
| - for line in FileToText(version_file).splitlines():
|
| - if line.startswith("#define MAJOR_VERSION"):
|
| - line = re.sub("\d+$", self[prefix + "major"], line)
|
| - elif line.startswith("#define MINOR_VERSION"):
|
| - line = re.sub("\d+$", self[prefix + "minor"], line)
|
| - elif line.startswith("#define BUILD_NUMBER"):
|
| - line = re.sub("\d+$", self[prefix + "build"], line)
|
| - elif line.startswith("#define PATCH_LEVEL"):
|
| - line = re.sub("\d+$", self[prefix + "patch"], line)
|
| - output += "%s\n" % line
|
| - TextToFile(output, version_file)
|
| -
|
| -
|
| -class BootstrapStep(Step):
|
| - MESSAGE = "Bootstapping v8 checkout."
|
| -
|
| - def RunStep(self):
|
| - if os.path.realpath(self.default_cwd) == os.path.realpath(V8_BASE):
|
| - self.Die("Can't use v8 checkout with calling script as work checkout.")
|
| - # Directory containing the working v8 checkout.
|
| - if not os.path.exists(self._options.work_dir):
|
| - os.makedirs(self._options.work_dir)
|
| - if not os.path.exists(self.default_cwd):
|
| - self.Command("fetch", "v8", cwd=self._options.work_dir)
|
| -
|
| -
|
| -class UploadStep(Step):
|
| - MESSAGE = "Upload for code review."
|
| -
|
| - def RunStep(self):
|
| - if self._options.reviewer:
|
| - print "Using account %s for review." % self._options.reviewer
|
| - reviewer = self._options.reviewer
|
| - else:
|
| - 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,
|
| - bypass_hooks=self._options.bypass_upload_hooks,
|
| - cc=self._options.cc)
|
| -
|
| -
|
| -class DetermineV8Sheriff(Step):
|
| - MESSAGE = "Determine the V8 sheriff for code review."
|
| -
|
| - def RunStep(self):
|
| - self["sheriff"] = None
|
| - if not self._options.sheriff: # pragma: no cover
|
| - return
|
| -
|
| - try:
|
| - # The googlers mapping maps @google.com accounts to @chromium.org
|
| - # accounts.
|
| - googlers = imp.load_source('googlers_mapping',
|
| - self._options.googlers_mapping)
|
| - googlers = googlers.list_to_dict(googlers.get_list())
|
| - except: # pragma: no cover
|
| - print "Skip determining sheriff without googler mapping."
|
| - return
|
| -
|
| - # The sheriff determined by the rotation on the waterfall has a
|
| - # @google.com account.
|
| - url = "https://chromium-build.appspot.com/p/chromium/sheriff_v8.js"
|
| - match = re.match(r"document\.write\('(\w+)'\)", self.ReadURL(url))
|
| -
|
| - # If "channel is sheriff", we can't match an account.
|
| - if match:
|
| - g_name = match.group(1)
|
| - self["sheriff"] = googlers.get(g_name + "@google.com",
|
| - g_name + "@chromium.org")
|
| - self._options.reviewer = self["sheriff"]
|
| - print "Found active sheriff: %s" % self["sheriff"]
|
| - else:
|
| - print "No active sheriff found."
|
| -
|
| -
|
| -def MakeStep(step_class=Step, number=0, state=None, config=None,
|
| - options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
|
| - # Allow to pass in empty dictionaries.
|
| - state = state if state is not None else {}
|
| - config = config if config is not None else {}
|
| -
|
| - try:
|
| - message = step_class.MESSAGE
|
| - except AttributeError:
|
| - message = step_class.__name__
|
| -
|
| - return step_class(message, number=number, config=config,
|
| - state=state, options=options,
|
| - handler=side_effect_handler)
|
| -
|
| -
|
| -class ScriptsBase(object):
|
| - def __init__(self,
|
| - config=None,
|
| - side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
|
| - state=None):
|
| - self._config = config or self._Config()
|
| - self._side_effect_handler = side_effect_handler
|
| - self._state = state if state is not None else {}
|
| -
|
| - def _Description(self):
|
| - return None
|
| -
|
| - def _PrepareOptions(self, parser):
|
| - pass
|
| -
|
| - def _ProcessOptions(self, options):
|
| - return True
|
| -
|
| - 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="The account name to be used for reviews.")
|
| - parser.add_argument("--sheriff", default=False, action="store_true",
|
| - help=("Determine current sheriff to review CLs. On "
|
| - "success, this will overwrite the reviewer "
|
| - "option."))
|
| - parser.add_argument("-s", "--step",
|
| - help="Specify the step where to start work. Default: 0.",
|
| - default=0, type=int)
|
| - parser.add_argument("--work-dir",
|
| - help=("Location where to bootstrap a working v8 "
|
| - "checkout."))
|
| - self._PrepareOptions(parser)
|
| -
|
| - if args is None: # pragma: no cover
|
| - options = parser.parse_args()
|
| - else:
|
| - options = parser.parse_args(args)
|
| -
|
| - # Process common options.
|
| - if options.step < 0: # pragma: no cover
|
| - print "Bad step number %d" % options.step
|
| - parser.print_help()
|
| - return None
|
| - if options.sheriff and not options.googlers_mapping: # pragma: no cover
|
| - print "To determine the current sheriff, requires the googler mapping"
|
| - 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
|
| - options.wait_for_lgtm = not options.force
|
| - options.force_readline_defaults = not options.manual
|
| - options.force_upload = not options.manual
|
| -
|
| - # Process script specific options.
|
| - if not self._ProcessOptions(options):
|
| - parser.print_help()
|
| - return None
|
| -
|
| - if not options.work_dir:
|
| - options.work_dir = "/tmp/v8-release-scripts-work-dir"
|
| - return options
|
| -
|
| - def RunSteps(self, step_classes, args=None):
|
| - options = self.MakeOptions(args)
|
| - if not options:
|
| - return 1
|
| -
|
| - state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
|
| - if options.step == 0 and os.path.exists(state_file):
|
| - os.remove(state_file)
|
| -
|
| - steps = []
|
| - for (number, step_class) in enumerate([BootstrapStep] + step_classes):
|
| - steps.append(MakeStep(step_class, number, self._state, self._config,
|
| - options, self._side_effect_handler))
|
| - for step in steps[options.step:]:
|
| - if step.Run():
|
| - return 0
|
| - return 0
|
| -
|
| - def Run(self, args=None):
|
| - return self.RunSteps(self._Steps(), args)
|
|
|