[SCM] WebKit Debian packaging branch, webkit-1.2, updated. upstream/1.1.90-6072-g9a69373

abarth at webkit.org abarth at webkit.org
Thu Apr 8 00:53:08 UTC 2010


The following commit has been merged in the webkit-1.2 branch:
commit 7bfec00fbc8cd537c3a463584cd6e78cb850a932
Author: abarth at webkit.org <abarth at webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Date:   Mon Jan 4 08:47:16 2010 +0000

    2010-01-04  Adam Barth  <abarth at webkit.org>
    
            Reviewed by Eric Seidel.
    
            [bzt] Move steps to a submodule
            https://bugs.webkit.org/show_bug.cgi?id=33135
    
            For great victory.  This will probably introduce some regressions
            because our test coverage isn't perfect, but I've tried to be careful.
    
            * Scripts/test-webkitpy:
            * Scripts/webkitpy/buildsteps.py: Removed.
            * Scripts/webkitpy/buildsteps_unittest.py: Removed.
            * Scripts/webkitpy/commands/download.py:
            * Scripts/webkitpy/commands/upload.py:
            * Scripts/webkitpy/steps/__init__.py: Added.
            * Scripts/webkitpy/steps/abstractstep.py: Added.
            * Scripts/webkitpy/steps/applypatch.py: Added.
            * Scripts/webkitpy/steps/applypatchwithlocalcommit.py: Added.
            * Scripts/webkitpy/steps/build.py: Added.
            * Scripts/webkitpy/steps/checkstyle.py: Added.
            * Scripts/webkitpy/steps/cleanworkingdirectory.py: Added.
            * Scripts/webkitpy/steps/cleanworkingdirectorywithlocalcommits.py: Added.
            * Scripts/webkitpy/steps/closebug.py: Added.
            * Scripts/webkitpy/steps/closebugforlanddiff.py: Added.
            * Scripts/webkitpy/steps/closepatch.py: Added.
            * Scripts/webkitpy/steps/commit.py: Added.
            * Scripts/webkitpy/steps/completerollout.py: Added.
            * Scripts/webkitpy/steps/confirmdiff.py: Added.
            * Scripts/webkitpy/steps/createbug.py: Added.
            * Scripts/webkitpy/steps/editchangelog.py: Added.
            * Scripts/webkitpy/steps/ensurebuildersaregreen.py: Added.
            * Scripts/webkitpy/steps/ensurelocalcommitifneeded.py: Added.
            * Scripts/webkitpy/steps/metastep.py: Added.
            * Scripts/webkitpy/steps/obsoletepatches.py: Added.
            * Scripts/webkitpy/steps/options.py: Added.
            * Scripts/webkitpy/steps/postdiff.py: Added.
            * Scripts/webkitpy/steps/preparechangelog.py: Added.
            * Scripts/webkitpy/steps/preparechangelogforrevert.py: Added.
            * Scripts/webkitpy/steps/promptforbugortitle.py: Added.
            * Scripts/webkitpy/steps/revertrevision.py: Added.
            * Scripts/webkitpy/steps/runtests.py: Added.
            * Scripts/webkitpy/steps/steps_unittest.py: Added.
            * Scripts/webkitpy/steps/update.py: Added.
            * Scripts/webkitpy/steps/updatechangelogswithreview_unittests.py: Added.
            * Scripts/webkitpy/steps/updatechangelogswithreviewer.py: Added.
            * Scripts/webkitpy/stepsequence.py:
    
    git-svn-id: http://svn.webkit.org/repository/webkit/trunk@52714 268f45cc-cd09-0410-ab3c-d52691b4dbfc

diff --git a/WebKitTools/ChangeLog b/WebKitTools/ChangeLog
index cac8831..e6925f3 100644
--- a/WebKitTools/ChangeLog
+++ b/WebKitTools/ChangeLog
@@ -1,3 +1,51 @@
+2010-01-04  Adam Barth  <abarth at webkit.org>
+
+        Reviewed by Eric Seidel.
+
+        [bzt] Move steps to a submodule
+        https://bugs.webkit.org/show_bug.cgi?id=33135
+
+        For great victory.  This will probably introduce some regressions
+        because our test coverage isn't perfect, but I've tried to be careful.
+
+        * Scripts/test-webkitpy:
+        * Scripts/webkitpy/buildsteps.py: Removed.
+        * Scripts/webkitpy/buildsteps_unittest.py: Removed.
+        * Scripts/webkitpy/commands/download.py:
+        * Scripts/webkitpy/commands/upload.py:
+        * Scripts/webkitpy/steps/__init__.py: Added.
+        * Scripts/webkitpy/steps/abstractstep.py: Added.
+        * Scripts/webkitpy/steps/applypatch.py: Added.
+        * Scripts/webkitpy/steps/applypatchwithlocalcommit.py: Added.
+        * Scripts/webkitpy/steps/build.py: Added.
+        * Scripts/webkitpy/steps/checkstyle.py: Added.
+        * Scripts/webkitpy/steps/cleanworkingdirectory.py: Added.
+        * Scripts/webkitpy/steps/cleanworkingdirectorywithlocalcommits.py: Added.
+        * Scripts/webkitpy/steps/closebug.py: Added.
+        * Scripts/webkitpy/steps/closebugforlanddiff.py: Added.
+        * Scripts/webkitpy/steps/closepatch.py: Added.
+        * Scripts/webkitpy/steps/commit.py: Added.
+        * Scripts/webkitpy/steps/completerollout.py: Added.
+        * Scripts/webkitpy/steps/confirmdiff.py: Added.
+        * Scripts/webkitpy/steps/createbug.py: Added.
+        * Scripts/webkitpy/steps/editchangelog.py: Added.
+        * Scripts/webkitpy/steps/ensurebuildersaregreen.py: Added.
+        * Scripts/webkitpy/steps/ensurelocalcommitifneeded.py: Added.
+        * Scripts/webkitpy/steps/metastep.py: Added.
+        * Scripts/webkitpy/steps/obsoletepatches.py: Added.
+        * Scripts/webkitpy/steps/options.py: Added.
+        * Scripts/webkitpy/steps/postdiff.py: Added.
+        * Scripts/webkitpy/steps/preparechangelog.py: Added.
+        * Scripts/webkitpy/steps/preparechangelogforrevert.py: Added.
+        * Scripts/webkitpy/steps/promptforbugortitle.py: Added.
+        * Scripts/webkitpy/steps/revertrevision.py: Added.
+        * Scripts/webkitpy/steps/runtests.py: Added.
+        * Scripts/webkitpy/steps/steps_unittest.py: Added.
+        * Scripts/webkitpy/steps/update.py: Added.
+        * Scripts/webkitpy/steps/updatechangelogswithreview_unittests.py: Added.
+        * Scripts/webkitpy/steps/updatechangelogswithreviewer.py: Added.
+        * Scripts/webkitpy/stepsequence.py:
+
 2010-01-04  Daniel Bates  <dbates at webkit.org>
 
         Reviewed by Eric Seidel.
diff --git a/WebKitTools/Scripts/test-webkitpy b/WebKitTools/Scripts/test-webkitpy
index e919c81..d3f511b 100755
--- a/WebKitTools/Scripts/test-webkitpy
+++ b/WebKitTools/Scripts/test-webkitpy
@@ -32,7 +32,6 @@ import unittest
 
 from webkitpy.bugzilla_unittest import *
 from webkitpy.buildbot_unittest import *
-from webkitpy.buildsteps_unittest import *
 from webkitpy.changelogs_unittest import *
 from webkitpy.commands.download_unittest import *
 from webkitpy.commands.early_warning_system_unittest import *
@@ -46,6 +45,8 @@ from webkitpy.diff_parser_unittest import *
 from webkitpy.executive_unittest import *
 from webkitpy.multicommandtool_unittest import *
 from webkitpy.queueengine_unittest import *
+from webkitpy.steps.steps_unittest import *
+from webkitpy.steps.updatechangelogswithreview_unittests import *
 from webkitpy.style_unittest import *
 from webkitpy.text_style_unittest import *
 from webkitpy.webkit_logging_unittest import *
