from common_includes import *
import push_to_trunk
-from push_to_trunk import PushToTrunkOptions
-from push_to_trunk import RunPushToTrunk
SETTINGS_LOCATION = "SETTINGS_LOCATION"
}
-class AutoRollOptions(CommonOptions):
- def __init__(self, options):
- super(AutoRollOptions, self).__init__(options)
- self.requires_editor = False
- self.status_password = options.status_password
- self.chromium = options.chromium
- self.push = getattr(options, 'push', False)
- self.author = getattr(options, 'author', None)
-
-
class Preparation(Step):
MESSAGE = "Preparation."
try:
if self._options.push:
self._side_effect_handler.Call(
- RunPushToTrunk,
- push_to_trunk.CONFIG,
- PushToTrunkOptions.MakeForcedOptions(self._options.author,
- self._options.reviewer,
- self._options.chromium),
- self._side_effect_handler)
+ PushToTrunk(push_to_trunk.CONFIG, self._side_effect_handler).Run,
+ ["-a", self._options.author,
+ "-c", self._options.chromium,
+ "-r", self._options.reviewer,
+ "-f"])
finally:
self.PushTreeStatus(self["tree_message"])
else:
% (latest, lkgr))
-def RunAutoRoll(config,
- options,
- side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
- step_classes = [
- Preparation,
- CheckAutoRollSettings,
- CheckTreeStatus,
- FetchLatestRevision,
- CheckLastPush,
- FetchLKGR,
- PushToTrunk,
- ]
- RunScript(step_classes, config, options, side_effect_handler)
-
-
-def BuildOptions():
- parser = argparse.ArgumentParser()
- parser.add_argument("-a", "--author",
- help="The author email used for rietveld.")
- parser.add_argument("-c", "--chromium",
- help=("The path to your Chromium src/ directory to "
- "automate the V8 roll."))
- parser.add_argument("-p", "--push",
- help="Push to trunk if possible. Dry run if unspecified.",
- default=False, action="store_true")
- parser.add_argument("-r", "--reviewer",
- help="The account name to be used for reviews.")
- parser.add_argument("-s", "--step",
- help="Specify the step where to start work. Default: 0.",
- default=0, type=int)
- parser.add_argument("--status-password",
- help="A file with the password to the status app.")
- return parser
-
-
-def Main():
- parser = BuildOptions()
- options = parser.parse_args()
- if not options.author or not options.chromium or not options.reviewer:
- print "You need to specify author, chromium src location and reviewer."
- parser.print_help()
- return 1
- RunAutoRoll(CONFIG, AutoRollOptions(options))
+class AutoRoll(ScriptsBase):
+ def _PrepareOptions(self, parser):
+ parser.add_argument("-c", "--chromium", required=True,
+ help=("The path to your Chromium src/ "
+ "directory to automate the V8 roll."))
+ parser.add_argument("-p", "--push",
+ help="Push to trunk. Dry run if unspecified.",
+ default=False, action="store_true")
+ parser.add_argument("--status-password",
+ help="A file with the password to the status app.")
+
+ def _ProcessOptions(self, options):
+ if not options.author or not options.reviewer:
+ print "You need to specify author and reviewer."
+ return False
+ options.requires_editor = False
+ return True
+
+ def _Steps(self):
+ return [
+ Preparation,
+ CheckAutoRollSettings,
+ CheckTreeStatus,
+ FetchLatestRevision,
+ CheckLastPush,
+ FetchLKGR,
+ PushToTrunk,
+ ]
+
if __name__ == "__main__":
- sys.exit(Main())
+ sys.exit(AutoRoll(CONFIG).Run())
# (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 json
import os
pass
-class CommonOptions(object):
- def __init__(self, options, manual=True):
- self.requires_editor = True
- self.wait_for_lgtm = True
- self.step = options.step
- self.force_readline_defaults = not manual
- self.force_upload = not manual
- self.manual = manual
- self.reviewer = getattr(options, 'reviewer', "")
- self.author = getattr(options, 'author', "")
-
-
class Step(GitRecipesMixin):
def __init__(self, text, requires, number, config, state, options, handler):
self._text = text
assert self._config is not None
assert self._state is not None
assert self._side_effect_handler is not None
- assert isinstance(options, CommonOptions)
def __getitem__(self, key):
# Convenience method to allow direct [] access on step classes for
handler=side_effect_handler)
-def RunScript(step_classes,
- config,
- options,
- side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
- state_file = "%s-state.json" % config[PERSISTFILE_BASENAME]
- if options.step == 0 and os.path.exists(state_file):
- os.remove(state_file)
- state = {}
- steps = []
- for (number, step_class) in enumerate(step_classes):
- steps.append(MakeStep(step_class, number, state, config,
- options, side_effect_handler))
-
- for step in steps[options.step:]:
- step.Run()
+class ScriptsBase(object):
+ # TODO(machenbach): Move static config here.
+ def __init__(self, config, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
+ state=None):
+ self._config = 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):
+ raise Exception("Not implemented.")
+
+ 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("-r", "--reviewer", default="",
+ help="The account name to be used for reviews.")
+ 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:
+ options = parser.parse_args()
+ else:
+ options = parser.parse_args(args)
+
+ # Process common options.
+ if options.step < 0:
+ print "Bad step number %d" % options.step
+ parser.print_help()
+ return None
+
+ # Defaults for options, common to all scripts.
+ options.manual = getattr(options, "manual", True)
+ options.force = getattr(options, "force", 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
+ 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(step_classes):
+ steps.append(MakeStep(step_class, number, self._state, self._config,
+ options, self._side_effect_handler))
+ for step in steps[options.step:]:
+ step.Run()
+ return 0
+
+ def Run(self, args=None):
+ return self.RunSteps(self._Steps(), args)
}
-class MergeToBranchOptions(CommonOptions):
- def __init__(self, options):
- super(MergeToBranchOptions, self).__init__(options, True)
- self.requires_editor = True
- self.wait_for_lgtm = True
- self.delete_sentinel = options.f
- self.message = getattr(options, "message", "")
- self.revert = getattr(options, "r", False)
- self.revert_bleeding_edge = getattr(options, "revert_bleeding_edge", False)
- self.patch = getattr(options, "p", "")
- self.branch = options.branch
- self.revisions = options.revisions
-
-
class Preparation(Step):
MESSAGE = "Preparation."
def RunStep(self):
if os.path.exists(self.Config(ALREADY_MERGING_SENTINEL_FILE)):
- if self._options.delete_sentinel:
+ if self._options.force:
os.remove(self.Config(ALREADY_MERGING_SENTINEL_FILE))
elif self._options.step == 0:
self.Die("A merge is already in progress")
print "patches: %s" % self["revision_list"]
-def RunMergeToBranch(config,
- options,
- side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
- step_classes = [
- Preparation,
- CreateBranch,
- SearchArchitecturePorts,
- FindGitRevisions,
- ApplyPatches,
- PrepareVersion,
- IncrementVersion,
- CommitLocal,
- UploadStep,
- CommitRepository,
- PrepareSVN,
- TagRevision,
- CleanUp,
- ]
-
- RunScript(step_classes, config, options, side_effect_handler)
-
-
-def BuildOptions():
- parser = argparse.ArgumentParser(
- description=("Performs the necessary steps to merge revisions from "
- "bleeding_edge to other branches, including trunk."))
- group = parser.add_mutually_exclusive_group(required=True)
- group.add_argument("--branch", help="The branch to merge to.")
- group.add_argument("-R", "--revert-bleeding-edge",
- help="Revert specified patches from bleeding edge.",
- default=False, action="store_true")
- parser.add_argument("revisions", nargs="*",
- help="The revisions to merge.")
- parser.add_argument("-a", "--author", default="",
- help="The author email used for rietveld.")
- parser.add_argument("-f",
- help="Delete sentinel file.",
- default=False, action="store_true")
- parser.add_argument("-m", "--message",
- help="A commit message for the patch.")
- parser.add_argument("-r", "--revert",
- help="Revert specified patches.",
- default=False, action="store_true")
- parser.add_argument("-p", "--patch", dest="p",
- help="A patch file to apply as part of the merge.")
- parser.add_argument("-s", "--step",
- help="The step where to start work. Default: 0.",
- default=0, type=int)
- return parser
-
-
-def ProcessOptions(options):
- # TODO(machenbach): Add a test that covers revert from bleeding_edge
- if len(options.revisions) < 1:
- if not options.patch:
- print "Either a patch file or revision numbers must be specified"
- return False
- if not options.message:
- print "You must specify a merge comment if no patches are specified"
- return False
- return True
-
-
-def Main():
- parser = BuildOptions()
- options = parser.parse_args()
- if not ProcessOptions(options):
- parser.print_help()
- return 1
- RunMergeToBranch(CONFIG, MergeToBranchOptions(options))
+class MergeToBranch(ScriptsBase):
+ def _Description(self):
+ return ("Performs the necessary steps to merge revisions from "
+ "bleeding_edge to other branches, including trunk.")
+
+ def _PrepareOptions(self, parser):
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument("--branch", help="The branch to merge to.")
+ group.add_argument("-R", "--revert-bleeding-edge",
+ help="Revert specified patches from bleeding edge.",
+ default=False, action="store_true")
+ parser.add_argument("revisions", nargs="*",
+ help="The revisions to merge.")
+ parser.add_argument("-f", "--force",
+ help="Delete sentinel file.",
+ default=False, action="store_true")
+ parser.add_argument("-m", "--message",
+ help="A commit message for the patch.")
+ parser.add_argument("--revert",
+ help="Revert specified patches.",
+ default=False, action="store_true")
+ parser.add_argument("-p", "--patch",
+ help="A patch file to apply as part of the merge.")
+
+ def _ProcessOptions(self, options):
+ # TODO(machenbach): Add a test that covers revert from bleeding_edge
+ if len(options.revisions) < 1:
+ if not options.patch:
+ print "Either a patch file or revision numbers must be specified"
+ return False
+ if not options.message:
+ print "You must specify a merge comment if no patches are specified"
+ return False
+ return True
+
+ def _Steps(self):
+ return [
+ Preparation,
+ CreateBranch,
+ SearchArchitecturePorts,
+ FindGitRevisions,
+ ApplyPatches,
+ PrepareVersion,
+ IncrementVersion,
+ CommitLocal,
+ UploadStep,
+ CommitRepository,
+ PrepareSVN,
+ TagRevision,
+ CleanUp,
+ ]
+
if __name__ == "__main__":
- sys.exit(Main())
+ sys.exit(MergeToBranch(CONFIG).Run())
PUSH_MESSAGE_RE = re.compile(r".* \(based on bleeding_edge revision r(\d+)\)$")
-class PushToTrunkOptions(CommonOptions):
- @staticmethod
- def MakeForcedOptions(author, reviewer, chrome_path):
- """Convenience wrapper."""
- class Options(object):
- pass
- options = Options()
- options.step = 0
- options.last_push = None
- options.last_bleeding_edge = None
- options.force = True
- options.manual = False
- options.chromium = chrome_path
- options.reviewer = reviewer
- options.author = author
- return PushToTrunkOptions(options)
-
- def __init__(self, options):
- super(PushToTrunkOptions, self).__init__(options, options.manual)
- self.requires_editor = not options.force
- self.wait_for_lgtm = not options.force
- self.tbr_commit = not options.manual
- self.last_push = options.last_push
- self.reviewer = options.reviewer
- self.chromium = options.chromium
- self.last_bleeding_edge = getattr(options, 'last_bleeding_edge', None)
- self.author = getattr(options, 'author', None)
-
-
class Preparation(Step):
MESSAGE = "Preparation."
self.GitDeleteBranch(self.Config(TRUNKBRANCH))
-def RunPushToTrunk(config,
- options,
- side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
- step_classes = [
- Preparation,
- FreshBranch,
- DetectLastPush,
- PrepareChangeLog,
- EditChangeLog,
- IncrementVersion,
- CommitLocal,
- UploadStep,
- CommitRepository,
- StragglerCommits,
- SquashCommits,
- NewBranch,
- ApplyChanges,
- SetVersion,
- CommitTrunk,
- SanityCheck,
- CommitSVN,
- TagRevision,
- CheckChromium,
- SwitchChromium,
- UpdateChromiumCheckout,
- UploadCL,
- SwitchV8,
- CleanUp,
- ]
-
- RunScript(step_classes, config, options, side_effect_handler)
-
-
-def BuildOptions():
- parser = argparse.ArgumentParser()
- group = parser.add_mutually_exclusive_group()
- group.add_argument("-f", "--force",
- help="Don't prompt the user.",
- default=False, action="store_true")
- group.add_argument("-m", "--manual",
- help="Prompt the user at every important step.",
- default=False, action="store_true")
- parser.add_argument("-a", "--author",
- help="The author email used for rietveld.")
- parser.add_argument("-b", "--last-bleeding-edge",
- help=("The git commit ID of the last bleeding edge "
- "revision that was pushed to trunk. This is used "
- "for the auto-generated ChangeLog entry."))
- parser.add_argument("-c", "--chromium",
- help=("The path to your Chromium src/ directory to "
- "automate the V8 roll."))
- parser.add_argument("-l", "--last-push",
- help="The git commit ID of the last push to trunk.")
- parser.add_argument("-r", "--reviewer",
- help="The account name to be used for reviews.")
- parser.add_argument("-s", "--step",
- help="The step where to start work. Default: 0.",
- default=0, type=int)
- return parser
-
-
-def ProcessOptions(options):
- if options.step < 0:
- print "Bad step number %d" % options.step
- return False
- if not options.manual and not options.reviewer:
- print "A reviewer (-r) is required in (semi-)automatic mode."
- return False
- if not options.manual and not options.chromium:
- print "A chromium checkout (-c) is required in (semi-)automatic mode."
- return False
- if not options.manual and not options.author:
- print "Specify your chromium.org email with -a in (semi-)automatic mode."
- return False
- return True
-
-
-def Main():
- parser = BuildOptions()
- options = parser.parse_args()
- if not ProcessOptions(options):
- parser.print_help()
- return 1
- RunPushToTrunk(CONFIG, PushToTrunkOptions(options))
+class PushToTrunk(ScriptsBase):
+ def _PrepareOptions(self, parser):
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument("-f", "--force",
+ help="Don't prompt the user.",
+ default=False, action="store_true")
+ group.add_argument("-m", "--manual",
+ help="Prompt the user at every important step.",
+ default=False, action="store_true")
+ parser.add_argument("-b", "--last-bleeding-edge",
+ help=("The git commit ID of the last bleeding edge "
+ "revision that was pushed to trunk. This is "
+ "used for the auto-generated ChangeLog entry."))
+ parser.add_argument("-c", "--chromium",
+ help=("The path to your Chromium src/ "
+ "directory to automate the V8 roll."))
+ parser.add_argument("-l", "--last-push",
+ help="The git commit ID of the last push to trunk.")
+
+ def _ProcessOptions(self, options):
+ if not options.manual and not options.reviewer:
+ print "A reviewer (-r) is required in (semi-)automatic mode."
+ return False
+ if not options.manual and not options.chromium:
+ print "A chromium checkout (-c) is required in (semi-)automatic mode."
+ return False
+ if not options.manual and not options.author:
+ print "Specify your chromium.org email with -a in (semi-)automatic mode."
+ return False
+
+ options.tbr_commit = not options.manual
+ return True
+
+ def _Steps(self):
+ return [
+ Preparation,
+ FreshBranch,
+ DetectLastPush,
+ PrepareChangeLog,
+ EditChangeLog,
+ IncrementVersion,
+ CommitLocal,
+ UploadStep,
+ CommitRepository,
+ StragglerCommits,
+ SquashCommits,
+ NewBranch,
+ ApplyChanges,
+ SetVersion,
+ CommitTrunk,
+ SanityCheck,
+ CommitSVN,
+ TagRevision,
+ CheckChromium,
+ SwitchChromium,
+ UpdateChromiumCheckout,
+ UploadCL,
+ SwitchV8,
+ CleanUp,
+ ]
+
if __name__ == "__main__":
- sys.exit(Main())
+ sys.exit(PushToTrunk(CONFIG).Run())
import unittest
import auto_roll
-from auto_roll import AutoRollOptions
from auto_roll import CheckLastPush
from auto_roll import FetchLatestRevision
from auto_roll import SETTINGS_LOCATION
]
-def MakeOptions(s=0, l=None, f=False, m=True, r=None, c=None, a=None,
- status_password=None, revert_bleeding_edge=None, p=None):
- """Convenience wrapper."""
- class Options(object):
- pass
- options = Options()
- options.step = s
- options.last_push = l
- options.force = f
- options.manual = m
- options.reviewer = r
- options.chromium = c
- options.author = a
- options.push = p
- options.status_password = status_password
- options.revert_bleeding_edge = revert_bleeding_edge
- return options
-
-
class ToplevelTest(unittest.TestCase):
def testMakeComment(self):
self.assertEquals("# Line 1\n# Line 2\n#",
f.write("#define IS_CANDIDATE_VERSION 0\n")
return name
- def MakeStep(self, step_class=Step, state=None, options=None):
+ def MakeStep(self):
+ """Convenience wrapper."""
+ options = ScriptsBase(TEST_CONFIG, self, self._state).MakeOptions([])
+ return MakeStep(step_class=Step, state=self._state,
+ config=TEST_CONFIG, side_effect_handler=self,
+ options=options)
+
+ def RunStep(self, script=PushToTrunk, step_class=Step, args=None):
"""Convenience wrapper."""
- options = options or CommonOptions(MakeOptions())
- state = state if state is not None else self._state
- return MakeStep(step_class=step_class, number=0, state=state,
- config=TEST_CONFIG, options=options,
- side_effect_handler=self)
+ args = args or ["-m"]
+ return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
def GitMock(self, cmd, args="", pipe=True):
print "%s %s" % (cmd, args)
])
self._state["last_push_bleeding_edge"] = "1234"
- self.MakeStep(PrepareChangeLog).Run()
+ self.RunStep(PushToTrunk, PrepareChangeLog)
actual_cl = FileToText(TEST_CONFIG[CHANGELOG_ENTRY_FILE])
"", # Open editor.
])
- self.MakeStep(EditChangeLog).Run()
+ self.RunStep(PushToTrunk, EditChangeLog)
self.assertEquals("New\n Lines\n\n\n Original CL",
FileToText(TEST_CONFIG[CHANGELOG_FILE]))
"Y", # Increment build number.
])
- self.MakeStep(IncrementVersion).Run()
+ self.RunStep(PushToTrunk, IncrementVersion)
self.assertEquals("3", self._state["new_major"])
self.assertEquals("22", self._state["new_minor"])
self._state["prepare_commit_hash"] = "hash1"
self._state["date"] = "1999-11-11"
- self.MakeStep(SquashCommits).Run()
+ self.RunStep(PushToTrunk, SquashCommits)
self.assertEquals(FileToText(TEST_CONFIG[COMMITMSG_FILE]), expected_msg)
patch = FileToText(TEST_CONFIG[ PATCH_FILE])
if force: args.append("-f")
if manual: args.append("-m")
else: args += ["-r", "reviewer@chromium.org"]
- options = push_to_trunk.BuildOptions().parse_args(args)
- RunPushToTrunk(TEST_CONFIG, PushToTrunkOptions(options), self)
+ PushToTrunk(TEST_CONFIG, self).Run(args)
deps = FileToText(TEST_CONFIG[DEPS_FILE])
self.assertTrue(re.search("\"v8_revision\": \"123456\"", deps))
["svn log -1 --oneline ChangeLog", "r99 | Prepare push to trunk..."],
])
- state = {}
- self.MakeStep(FetchLatestRevision, state=state).Run()
- self.assertRaises(Exception, self.MakeStep(CheckLastPush, state=state).Run)
+ self.RunStep(auto_roll.AutoRoll, FetchLatestRevision, AUTO_ROLL_ARGS)
+ self.assertRaises(Exception, lambda: self.RunStep(auto_roll.AutoRoll,
+ CheckLastPush,
+ AUTO_ROLL_ARGS))
def testAutoRoll(self):
password = self.MakeEmptyTempFile()
["svn find-rev push_hash", "65"],
])
- options = auto_roll.BuildOptions().parse_args(
+ auto_roll.AutoRoll(TEST_CONFIG, self).Run(
AUTO_ROLL_ARGS + ["--status-password", password])
- auto_roll.RunAutoRoll(TEST_CONFIG, AutoRollOptions(options), self)
state = json.loads(FileToText("%s-state.json"
% TEST_CONFIG[PERSISTFILE_BASENAME]))
["svn fetch", ""],
])
- options = auto_roll.BuildOptions().parse_args(AUTO_ROLL_ARGS)
def RunAutoRoll():
- auto_roll.RunAutoRoll(TEST_CONFIG, AutoRollOptions(options), self)
+ auto_roll.AutoRoll(TEST_CONFIG, self).Run(AUTO_ROLL_ARGS)
self.assertRaises(Exception, RunAutoRoll)
def testAutoRollStoppedByTreeStatus(self):
["svn fetch", ""],
])
- options = auto_roll.BuildOptions().parse_args(AUTO_ROLL_ARGS)
def RunAutoRoll():
- auto_roll.RunAutoRoll(TEST_CONFIG, AutoRollOptions(options), self)
+ auto_roll.AutoRoll(TEST_CONFIG, self).Run(AUTO_ROLL_ARGS)
self.assertRaises(Exception, RunAutoRoll)
def testMergeToBranch(self):
# ports of r12345. r56789 is the MIPS port of r34567.
args = ["-f", "-p", extra_patch, "--branch", "trunk", "12345", "23456",
"34567"]
- options = merge_to_branch.BuildOptions().parse_args(args)
- self.assertTrue(merge_to_branch.ProcessOptions(options))
# The first run of the script stops because of the svn being down.
self.assertRaises(GitFailedException,
- lambda: RunMergeToBranch(TEST_CONFIG,
- MergeToBranchOptions(options),
- self))
+ lambda: MergeToBranch(TEST_CONFIG, self).Run(args))
# Test that state recovery after restarting the script works.
- options.step = 3
- RunMergeToBranch(TEST_CONFIG, MergeToBranchOptions(options), self)
+ args += ["-s", "3"]
+ MergeToBranch(TEST_CONFIG, self).Run(args)
class SystemTest(unittest.TestCase):
def testReload(self):
step = MakeStep(step_class=PrepareChangeLog, number=0, state={}, config={},
- options=CommonOptions(MakeOptions()),
side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER)
body = step.Reload(
"""------------------------------------------------------------------------