diff --git a/WebKitTools/Scripts/webkitpy/buildsteps.py b/WebKitTools/Scripts/webkitpy/buildsteps.py
deleted file mode 100644
index 2ad850e..0000000
--- a/WebKitTools/Scripts/webkitpy/buildsteps.py
+++ /dev/null
@@ -1,556 +0,0 @@
-# Copyright (C) 2009 Google Inc. 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 os
-import StringIO
-
-from optparse import make_option
-
-from webkitpy.comments import bug_comment_from_commit_text
-from webkitpy.executive import ScriptError
-from webkitpy.grammar import pluralize
-from webkitpy.webkit_logging import log, error
-from webkitpy.webkitport import WebKitPort
-from webkitpy.changelogs import ChangeLog
-
-# FIXME: Why do some of these have "Step" in their name but not all?
-__all__ = [
-    "ApplyPatchStep",
-    "ApplyPatchWithLocalCommitStep",
-    "BuildStep",
-    "CheckStyleStep",
-    "CleanWorkingDirectoryStep",
-    "CleanWorkingDirectoryWithLocalCommitsStep",
-    "CloseBugForLandDiffStep",
-    "CloseBugStep",
-    "ClosePatchStep",
-    "CommitStep",
-    "CompleteRollout",
-    "CreateBugStep",
-    "EnsureBuildersAreGreenStep",
-    "EnsureLocalCommitIfNeeded",
-    "ObsoletePatchesOnBugStep",
-    "PostDiffToBugStep",
-    "PrepareChangeLogForRevertStep",
-    "PrepareChangeLogStep",
-    "PromptForBugOrTitleStep",
-    "RevertRevisionStep",
-    "RunTestsStep",
-    "UpdateChangeLogsWithReviewerStep",
-    "UpdateStep",
-]
-
-class CommandOptions(object):
-    force_clean = make_option("--force-clean", action="store_true", dest="force_clean", default=False, help="Clean working directory before applying patches (removes local changes and commits)")
-    clean = make_option("--no-clean", action="store_false", dest="clean", default=True, help="Don't check if the working directory is clean before applying patches")
-    check_builders = make_option("--ignore-builders", action="store_false", dest="check_builders", default=True, help="Don't check to see if the build.webkit.org builders are green before landing.")
-    quiet = make_option("--quiet", action="store_true", dest="quiet", default=False, help="Produce less console output.")
-    non_interactive = make_option("--non-interactive", action="store_true", dest="non_interactive", default=False, help="Never prompt the user, fail as fast as possible.")
-    parent_command = make_option("--parent-command", action="store", dest="parent_command", default=None, help="(Internal) The command that spawned this instance.")
-    update = make_option("--no-update", action="store_false", dest="update", default=True, help="Don't update the working directory.")
-    local_commit = make_option("--local-commit", action="store_true", dest="local_commit", default=False, help="Make a local commit for each applied patch")
-    build = make_option("--no-build", action="store_false", dest="build", default=True, help="Commit without building first, implies --no-test.")
-    build_style = make_option("--build-style", action="store", dest="build_style", default=None, help="Whether to build debug, release, or both.")
-    test = make_option("--no-test", action="store_false", dest="test", default=True, help="Commit without running run-webkit-tests.")
-    close_bug = make_option("--no-close", action="store_false", dest="close_bug", default=True, help="Leave bug open after landing.")
-    port = make_option("--port", action="store", dest="port", default=None, help="Specify a port (e.g., mac, qt, gtk, ...).")
-    reviewer = make_option("-r", "--reviewer", action="store", type="string", dest="reviewer", help="Update ChangeLogs to say Reviewed by REVIEWER.")
-    complete_rollout = make_option("--complete-rollout", action="store_true", dest="complete_rollout", help="Commit the revert and re-open the original bug.")
-    obsolete_patches = make_option("--no-obsolete", action="store_false", dest="obsolete_patches", default=True, help="Do not obsolete old patches before posting this one.")
-    review = make_option("--no-review", action="store_false", dest="review", default=True, help="Do not mark the patch for review.")
-    request_commit = make_option("--request-commit", action="store_true", dest="request_commit", default=False, help="Mark the patch as needing auto-commit after review.")
-    description = make_option("-m", "--description", action="store", type="string", dest="description", help="Description string for the attachment (default: \"patch\")")
-    cc = make_option("--cc", action="store", type="string", dest="cc", help="Comma-separated list of email addresses to carbon-copy.")
-    component = make_option("--component", action="store", type="string", dest="component", help="Component for the new bug.")
-    confirm = make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Skip confirmation steps.")
-    email = make_option("--email", action="store", type="string", dest="email", help="Email address to use in ChangeLogs.")
-
-
-class AbstractStep(object):
-    def __init__(self, tool, options):
-        self._tool = tool
-        self._options = options
-        self._port = None
-
-    def _run_script(self, script_name, quiet=False, port=WebKitPort):
-        log("Running %s" % script_name)
-        # FIXME: This should use self.port()
-        self._tool.executive.run_and_throw_if_fail(port.script_path(script_name), quiet)
-
-    # FIXME: The port should live on the tool.
-    def port(self):
-        if self._port:
-            return self._port
-        self._port = WebKitPort.port(self._options.port)
-        return self._port
-
-    _well_known_keys = {
-        "diff" : lambda self: self._tool.scm().create_patch(),
-        "changelogs" : lambda self: self._tool.scm().modified_changelogs(),
-    }
-
-    def cached_lookup(self, state, key, promise=None):
-        if state.get(key):
-            return state[key]
-        if not promise:
-            promise = self._well_known_keys.get(key)
-        state[key] = promise(self)
-        return state[key]
-
-    @classmethod
-    def options(cls):
-        return []
-
-    def run(self, state):
-        raise NotImplementedError, "subclasses must implement"
-
-
-# FIXME: Unify with StepSequence?  I'm not sure yet which is the better design.
-class MetaStep(AbstractStep):
-    substeps = [] # Override in subclasses
-    def __init__(self, tool, options):
-        AbstractStep.__init__(self, tool, options)
-        self._step_instances = []
-        for step_class in self.substeps:
-            self._step_instances.append(step_class(tool, options))
-
-    @staticmethod
-    def _collect_options_from_steps(steps):
-        collected_options = []
-        for step in steps:
-            collected_options = collected_options + step.options()
-        return collected_options
-
-    @classmethod
-    def options(cls):
-        return cls._collect_options_from_steps(cls.substeps)
-
-    def run(self, state):
-        for step in self._step_instances:
-             step.run(state)
-
-
-class PromptForBugOrTitleStep(AbstractStep):
-    def run(self, state):
-        # No need to prompt if we alrady have the bug_id.
-        if state.get("bug_id"):
-            return
-        user_response = self._tool.user.prompt("Please enter a bug number or a title for a new bug:\n")
-        # If the user responds with a number, we assume it's bug number.
-        # Otherwise we assume it's a bug subject.
-        try:
-            state["bug_id"] = int(user_response)
-        except ValueError, TypeError:
-            state["bug_title"] = user_response
-            # FIXME: This is kind of a lame description.
-            state["bug_description"] = user_response
-
-
-class CreateBugStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.cc,
-            CommandOptions.component,
-        ]
-
-    def run(self, state):
-        # No need to create a bug if we already have one.
-        if state.get("bug_id"):
-            return
-        state["bug_id"] = self._tool.bugs.create_bug(state["bug_title"], state["bug_description"], component=self._options.component, cc=self._options.cc)
-
-
-class PrepareChangeLogStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.port,
-            CommandOptions.quiet,
-            CommandOptions.email,
-        ]
-
-    def run(self, state):
-        if self.cached_lookup(state, "changelogs"):
-            return
-        os.chdir(self._tool.scm().checkout_root)
-        args = [self.port().script_path("prepare-ChangeLog")]
-        if state["bug_id"]:
-            args.append("--bug=%s" % state["bug_id"])
-        if self._options.email:
-            args.append("--email=%s" % self._options.email)
-        try:
-            self._tool.executive.run_and_throw_if_fail(args, self._options.quiet)
-        except ScriptError, e:
-            error("Unable to prepare ChangeLogs.")
-        state["diff"] = None # We've changed the diff
-
-
-class EditChangeLogStep(AbstractStep):
-    def run(self, state):
-        self._tool.user.edit(self.cached_lookup(state, "changelogs"))
-
-
-class ObsoletePatchesOnBugStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.obsolete_patches,
-        ]
-
-    def run(self, state):
-        if not self._options.obsolete_patches:
-            return
-        bug_id = state["bug_id"]
-        patches = self._tool.bugs.fetch_patches_from_bug(bug_id)
-        if not patches:
-            return
-        log("Obsoleting %s on bug %s" % (pluralize("old patch", len(patches)), bug_id))
-        for patch in patches:
-            self._tool.bugs.obsolete_attachment(patch["id"])
-
-
-class ConfirmDiffStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.confirm,
-        ]
-
-    def run(self, state):
-        if not self._options.confirm:
-            return
-        diff = self.cached_lookup(state, "diff")
-        self._tool.user.page(diff)
-        if not self._tool.user.confirm("Was that diff correct?"):
-            error("User declined to continue.")
-
-
-class PostDiffToBugStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.description,
-            CommandOptions.review,
-            CommandOptions.request_commit,
-        ]
-
-    def run(self, state):
-        diff = self.cached_lookup(state, "diff")
-        diff_file = StringIO.StringIO(diff) # add_patch_to_bug expects a file-like object
-        description = self._options.description or "Patch"
-        self._tool.bugs.add_patch_to_bug(state["bug_id"], diff_file, description, mark_for_review=self._options.review, mark_for_commit_queue=self._options.request_commit)
-
-
-class PrepareChangeLogForRevertStep(AbstractStep):
-    def run(self, state):
-        # First, discard the ChangeLog changes from the rollout.
-        os.chdir(self._tool.scm().checkout_root)
-        changelog_paths = self._tool.scm().modified_changelogs()
-        self._tool.scm().revert_files(changelog_paths)
-
-        # Second, make new ChangeLog entries for this rollout.
-        # This could move to prepare-ChangeLog by adding a --revert= option.
-        self._run_script("prepare-ChangeLog")
-        for changelog_path in changelog_paths:
-            ChangeLog(changelog_path).update_for_revert(state["revision"])
-
-
-class CleanWorkingDirectoryStep(AbstractStep):
-    def __init__(self, tool, options, allow_local_commits=False):
-        AbstractStep.__init__(self, tool, options)
-        self._allow_local_commits = allow_local_commits
-
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.force_clean,
-            CommandOptions.clean,
-        ]
-
-    def run(self, state):
-        os.chdir(self._tool.scm().checkout_root)
-        if not self._allow_local_commits:
-            self._tool.scm().ensure_no_local_commits(self._options.force_clean)
-        if self._options.clean:
-            self._tool.scm().ensure_clean_working_directory(force_clean=self._options.force_clean)
-
-
-class CleanWorkingDirectoryWithLocalCommitsStep(CleanWorkingDirectoryStep):
-    def __init__(self, tool, options):
-        # FIXME: This a bit of a hack.  Consider doing this more cleanly.
-        CleanWorkingDirectoryStep.__init__(self, tool, options, allow_local_commits=True)
-
-
-class UpdateStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.update,
-            CommandOptions.port,
-        ]
-
-    def run(self, state):
-        if not self._options.update:
-            return
-        log("Updating working directory")
-        self._tool.executive.run_and_throw_if_fail(self.port().update_webkit_command(), quiet=True)
-
-
-class ApplyPatchStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.non_interactive,
-        ]
-
-    def run(self, state):
-        log("Processing patch %s from bug %s." % (state["patch"]["id"], state["patch"]["bug_id"]))
-        self._tool.scm().apply_patch(state["patch"], force=self._options.non_interactive)
-
-
-class RevertRevisionStep(AbstractStep):
-    def run(self, state):
-        self._tool.scm().apply_reverse_diff(state["revision"])
-
-
-class ApplyPatchWithLocalCommitStep(ApplyPatchStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.local_commit,
-        ] + ApplyPatchStep.options()
-    
-    def run(self, state):
-        ApplyPatchStep.run(self, state)
-        if self._options.local_commit:
-            commit_message = self._tool.scm().commit_message_for_this_commit()
-            self._tool.scm().commit_locally_with_message(commit_message.message() or state["patch"]["name"])
-
-
-class EnsureBuildersAreGreenStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.check_builders,
-        ]
-
-    def run(self, state):
-        if not self._options.check_builders:
-            return
-        red_builders_names = self._tool.buildbot.red_core_builders_names()
-        if not red_builders_names:
-            return
-        red_builders_names = map(lambda name: "\"%s\"" % name, red_builders_names) # Add quotes around the names.
-        error("Builders [%s] are red, please do not commit.\nSee http://%s.\nPass --ignore-builders to bypass this check." % (", ".join(red_builders_names), self._tool.buildbot.buildbot_host))
-
-
-class EnsureLocalCommitIfNeeded(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.local_commit,
-        ]
-
-    def run(self, state):
-        if self._options.local_commit and not self._tool.scm().supports_local_commits():
-            error("--local-commit passed, but %s does not support local commits" % self._tool.scm.display_name())
-
-
-class UpdateChangeLogsWithReviewerStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.reviewer,
-        ]
-
-    def _guess_reviewer_from_bug(self, bug_id):
-        patches = self._tool.bugs.fetch_reviewed_patches_from_bug(bug_id)
-        if len(patches) != 1:
-            log("%s on bug %s, cannot infer reviewer." % (pluralize("reviewed patch", len(patches)), bug_id))
-            return None
-        patch = patches[0]
-        reviewer = patch["reviewer"]
-        log("Guessing \"%s\" as reviewer from attachment %s on bug %s." % (reviewer, patch["id"], bug_id))
-        return reviewer
-
-    def run(self, state):
-        bug_id = state["patch"]["bug_id"]
-        reviewer = self._options.reviewer
-        if not reviewer:
-            if not bug_id:
-                log("No bug id provided and --reviewer= not provided.  Not updating ChangeLogs with reviewer.")
-                return
-            reviewer = self._guess_reviewer_from_bug(bug_id)
-
-        if not reviewer:
-            log("Failed to guess reviewer from bug %s and --reviewer= not provided.  Not updating ChangeLogs with reviewer." % bug_id)
-            return
-
-        os.chdir(self._tool.scm().checkout_root)
-        for changelog_path in self._tool.scm().modified_changelogs():
-            ChangeLog(changelog_path).set_reviewer(reviewer)
-
-
-class BuildStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.build,
-            CommandOptions.quiet,
-            CommandOptions.build_style,
-        ]
-
-    def build(self, build_style):
-        self._tool.executive.run_and_throw_if_fail(self.port().build_webkit_command(build_style=build_style), self._options.quiet)
-
-    def run(self, state):
-        if not self._options.build:
-            return
-        log("Building WebKit")
-        if self._options.build_style == "both":
-            self.build("debug")
-            self.build("release")
-        else:
-            self.build(self._options.build_style)
-
-
-class CheckStyleStep(AbstractStep):
-    def run(self, state):
-        self._run_script("check-webkit-style")
-
-
-class RunTestsStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.build,
-            CommandOptions.test,
-            CommandOptions.non_interactive,
-            CommandOptions.quiet,
-            CommandOptions.port,
-        ]
-
-    def run(self, state):
-        if not self._options.build:
-            return
-        if not self._options.test:
-            return
-        args = self.port().run_webkit_tests_command()
-        if self._options.non_interactive:
-            args.append("--no-launch-safari")
-            args.append("--exit-after-n-failures=1")
-        if self._options.quiet:
-            args.append("--quiet")
-        self._tool.executive.run_and_throw_if_fail(args)
-
-
-class CommitStep(AbstractStep):
-    def run(self, state):
-        commit_message = self._tool.scm().commit_message_for_this_commit()
-        state["commit_text"] =  self._tool.scm().commit_with_message(commit_message.message())
-
-
-class ClosePatchStep(AbstractStep):
-    def run(self, state):
-        comment_text = bug_comment_from_commit_text(self._tool.scm(), state["commit_text"])
-        self._tool.bugs.clear_attachment_flags(state["patch"]["id"], comment_text)
-
-
-class CloseBugStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.close_bug,
-        ]
-
-    def run(self, state):
-        if not self._options.close_bug:
-            return
-        # Check to make sure there are no r? or r+ patches on the bug before closing.
-        # Assume that r- patches are just previous patches someone forgot to obsolete.
-        patches = self._tool.bugs.fetch_patches_from_bug(state["patch"]["bug_id"])
-        for patch in patches:
-            review_flag = patch.get("review")
-            if review_flag == "?" or review_flag == "+":
-                log("Not closing bug %s as attachment %s has review=%s.  Assuming there are more patches to land from this bug." % (patch["bug_id"], patch["id"], review_flag))
-                return
-        self._tool.bugs.close_bug_as_fixed(state["patch"]["bug_id"], "All reviewed patches have been landed.  Closing bug.")
-
-
-class CloseBugForLandDiffStep(AbstractStep):
-    @classmethod
-    def options(cls):
-        return [
-            CommandOptions.close_bug,
-        ]
-
-    def run(self, state):
-        comment_text = bug_comment_from_commit_text(self._tool.scm(), state["commit_text"])
-        bug_id = state["patch"]["bug_id"]
-        if bug_id:
-            log("Updating bug %s" % bug_id)
-            if self._options.close_bug:
-                self._tool.bugs.close_bug_as_fixed(bug_id, comment_text)
-            else:
-                # FIXME: We should a smart way to figure out if the patch is attached
-                # to the bug, and if so obsolete it.
-                self._tool.bugs.post_comment_to_bug(bug_id, comment_text)
-        else:
-            log(comment_text)
-            log("No bug id provided.")
-
-
-class CompleteRollout(MetaStep):
-    substeps = [
-        BuildStep,
-        CommitStep,
-    ]
-
-    @classmethod
-    def options(cls):
-        collected_options = cls._collect_options_from_steps(cls.substeps)
-        collected_options.append(CommandOptions.complete_rollout)
-        return collected_options
-
-    def run(self, state):
-        bug_id = state["bug_id"]
-        # FIXME: Fully automated rollout is not 100% idiot-proof yet, so for now just log with instructions on how to complete the rollout.
-        # Once we trust rollout we will remove this option.
-        if not self._options.complete_rollout:
-            log("\nNOTE: Rollout support is experimental.\nPlease verify the rollout diff and use \"bugzilla-tool land-diff %s\" to commit the rollout." % bug_id)
-            return
-
-        MetaStep.run(self, state)
-
-        if not bug_id:
-            log(state["commit_text"])
-            log("No bugs were updated or re-opened to reflect this rollout.")
-            return
-        # FIXME: I'm not sure state["commit_text"] is quite right here.
-        self._tool.bugs.reopen_bug(bug_id, state["commit_text"])
diff --git a/WebKitTools/Scripts/webkitpy/buildsteps_unittest.py b/WebKitTools/Scripts/webkitpy/buildsteps_unittest.py
deleted file mode 100644
index 158f4c0..0000000
--- a/WebKitTools/Scripts/webkitpy/buildsteps_unittest.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright (C) 2009 Google Inc. 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 unittest
-
-from webkitpy.buildsteps import UpdateChangeLogsWithReviewerStep, UpdateStep, PromptForBugOrTitleStep
-from webkitpy.mock_bugzillatool import MockBugzillaTool
-from webkitpy.outputcapture import OutputCapture
-from webkitpy.mock import Mock
-
-
-class UpdateChangeLogsWithReviewerStepTest(unittest.TestCase):
-    def test_guess_reviewer_from_bug(self):
-        capture = OutputCapture()
-        step = UpdateChangeLogsWithReviewerStep(MockBugzillaTool(), [])
-        expected_stderr = "0 reviewed patches on bug 75, cannot infer reviewer.\n"
-        capture.assert_outputs(self, step._guess_reviewer_from_bug, [75], expected_stderr=expected_stderr)
-
-
-class StepsTest(unittest.TestCase):
-    def _run_step(self, step, tool=None, options=None, state=None):
-        if not tool:
-            tool = MockBugzillaTool()
-        if not options:
-            options = Mock()
-        if not state:
-            state = {}
-        step(tool, options).run(state)
-
-    def test_update_step(self):
-        options = Mock()
-        options.update = True
-        self._run_step(UpdateStep, options)
-
-    def test_prompt_for_bug_or_title_step(self):
-        tool = MockBugzillaTool()
-        tool.user.prompt = lambda message: 42
-        self._run_step(PromptForBugOrTitleStep, tool=tool)
diff --git a/WebKitTools/Scripts/webkitpy/commands/download.py b/WebKitTools/Scripts/webkitpy/commands/download.py
index 55bdbf4..3e1bb4e 100644
--- a/WebKitTools/Scripts/webkitpy/commands/download.py
+++ b/WebKitTools/Scripts/webkitpy/commands/download.py
@@ -32,9 +32,10 @@ import os
 
 from optparse import make_option
 
+import webkitpy.steps as steps
+
 from webkitpy.bugzilla import parse_bug_id
 # We could instead use from modules import buildsteps and then prefix every buildstep with "buildsteps."
-from webkitpy.buildsteps import *
 from webkitpy.changelogs import ChangeLog
 from webkitpy.comments import bug_comment_from_commit_text
 from webkitpy.executive import ScriptError
@@ -62,9 +63,9 @@ class Build(AbstractSequencedCommmand):
     name = "build"
     help_text = "Update working copy and build"
     steps = [
-        CleanWorkingDirectoryStep,
-        UpdateStep,
-        BuildStep,
+        steps.CleanWorkingDirectory,
+        steps.Update,
+        steps.Build,
     ]
 
 
@@ -72,10 +73,10 @@ class BuildAndTest(AbstractSequencedCommmand):
     name = "build-and-test"
     help_text = "Update working copy, build, and run the tests"
     steps = [
-        CleanWorkingDirectoryStep,
-        UpdateStep,
-        BuildStep,
-        RunTestsStep,
+        steps.CleanWorkingDirectory,
+        steps.Update,
+        steps.Build,
+        steps.RunTests,
     ]
 
 
@@ -85,13 +86,13 @@ class LandDiff(AbstractSequencedCommmand):
     argument_names = "[BUGID]"
     show_in_main_help = True
     steps = [
-        EnsureBuildersAreGreenStep,
-        UpdateChangeLogsWithReviewerStep,
-        EnsureBuildersAreGreenStep,
-        BuildStep,
-        RunTestsStep,
-        CommitStep,
-        CloseBugForLandDiffStep,
+        steps.EnsureBuildersAreGreen,
+        steps.UpdateChangeLogsWithReviewer,
+        steps.EnsureBuildersAreGreen,
+        steps.Build,
+        steps.RunTests,
+        steps.Commit,
+        steps.CloseBugForLandDiff,
     ]
 
     def _prepare_state(self, options, args, tool):
@@ -169,10 +170,10 @@ class CheckStyle(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
     help_text = "Run check-webkit-style on the specified attachments"
     argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
     main_steps = [
-        CleanWorkingDirectoryStep,
-        UpdateStep,
-        ApplyPatchStep,
-        CheckStyleStep,
+        steps.CleanWorkingDirectory,
+        steps.Update,
+        steps.ApplyPatch,
+        steps.CheckStyle,
     ]
 
 
@@ -181,21 +182,21 @@ class BuildAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
     help_text = "Apply and build patches from bugzilla"
     argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
     main_steps = [
-        CleanWorkingDirectoryStep,
-        UpdateStep,
-        ApplyPatchStep,
-        BuildStep,
+        steps.CleanWorkingDirectory,
+        steps.Update,
+        steps.ApplyPatch,
+        steps.Build,
     ]
 
 
 class AbstractPatchApplyingCommand(AbstractPatchSequencingCommand):
     prepare_steps = [
-        EnsureLocalCommitIfNeeded,
-        CleanWorkingDirectoryWithLocalCommitsStep,
-        UpdateStep,
+        steps.EnsureLocalCommitIfNeeded,
+        steps.CleanWorkingDirectoryWithLocalCommits,
+        steps.Update,
     ]
     main_steps = [
-        ApplyPatchWithLocalCommitStep,
+        steps.ApplyPatchWithLocalCommit,
     ]
 
 
@@ -215,18 +216,18 @@ class ApplyPatches(AbstractPatchApplyingCommand, ProcessBugsMixin):
 
 class AbstractPatchLandingCommand(AbstractPatchSequencingCommand):
     prepare_steps = [
-        EnsureBuildersAreGreenStep,
+        steps.EnsureBuildersAreGreen,
     ]
     main_steps = [
-        CleanWorkingDirectoryStep,
-        UpdateStep,
-        ApplyPatchStep,
-        EnsureBuildersAreGreenStep,
-        BuildStep,
-        RunTestsStep,
-        CommitStep,
-        ClosePatchStep,
-        CloseBugStep,
+        steps.CleanWorkingDirectory,
+        steps.Update,
+        steps.ApplyPatch,
+        steps.EnsureBuildersAreGreen,
+        steps.Build,
+        steps.RunTests,
+        steps.Commit,
+        steps.ClosePatch,
+        steps.CloseBug,
     ]
 
 
@@ -250,11 +251,11 @@ class Rollout(AbstractSequencedCommmand):
     help_text = "Revert the given revision in the working copy and optionally commit the revert and re-open the original bug"
     argument_names = "REVISION [BUGID]"
     steps = [
-        CleanWorkingDirectoryStep,
-        UpdateStep,
-        RevertRevisionStep,
-        PrepareChangeLogForRevertStep,
-        CompleteRollout,
+        steps.CleanWorkingDirectory,
+        steps.Update,
+        steps.RevertRevision,
+        steps.PrepareChangeLogForRevert,
+        steps.CompleteRollout,
     ]
 
     @staticmethod
diff --git a/WebKitTools/Scripts/webkitpy/commands/upload.py b/WebKitTools/Scripts/webkitpy/commands/upload.py
index 154fdff..165f2ef 100644
--- a/WebKitTools/Scripts/webkitpy/commands/upload.py
+++ b/WebKitTools/Scripts/webkitpy/commands/upload.py
@@ -35,8 +35,9 @@ import sys
 
 from optparse import make_option
 
+import webkitpy.steps as steps
+
 from webkitpy.bugzilla import parse_bug_id
-from webkitpy.buildsteps import PrepareChangeLogStep, EditChangeLogStep, ConfirmDiffStep, CommandOptions, ObsoletePatchesOnBugStep, PostDiffToBugStep, PromptForBugOrTitleStep, CreateBugStep
 from webkitpy.commands.download import AbstractSequencedCommmand
 from webkitpy.comments import bug_comment_from_svn_revision
 from webkitpy.committers import CommitterList
@@ -91,7 +92,7 @@ class ObsoleteAttachments(AbstractSequencedCommmand):
     help_text = "Mark all attachments on a bug as obsolete"
     argument_names = "BUGID"
     steps = [
-        ObsoletePatchesOnBugStep,
+        steps.ObsoletePatches,
     ]
 
     def _prepare_state(self, options, args, tool):
@@ -114,9 +115,9 @@ class PostDiff(AbstractPatchUploadingCommand):
     argument_names = "[BUGID]"
     show_in_main_help = True
     steps = [
-        ConfirmDiffStep,
-        ObsoletePatchesOnBugStep,
-        PostDiffToBugStep,
+        steps.ConfirmDiff,
+        steps.ObsoletePatches,
+        steps.PostDiff,
     ]
 
     def _prepare_state(self, options, args, tool):
@@ -132,9 +133,9 @@ class PrepareDiff(AbstractSequencedCommmand):
     help_text = "Creates a bug (or prompts for an existing bug) and prepares the ChangeLogs"
     argument_names = "[BUGID]"
     steps = [
-        PromptForBugOrTitleStep,
-        CreateBugStep,
-        PrepareChangeLogStep,
+        steps.PromptForBugOrTitle,
+        steps.CreateBug,
+        steps.PrepareChangeLog,
     ]
 
     def _prepare_state(self, options, args, tool):
@@ -147,13 +148,13 @@ class SubmitPatch(AbstractPatchUploadingCommand):
     help_text = "Automates the process of uploading a patch for review"
     argument_names = "[BUGID]"
     steps = [
-        PromptForBugOrTitleStep,
-        CreateBugStep,
-        PrepareChangeLogStep,
-        EditChangeLogStep,
-        ConfirmDiffStep,
-        ObsoletePatchesOnBugStep,
-        PostDiffToBugStep,
+        steps.PromptForBugOrTitle,
+        steps.CreateBug,
+        steps.PrepareChangeLog,
+        steps.EditChangeLog,
+        steps.ConfirmDiff,
+        steps.ObsoletePatches,
+        steps.PostDiff,
     ]
 
     def _prepare_state(self, options, args, tool):
@@ -166,7 +167,7 @@ class EditChangeLog(AbstractSequencedCommmand):
     name = "edit-changelog"
     help_text = "Opens modified ChangeLogs in $EDITOR"
     steps = [
-        EditChangeLogStep,
+        steps.EditChangeLog,
     ]
 
 
@@ -181,9 +182,9 @@ class PostCommits(AbstractDeclarativeCommmand):
             make_option("-b", "--bug-id", action="store", type="string", dest="bug_id", help="Specify bug id if no URL is provided in the commit log."),
             make_option("--add-log-as-comment", action="store_true", dest="add_log_as_comment", default=False, help="Add commit log message as a comment when uploading the patch."),
             make_option("-m", "--description", action="store", type="string", dest="description", help="Description string for the attachment (default: description from commit message)"),
-            CommandOptions.obsolete_patches,
-            CommandOptions.review,
-            CommandOptions.request_commit,
+            steps.Options.obsolete_patches,
+            steps.Options.review,
+            steps.Options.request_commit,
         ]
         AbstractDeclarativeCommmand.__init__(self, options=options, requires_local_commits=True)
 
@@ -216,7 +217,7 @@ class PostCommits(AbstractDeclarativeCommmand):
 
             if options.obsolete_patches and bug_id not in have_obsoleted_patches:
                 state = { "bug_id": bug_id }
-                ObsoletePatchesOnBugStep(tool, options).run(state)
+                steps.ObsoletePatches(tool, options).run(state)
                 have_obsoleted_patches.add(bug_id)
 
             diff_file = self._diff_file_for_commit(tool, commit_id)
@@ -323,8 +324,8 @@ class CreateBug(AbstractDeclarativeCommmand):
 
     def __init__(self):
         options = [
-            CommandOptions.cc,
-            CommandOptions.component,
+            steps.Options.cc,
+            steps.Options.component,
             make_option("--no-prompt", action="store_false", dest="prompt", default=True, help="Do not prompt for bug title and comment; use commit log instead."),
             make_option("--no-review", action="store_false", dest="review", default=True, help="Do not mark the patch for review."),
             make_option("--request-commit", action="store_true", dest="request_commit", default=False, help="Mark the patch as needing auto-commit after review."),
diff --git a/WebKitTools/Scripts/webkitpy/steps/__init__.py b/WebKitTools/Scripts/webkitpy/steps/__init__.py
new file mode 100644
index 0000000..2ca0537
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/__init__.py
@@ -0,0 +1,55 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+# FIXME: Is this the right way to do this?
+from webkitpy.steps.applypatch import ApplyPatch
+from webkitpy.steps.applypatchwithlocalcommit import ApplyPatchWithLocalCommit
+from webkitpy.steps.build import Build
+from webkitpy.steps.checkstyle import CheckStyle
+from webkitpy.steps.cleanworkingdirectory import CleanWorkingDirectory
+from webkitpy.steps.cleanworkingdirectorywithlocalcommits import CleanWorkingDirectoryWithLocalCommits
+from webkitpy.steps.closebug import CloseBug
+from webkitpy.steps.closebugforlanddiff import CloseBugForLandDiff
+from webkitpy.steps.closepatch import ClosePatch
+from webkitpy.steps.commit import Commit
+from webkitpy.steps.completerollout import CompleteRollout
+from webkitpy.steps.confirmdiff import ConfirmDiff
+from webkitpy.steps.createbug import CreateBug
+from webkitpy.steps.editchangelog import EditChangeLog
+from webkitpy.steps.ensurebuildersaregreen import EnsureBuildersAreGreen
+from webkitpy.steps.ensurelocalcommitifneeded import EnsureLocalCommitIfNeeded
+from webkitpy.steps.obsoletepatches import ObsoletePatches
+from webkitpy.steps.options import Options
+from webkitpy.steps.postdiff import PostDiff
+from webkitpy.steps.preparechangelogforrevert import PrepareChangeLogForRevert
+from webkitpy.steps.preparechangelog import PrepareChangeLog
+from webkitpy.steps.promptforbugortitle import PromptForBugOrTitle
+from webkitpy.steps.revertrevision import RevertRevision
+from webkitpy.steps.runtests import RunTests
+from webkitpy.steps.updatechangelogswithreviewer import UpdateChangeLogsWithReviewer
+from webkitpy.steps.update import Update
diff --git a/WebKitTools/Scripts/webkitpy/steps/abstractstep.py b/WebKitTools/Scripts/webkitpy/steps/abstractstep.py
new file mode 100644
index 0000000..639cf55
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/abstractstep.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.webkit_logging import log
+from webkitpy.webkitport import WebKitPort
+
+
+class AbstractStep(object):
+    def __init__(self, tool, options):
+        self._tool = tool
+        self._options = options
+        self._port = None
+
+    def _run_script(self, script_name, quiet=False, port=WebKitPort):
+        log("Running %s" % script_name)
+        # FIXME: This should use self.port()
+        self._tool.executive.run_and_throw_if_fail(port.script_path(script_name), quiet)
+
+    # FIXME: The port should live on the tool.
+    def port(self):
+        if self._port:
+            return self._port
+        self._port = WebKitPort.port(self._options.port)
+        return self._port
+
+    _well_known_keys = {
+        "diff" : lambda self: self._tool.scm().create_patch(),
+        "changelogs" : lambda self: self._tool.scm().modified_changelogs(),
+    }
+
+    def cached_lookup(self, state, key, promise=None):
+        if state.get(key):
+            return state[key]
+        if not promise:
+            promise = self._well_known_keys.get(key)
+        state[key] = promise(self)
+        return state[key]
+
+    @classmethod
+    def options(cls):
+        return []
+
+    def run(self, state):
+        raise NotImplementedError, "subclasses must implement"
diff --git a/WebKitTools/Scripts/webkitpy/steps/applypatch.py b/WebKitTools/Scripts/webkitpy/steps/applypatch.py
new file mode 100644
index 0000000..e1a1551
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/applypatch.py
@@ -0,0 +1,42 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import log
+
+class ApplyPatch(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.non_interactive,
+        ]
+
+    def run(self, state):
+        log("Processing patch %s from bug %s." % (state["patch"]["id"], state["patch"]["bug_id"]))
+        self._tool.scm().apply_patch(state["patch"], force=self._options.non_interactive)
diff --git a/WebKitTools/Scripts/webkitpy/steps/applypatchwithlocalcommit.py b/WebKitTools/Scripts/webkitpy/steps/applypatchwithlocalcommit.py
new file mode 100644
index 0000000..01fab1a
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/applypatchwithlocalcommit.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.applypatch import ApplyPatch
+from webkitpy.steps.options import Options
+
+class ApplyPatchWithLocalCommit(ApplyPatch):
+    @classmethod
+    def options(cls):
+        return [
+            Options.local_commit,
+        ] + ApplyPatch.options()
+
+    def run(self, state):
+        ApplyPatch.run(self, state)
+        if self._options.local_commit:
+            commit_message = self._tool.scm().commit_message_for_this_commit()
+            self._tool.scm().commit_locally_with_message(commit_message.message() or state["patch"]["name"])
diff --git a/WebKitTools/Scripts/webkitpy/steps/build.py b/WebKitTools/Scripts/webkitpy/steps/build.py
new file mode 100644
index 0000000..1823cff
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/build.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import log
+
+
+class Build(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.build,
+            Options.quiet,
+            Options.build_style,
+        ]
+
+    def build(self, build_style):
+        self._tool.executive.run_and_throw_if_fail(self.port().build_webkit_command(build_style=build_style), self._options.quiet)
+
+    def run(self, state):
+        if not self._options.build:
+            return
+        log("Building WebKit")
+        if self._options.build_style == "both":
+            self.build("debug")
+            self.build("release")
+        else:
+            self.build(self._options.build_style)
diff --git a/WebKitTools/Scripts/webkitpy/steps/checkstyle.py b/WebKitTools/Scripts/webkitpy/steps/checkstyle.py
new file mode 100644
index 0000000..d235ac0
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/checkstyle.py
@@ -0,0 +1,33 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+
+class CheckStyle(AbstractStep):
+    def run(self, state):
+        self._run_script("check-webkit-style")
diff --git a/WebKitTools/Scripts/webkitpy/steps/cleanworkingdirectory.py b/WebKitTools/Scripts/webkitpy/steps/cleanworkingdirectory.py
new file mode 100644
index 0000000..88e38f5
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/cleanworkingdirectory.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010 Google Inc. 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 os
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+
+
+class CleanWorkingDirectory(AbstractStep):
+    def __init__(self, tool, options, allow_local_commits=False):
+        AbstractStep.__init__(self, tool, options)
+        self._allow_local_commits = allow_local_commits
+
+    @classmethod
+    def options(cls):
+        return [
+            Options.force_clean,
+            Options.clean,
+        ]
+
+    def run(self, state):
+        os.chdir(self._tool.scm().checkout_root)
+        if not self._allow_local_commits:
+            self._tool.scm().ensure_no_local_commits(self._options.force_clean)
+        if self._options.clean:
+            self._tool.scm().ensure_clean_working_directory(force_clean=self._options.force_clean)
diff --git a/WebKitTools/Scripts/webkitpy/steps/cleanworkingdirectorywithlocalcommits.py b/WebKitTools/Scripts/webkitpy/steps/cleanworkingdirectorywithlocalcommits.py
new file mode 100644
index 0000000..cabeba2
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/cleanworkingdirectorywithlocalcommits.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.cleanworkingdirectory import CleanWorkingDirectory
+
+class CleanWorkingDirectoryWithLocalCommits(CleanWorkingDirectory):
+    def __init__(self, tool, options):
+        # FIXME: This a bit of a hack.  Consider doing this more cleanly.
+        CleanWorkingDirectory.__init__(self, tool, options, allow_local_commits=True)
diff --git a/WebKitTools/Scripts/webkitpy/steps/closebug.py b/WebKitTools/Scripts/webkitpy/steps/closebug.py
new file mode 100644
index 0000000..aa4b213
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/closebug.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import log
+
+
+class CloseBug(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.close_bug,
+        ]
+
+    def run(self, state):
+        if not self._options.close_bug:
+            return
+        # Check to make sure there are no r? or r+ patches on the bug before closing.
+        # Assume that r- patches are just previous patches someone forgot to obsolete.
+        patches = self._tool.bugs.fetch_patches_from_bug(state["patch"]["bug_id"])
+        for patch in patches:
+            review_flag = patch.get("review")
+            if review_flag == "?" or review_flag == "+":
+                log("Not closing bug %s as attachment %s has review=%s.  Assuming there are more patches to land from this bug." % (patch["bug_id"], patch["id"], review_flag))
+                return
+        self._tool.bugs.close_bug_as_fixed(state["patch"]["bug_id"], "All reviewed patches have been landed.  Closing bug.")
diff --git a/WebKitTools/Scripts/webkitpy/steps/closebugforlanddiff.py b/WebKitTools/Scripts/webkitpy/steps/closebugforlanddiff.py
new file mode 100644
index 0000000..0d52fe2
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/closebugforlanddiff.py
@@ -0,0 +1,55 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.comments import bug_comment_from_commit_text
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import log
+
+
+class CloseBugForLandDiff(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.close_bug,
+        ]
+
+    def run(self, state):
+        comment_text = bug_comment_from_commit_text(self._tool.scm(), state["commit_text"])
+        bug_id = state["patch"]["bug_id"]
+        if bug_id:
+            log("Updating bug %s" % bug_id)
+            if self._options.close_bug:
+                self._tool.bugs.close_bug_as_fixed(bug_id, comment_text)
+            else:
+                # FIXME: We should a smart way to figure out if the patch is attached
+                # to the bug, and if so obsolete it.
+                self._tool.bugs.post_comment_to_bug(bug_id, comment_text)
+        else:
+            log(comment_text)
+            log("No bug id provided.")
diff --git a/WebKitTools/Scripts/webkitpy/steps/closepatch.py b/WebKitTools/Scripts/webkitpy/steps/closepatch.py
new file mode 100644
index 0000000..de1a563
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/closepatch.py
@@ -0,0 +1,36 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.comments import bug_comment_from_commit_text
+from webkitpy.steps.abstractstep import AbstractStep
+
+
+class ClosePatch(AbstractStep):
+    def run(self, state):
+        comment_text = bug_comment_from_commit_text(self._tool.scm(), state["commit_text"])
+        self._tool.bugs.clear_attachment_flags(state["patch"]["id"], comment_text)
diff --git a/WebKitTools/Scripts/webkitpy/steps/commit.py b/WebKitTools/Scripts/webkitpy/steps/commit.py
new file mode 100644
index 0000000..dd1fed7
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/commit.py
@@ -0,0 +1,35 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+
+
+class Commit(AbstractStep):
+    def run(self, state):
+        commit_message = self._tool.scm().commit_message_for_this_commit()
+        state["commit_text"] =  self._tool.scm().commit_with_message(commit_message.message())
diff --git a/WebKitTools/Scripts/webkitpy/steps/completerollout.py b/WebKitTools/Scripts/webkitpy/steps/completerollout.py
new file mode 100644
index 0000000..5b83d13
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/completerollout.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.build import Build
+from webkitpy.steps.commit import Commit
+from webkitpy.steps.metastep import MetaStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import log
+
+
+class CompleteRollout(MetaStep):
+    substeps = [
+        Build,
+        Commit,
+    ]
+
+    @classmethod
+    def options(cls):
+        collected_options = cls._collect_options_from_steps(cls.substeps)
+        collected_options.append(Options.complete_rollout)
+        return collected_options
+
+    def run(self, state):
+        bug_id = state["bug_id"]
+        # FIXME: Fully automated rollout is not 100% idiot-proof yet, so for now just log with instructions on how to complete the rollout.
+        # Once we trust rollout we will remove this option.
+        if not self._options.complete_rollout:
+            log("\nNOTE: Rollout support is experimental.\nPlease verify the rollout diff and use \"bugzilla-tool land-diff %s\" to commit the rollout." % bug_id)
+            return
+
+        MetaStep.run(self, state)
+
+        if not bug_id:
+            log(state["commit_text"])
+            log("No bugs were updated or re-opened to reflect this rollout.")
+            return
+        # FIXME: I'm not sure state["commit_text"] is quite right here.
+        self._tool.bugs.reopen_bug(bug_id, state["commit_text"])
diff --git a/WebKitTools/Scripts/webkitpy/steps/confirmdiff.py b/WebKitTools/Scripts/webkitpy/steps/confirmdiff.py
new file mode 100644
index 0000000..1129902
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/confirmdiff.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import error
+
+
+class ConfirmDiff(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.confirm,
+        ]
+
+    def run(self, state):
+        if not self._options.confirm:
+            return
+        diff = self.cached_lookup(state, "diff")
+        self._tool.user.page(diff)
+        if not self._tool.user.confirm("Was that diff correct?"):
+            error("User declined to continue.")
diff --git a/WebKitTools/Scripts/webkitpy/steps/createbug.py b/WebKitTools/Scripts/webkitpy/steps/createbug.py
new file mode 100644
index 0000000..75bf17f
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/createbug.py
@@ -0,0 +1,45 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+
+
+class CreateBug(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.cc,
+            Options.component,
+        ]
+
+    def run(self, state):
+        # No need to create a bug if we already have one.
+        if state.get("bug_id"):
+            return
+        state["bug_id"] = self._tool.bugs.create_bug(state["bug_title"], state["bug_description"], component=self._options.component, cc=self._options.cc)
diff --git a/WebKitTools/Scripts/webkitpy/steps/editchangelog.py b/WebKitTools/Scripts/webkitpy/steps/editchangelog.py
new file mode 100644
index 0000000..bdf7d67
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/editchangelog.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+
+
+class EditChangeLog(AbstractStep):
+    def run(self, state):
+        self._tool.user.edit(self.cached_lookup(state, "changelogs"))
diff --git a/WebKitTools/Scripts/webkitpy/steps/ensurebuildersaregreen.py b/WebKitTools/Scripts/webkitpy/steps/ensurebuildersaregreen.py
new file mode 100644
index 0000000..96f265a
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/ensurebuildersaregreen.py
@@ -0,0 +1,48 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import error
+
+
+class EnsureBuildersAreGreen(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.check_builders,
+        ]
+
+    def run(self, state):
+        if not self._options.check_builders:
+            return
+        red_builders_names = self._tool.buildbot.red_core_builders_names()
+        if not red_builders_names:
+            return
+        red_builders_names = map(lambda name: "\"%s\"" % name, red_builders_names) # Add quotes around the names.
+        error("Builders [%s] are red, please do not commit.\nSee http://%s.\nPass --ignore-builders to bypass this check." % (", ".join(red_builders_names), self._tool.buildbot.buildbot_host))
diff --git a/WebKitTools/Scripts/webkitpy/steps/ensurelocalcommitifneeded.py b/WebKitTools/Scripts/webkitpy/steps/ensurelocalcommitifneeded.py
new file mode 100644
index 0000000..cecf891
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/ensurelocalcommitifneeded.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import error
+
+
+class EnsureLocalCommitIfNeeded(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.local_commit,
+        ]
+
+    def run(self, state):
+        if self._options.local_commit and not self._tool.scm().supports_local_commits():
+            error("--local-commit passed, but %s does not support local commits" % self._tool.scm.display_name())
diff --git a/WebKitTools/Scripts/webkitpy/steps/metastep.py b/WebKitTools/Scripts/webkitpy/steps/metastep.py
new file mode 100644
index 0000000..9f368de
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/metastep.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+
+
+# FIXME: Unify with StepSequence?  I'm not sure yet which is the better design.
+class MetaStep(AbstractStep):
+    substeps = [] # Override in subclasses
+    def __init__(self, tool, options):
+        AbstractStep.__init__(self, tool, options)
+        self._step_instances = []
+        for step_class in self.substeps:
+            self._step_instances.append(step_class(tool, options))
+
+    @staticmethod
+    def _collect_options_from_steps(steps):
+        collected_options = []
+        for step in steps:
+            collected_options = collected_options + step.options()
+        return collected_options
+
+    @classmethod
+    def options(cls):
+        return cls._collect_options_from_steps(cls.substeps)
+
+    def run(self, state):
+        for step in self._step_instances:
+             step.run(state)
diff --git a/WebKitTools/Scripts/webkitpy/steps/obsoletepatches.py b/WebKitTools/Scripts/webkitpy/steps/obsoletepatches.py
new file mode 100644
index 0000000..d9c6431
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/obsoletepatches.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.grammar import pluralize
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import log
+
+
+class ObsoletePatches(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.obsolete_patches,
+        ]
+
+    def run(self, state):
+        if not self._options.obsolete_patches:
+            return
+        bug_id = state["bug_id"]
+        patches = self._tool.bugs.fetch_patches_from_bug(bug_id)
+        if not patches:
+            return
+        log("Obsoleting %s on bug %s" % (pluralize("old patch", len(patches)), bug_id))
+        for patch in patches:
+            self._tool.bugs.obsolete_attachment(patch["id"])
diff --git a/WebKitTools/Scripts/webkitpy/steps/options.py b/WebKitTools/Scripts/webkitpy/steps/options.py
new file mode 100644
index 0000000..673be1e
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/options.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from optparse import make_option
+
+class Options(object):
+    force_clean = make_option("--force-clean", action="store_true", dest="force_clean", default=False, help="Clean working directory before applying patches (removes local changes and commits)")
+    clean = make_option("--no-clean", action="store_false", dest="clean", default=True, help="Don't check if the working directory is clean before applying patches")
+    check_builders = make_option("--ignore-builders", action="store_false", dest="check_builders", default=True, help="Don't check to see if the build.webkit.org builders are green before landing.")
+    quiet = make_option("--quiet", action="store_true", dest="quiet", default=False, help="Produce less console output.")
+    non_interactive = make_option("--non-interactive", action="store_true", dest="non_interactive", default=False, help="Never prompt the user, fail as fast as possible.")
+    parent_command = make_option("--parent-command", action="store", dest="parent_command", default=None, help="(Internal) The command that spawned this instance.")
+    update = make_option("--no-update", action="store_false", dest="update", default=True, help="Don't update the working directory.")
+    local_commit = make_option("--local-commit", action="store_true", dest="local_commit", default=False, help="Make a local commit for each applied patch")
+    build = make_option("--no-build", action="store_false", dest="build", default=True, help="Commit without building first, implies --no-test.")
+    build_style = make_option("--build-style", action="store", dest="build_style", default=None, help="Whether to build debug, release, or both.")
+    test = make_option("--no-test", action="store_false", dest="test", default=True, help="Commit without running run-webkit-tests.")
+    close_bug = make_option("--no-close", action="store_false", dest="close_bug", default=True, help="Leave bug open after landing.")
+    port = make_option("--port", action="store", dest="port", default=None, help="Specify a port (e.g., mac, qt, gtk, ...).")
+    reviewer = make_option("-r", "--reviewer", action="store", type="string", dest="reviewer", help="Update ChangeLogs to say Reviewed by REVIEWER.")
+    complete_rollout = make_option("--complete-rollout", action="store_true", dest="complete_rollout", help="Commit the revert and re-open the original bug.")
+    obsolete_patches = make_option("--no-obsolete", action="store_false", dest="obsolete_patches", default=True, help="Do not obsolete old patches before posting this one.")
+    review = make_option("--no-review", action="store_false", dest="review", default=True, help="Do not mark the patch for review.")
+    request_commit = make_option("--request-commit", action="store_true", dest="request_commit", default=False, help="Mark the patch as needing auto-commit after review.")
+    description = make_option("-m", "--description", action="store", type="string", dest="description", help="Description string for the attachment (default: \"patch\")")
+    cc = make_option("--cc", action="store", type="string", dest="cc", help="Comma-separated list of email addresses to carbon-copy.")
+    component = make_option("--component", action="store", type="string", dest="component", help="Component for the new bug.")
+    confirm = make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Skip confirmation steps.")
+    email = make_option("--email", action="store", type="string", dest="email", help="Email address to use in ChangeLogs.")
diff --git a/WebKitTools/Scripts/webkitpy/steps/postdiff.py b/WebKitTools/Scripts/webkitpy/steps/postdiff.py
new file mode 100644
index 0000000..47d3344
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/postdiff.py
@@ -0,0 +1,48 @@
+# Copyright (C) 2010 Google Inc. 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 StringIO
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+
+
+class PostDiff(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.description,
+            Options.review,
+            Options.request_commit,
+        ]
+
+    def run(self, state):
+        diff = self.cached_lookup(state, "diff")
+        diff_file = StringIO.StringIO(diff) # add_patch_to_bug expects a file-like object
+        description = self._options.description or "Patch"
+        self._tool.bugs.add_patch_to_bug(state["bug_id"], diff_file, description, mark_for_review=self._options.review, mark_for_commit_queue=self._options.request_commit)
diff --git a/WebKitTools/Scripts/webkitpy/steps/preparechangelog.py b/WebKitTools/Scripts/webkitpy/steps/preparechangelog.py
new file mode 100644
index 0000000..bd41f0b
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/preparechangelog.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2010 Google Inc. 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 os
+
+from webkitpy.executive import ScriptError
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import error
+
+
+class PrepareChangeLog(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.port,
+            Options.quiet,
+            Options.email,
+        ]
+
+    def run(self, state):
+        if self.cached_lookup(state, "changelogs"):
+            return
+        os.chdir(self._tool.scm().checkout_root)
+        args = [self.port().script_path("prepare-ChangeLog")]
+        if state["bug_id"]:
+            args.append("--bug=%s" % state["bug_id"])
+        if self._options.email:
+            args.append("--email=%s" % self._options.email)
+        try:
+            self._tool.executive.run_and_throw_if_fail(args, self._options.quiet)
+        except ScriptError, e:
+            error("Unable to prepare ChangeLogs.")
+        state["diff"] = None # We've changed the diff
diff --git a/WebKitTools/Scripts/webkitpy/steps/preparechangelogforrevert.py b/WebKitTools/Scripts/webkitpy/steps/preparechangelogforrevert.py
new file mode 100644
index 0000000..99f392b
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/preparechangelogforrevert.py
@@ -0,0 +1,46 @@
+# Copyright (C) 2010 Google Inc. 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 os
+
+from webkitpy.changelogs import ChangeLog
+from webkitpy.steps.abstractstep import AbstractStep
+
+
+class PrepareChangeLogForRevert(AbstractStep):
+    def run(self, state):
+        # First, discard the ChangeLog changes from the rollout.
+        os.chdir(self._tool.scm().checkout_root)
+        changelog_paths = self._tool.scm().modified_changelogs()
+        self._tool.scm().revert_files(changelog_paths)
+
+        # Second, make new ChangeLog entries for this rollout.
+        # This could move to prepare-ChangeLog by adding a --revert= option.
+        self._run_script("prepare-ChangeLog")
+        for changelog_path in changelog_paths:
+            ChangeLog(changelog_path).update_for_revert(state["revision"])
diff --git a/WebKitTools/Scripts/webkitpy/steps/promptforbugortitle.py b/WebKitTools/Scripts/webkitpy/steps/promptforbugortitle.py
new file mode 100644
index 0000000..fb2f877
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/promptforbugortitle.py
@@ -0,0 +1,45 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+
+
+class PromptForBugOrTitle(AbstractStep):
+    def run(self, state):
+        # No need to prompt if we alrady have the bug_id.
+        if state.get("bug_id"):
+            return
+        user_response = self._tool.user.prompt("Please enter a bug number or a title for a new bug:\n")
+        # If the user responds with a number, we assume it's bug number.
+        # Otherwise we assume it's a bug subject.
+        try:
+            state["bug_id"] = int(user_response)
+        except ValueError, TypeError:
+            state["bug_title"] = user_response
+            # FIXME: This is kind of a lame description.
+            state["bug_description"] = user_response
diff --git a/WebKitTools/Scripts/webkitpy/steps/revertrevision.py b/WebKitTools/Scripts/webkitpy/steps/revertrevision.py
new file mode 100644
index 0000000..ce6c263
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/revertrevision.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+
+
+class RevertRevision(AbstractStep):
+    def run(self, state):
+        self._tool.scm().apply_reverse_diff(state["revision"])
diff --git a/WebKitTools/Scripts/webkitpy/steps/runtests.py b/WebKitTools/Scripts/webkitpy/steps/runtests.py
new file mode 100644
index 0000000..da0a691
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/runtests.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+
+class RunTests(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.build,
+            Options.test,
+            Options.non_interactive,
+            Options.quiet,
+            Options.port,
+        ]
+
+    def run(self, state):
+        if not self._options.build:
+            return
+        if not self._options.test:
+            return
+        args = self.port().run_webkit_tests_command()
+        if self._options.non_interactive:
+            args.append("--no-launch-safari")
+            args.append("--exit-after-n-failures=1")
+        if self._options.quiet:
+            args.append("--quiet")
+        self._tool.executive.run_and_throw_if_fail(args)
diff --git a/WebKitTools/Scripts/webkitpy/steps/steps_unittest.py b/WebKitTools/Scripts/webkitpy/steps/steps_unittest.py
new file mode 100644
index 0000000..3e6a032
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/steps_unittest.py
@@ -0,0 +1,56 @@
+# Copyright (C) 2010 Google Inc. 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 unittest
+
+from webkitpy.steps.update import Update
+from webkitpy.steps.promptforbugortitle import PromptForBugOrTitle
+from webkitpy.mock_bugzillatool import MockBugzillaTool
+from webkitpy.outputcapture import OutputCapture
+from webkitpy.mock import Mock
+
+
+class StepsTest(unittest.TestCase):
+    def _run_step(self, step, tool=None, options=None, state=None):
+        if not tool:
+            tool = MockBugzillaTool()
+        if not options:
+            options = Mock()
+        if not state:
+            state = {}
+        step(tool, options).run(state)
+
+    def test_update_step(self):
+        options = Mock()
+        options.update = True
+        self._run_step(Update, options)
+
+    def test_prompt_for_bug_or_title_step(self):
+        tool = MockBugzillaTool()
+        tool.user.prompt = lambda message: 42
+        self._run_step(PromptForBugOrTitle, tool=tool)
diff --git a/WebKitTools/Scripts/webkitpy/steps/update.py b/WebKitTools/Scripts/webkitpy/steps/update.py
new file mode 100644
index 0000000..0f45671
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/update.py
@@ -0,0 +1,46 @@
+# Copyright (C) 2010 Google Inc. 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.
+
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import log
+
+
+class Update(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.update,
+            Options.port,
+        ]
+
+    def run(self, state):
+        if not self._options.update:
+            return
+        log("Updating working directory")
+        self._tool.executive.run_and_throw_if_fail(self.port().update_webkit_command(), quiet=True)
diff --git a/WebKitTools/Scripts/webkitpy/steps/updatechangelogswithreview_unittests.py b/WebKitTools/Scripts/webkitpy/steps/updatechangelogswithreview_unittests.py
new file mode 100644
index 0000000..43daa22
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/updatechangelogswithreview_unittests.py
@@ -0,0 +1,40 @@
+# Copyright (C) 2009 Google Inc. 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 unittest
+
+from webkitpy.steps.updatechangelogswithreviewer import UpdateChangeLogsWithReviewer
+from webkitpy.mock_bugzillatool import MockBugzillaTool
+from webkitpy.outputcapture import OutputCapture
+
+class UpdateChangeLogsWithReviewerStepTest(unittest.TestCase):
+    def test_guess_reviewer_from_bug(self):
+        capture = OutputCapture()
+        step = UpdateChangeLogsWithReviewer(MockBugzillaTool(), [])
+        expected_stderr = "0 reviewed patches on bug 75, cannot infer reviewer.\n"
+        capture.assert_outputs(self, step._guess_reviewer_from_bug, [75], expected_stderr=expected_stderr)
diff --git a/WebKitTools/Scripts/webkitpy/steps/updatechangelogswithreviewer.py b/WebKitTools/Scripts/webkitpy/steps/updatechangelogswithreviewer.py
new file mode 100644
index 0000000..60df204
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/steps/updatechangelogswithreviewer.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2010 Google Inc. 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 os
+
+from webkitpy.changelogs import ChangeLog
+from webkitpy.grammar import pluralize
+from webkitpy.steps.abstractstep import AbstractStep
+from webkitpy.steps.options import Options
+from webkitpy.webkit_logging import log, error
+
+class UpdateChangeLogsWithReviewer(AbstractStep):
+    @classmethod
+    def options(cls):
+        return [
+            Options.reviewer,
+        ]
+
+    def _guess_reviewer_from_bug(self, bug_id):
+        patches = self._tool.bugs.fetch_reviewed_patches_from_bug(bug_id)
+        if len(patches) != 1:
+            log("%s on bug %s, cannot infer reviewer." % (pluralize("reviewed patch", len(patches)), bug_id))
+            return None
+        patch = patches[0]
+        reviewer = patch["reviewer"]
+        log("Guessing \"%s\" as reviewer from attachment %s on bug %s." % (reviewer, patch["id"], bug_id))
+        return reviewer
+
+    def run(self, state):
+        bug_id = state["patch"]["bug_id"]
+        reviewer = self._options.reviewer
+        if not reviewer:
+            if not bug_id:
+                log("No bug id provided and --reviewer= not provided.  Not updating ChangeLogs with reviewer.")
+                return
+            reviewer = self._guess_reviewer_from_bug(bug_id)
+
+        if not reviewer:
+            log("Failed to guess reviewer from bug %s and --reviewer= not provided.  Not updating ChangeLogs with reviewer." % bug_id)
+            return
+
+        os.chdir(self._tool.scm().checkout_root)
+        for changelog_path in self._tool.scm().modified_changelogs():
+            ChangeLog(changelog_path).set_reviewer(reviewer)
diff --git a/WebKitTools/Scripts/webkitpy/stepsequence.py b/WebKitTools/Scripts/webkitpy/stepsequence.py
index b7e544a..008b366 100644
--- a/WebKitTools/Scripts/webkitpy/stepsequence.py
+++ b/WebKitTools/Scripts/webkitpy/stepsequence.py
@@ -26,7 +26,8 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-from webkitpy.buildsteps import CommandOptions
+import webkitpy.steps as steps
+
 from webkitpy.executive import ScriptError
 from webkitpy.webkit_logging import log
 from webkitpy.scm import CheckoutNeedsUpdate
@@ -45,8 +46,8 @@ class StepSequence(object):
 
     def options(self):
         collected_options = [
-            CommandOptions.parent_command,
-            CommandOptions.quiet,
+            steps.Options.parent_command,
+            steps.Options.quiet,
         ]
         for step in self._steps:
             collected_options = collected_options + step.options()

-- 
WebKit Debian packaging



More information about the Pkg-webkit-commits mailing list