WebKitTools/Scripts/webkitpy/tool/commands/download.py
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 # Copyright (c) 2009 Google Inc. All rights reserved.
       
     2 # Copyright (c) 2009 Apple Inc. All rights reserved.
       
     3 #
       
     4 # Redistribution and use in source and binary forms, with or without
       
     5 # modification, are permitted provided that the following conditions are
       
     6 # met:
       
     7 # 
       
     8 #     * Redistributions of source code must retain the above copyright
       
     9 # notice, this list of conditions and the following disclaimer.
       
    10 #     * Redistributions in binary form must reproduce the above
       
    11 # copyright notice, this list of conditions and the following disclaimer
       
    12 # in the documentation and/or other materials provided with the
       
    13 # distribution.
       
    14 #     * Neither the name of Google Inc. nor the names of its
       
    15 # contributors may be used to endorse or promote products derived from
       
    16 # this software without specific prior written permission.
       
    17 # 
       
    18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       
    19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
       
    20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
       
    21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
       
    22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
       
    23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
       
    24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
       
    25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
       
    26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
       
    28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    29 
       
    30 import os
       
    31 
       
    32 from optparse import make_option
       
    33 
       
    34 import webkitpy.tool.steps as steps
       
    35 
       
    36 from webkitpy.common.checkout.changelog import ChangeLog, view_source_url
       
    37 from webkitpy.common.system.executive import ScriptError
       
    38 from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand
       
    39 from webkitpy.tool.commands.stepsequence import StepSequence
       
    40 from webkitpy.tool.comments import bug_comment_from_commit_text
       
    41 from webkitpy.tool.grammar import pluralize
       
    42 from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
       
    43 from webkitpy.common.system.deprecated_logging import error, log
       
    44 
       
    45 
       
    46 class Update(AbstractSequencedCommand):
       
    47     name = "update"
       
    48     help_text = "Update working copy (used internally)"
       
    49     steps = [
       
    50         steps.CleanWorkingDirectory,
       
    51         steps.Update,
       
    52     ]
       
    53 
       
    54 
       
    55 class Build(AbstractSequencedCommand):
       
    56     name = "build"
       
    57     help_text = "Update working copy and build"
       
    58     steps = [
       
    59         steps.CleanWorkingDirectory,
       
    60         steps.Update,
       
    61         steps.Build,
       
    62     ]
       
    63 
       
    64 
       
    65 class BuildAndTest(AbstractSequencedCommand):
       
    66     name = "build-and-test"
       
    67     help_text = "Update working copy, build, and run the tests"
       
    68     steps = [
       
    69         steps.CleanWorkingDirectory,
       
    70         steps.Update,
       
    71         steps.Build,
       
    72         steps.RunTests,
       
    73     ]
       
    74 
       
    75 
       
    76 class Land(AbstractSequencedCommand):
       
    77     name = "land"
       
    78     help_text = "Land the current working directory diff and updates the associated bug if any"
       
    79     argument_names = "[BUGID]"
       
    80     show_in_main_help = True
       
    81     steps = [
       
    82         steps.EnsureBuildersAreGreen,
       
    83         steps.UpdateChangeLogsWithReviewer,
       
    84         steps.ValidateReviewer,
       
    85         steps.Build,
       
    86         steps.RunTests,
       
    87         steps.Commit,
       
    88         steps.CloseBugForLandDiff,
       
    89     ]
       
    90     long_help = """land commits the current working copy diff (just as svn or git commit would).
       
    91 land will NOT build and run the tests before committing, but you can use the --build option for that.
       
    92 If a bug id is provided, or one can be found in the ChangeLog land will update the bug after committing."""
       
    93 
       
    94     def _prepare_state(self, options, args, tool):
       
    95         return {
       
    96             "bug_id": (args and args[0]) or tool.checkout().bug_id_for_this_commit(options.git_commit),
       
    97         }
       
    98 
       
    99 
       
   100 class LandCowboy(AbstractSequencedCommand):
       
   101     name = "land-cowboy"
       
   102     help_text = "Prepares a ChangeLog and lands the current working directory diff."
       
   103     steps = [
       
   104         steps.PrepareChangeLog,
       
   105         steps.EditChangeLog,
       
   106         steps.ConfirmDiff,
       
   107         steps.Build,
       
   108         steps.RunTests,
       
   109         steps.Commit,
       
   110     ]
       
   111 
       
   112 
       
   113 class AbstractPatchProcessingCommand(AbstractDeclarativeCommand):
       
   114     # Subclasses must implement the methods below.  We don't declare them here
       
   115     # because we want to be able to implement them with mix-ins.
       
   116     #
       
   117     # def _fetch_list_of_patches_to_process(self, options, args, tool):
       
   118     # def _prepare_to_process(self, options, args, tool):
       
   119 
       
   120     @staticmethod
       
   121     def _collect_patches_by_bug(patches):
       
   122         bugs_to_patches = {}
       
   123         for patch in patches:
       
   124             bugs_to_patches[patch.bug_id()] = bugs_to_patches.get(patch.bug_id(), []) + [patch]
       
   125         return bugs_to_patches
       
   126 
       
   127     def execute(self, options, args, tool):
       
   128         self._prepare_to_process(options, args, tool)
       
   129         patches = self._fetch_list_of_patches_to_process(options, args, tool)
       
   130 
       
   131         # It's nice to print out total statistics.
       
   132         bugs_to_patches = self._collect_patches_by_bug(patches)
       
   133         log("Processing %s from %s." % (pluralize("patch", len(patches)), pluralize("bug", len(bugs_to_patches))))
       
   134 
       
   135         for patch in patches:
       
   136             self._process_patch(patch, options, args, tool)
       
   137 
       
   138 
       
   139 class AbstractPatchSequencingCommand(AbstractPatchProcessingCommand):
       
   140     prepare_steps = None
       
   141     main_steps = None
       
   142 
       
   143     def __init__(self):
       
   144         options = []
       
   145         self._prepare_sequence = StepSequence(self.prepare_steps)
       
   146         self._main_sequence = StepSequence(self.main_steps)
       
   147         options = sorted(set(self._prepare_sequence.options() + self._main_sequence.options()))
       
   148         AbstractPatchProcessingCommand.__init__(self, options)
       
   149 
       
   150     def _prepare_to_process(self, options, args, tool):
       
   151         self._prepare_sequence.run_and_handle_errors(tool, options)
       
   152 
       
   153     def _process_patch(self, patch, options, args, tool):
       
   154         state = { "patch" : patch }
       
   155         self._main_sequence.run_and_handle_errors(tool, options, state)
       
   156 
       
   157 
       
   158 class ProcessAttachmentsMixin(object):
       
   159     def _fetch_list_of_patches_to_process(self, options, args, tool):
       
   160         return map(lambda patch_id: tool.bugs.fetch_attachment(patch_id), args)
       
   161 
       
   162 
       
   163 class ProcessBugsMixin(object):
       
   164     def _fetch_list_of_patches_to_process(self, options, args, tool):
       
   165         all_patches = []
       
   166         for bug_id in args:
       
   167             patches = tool.bugs.fetch_bug(bug_id).reviewed_patches()
       
   168             log("%s found on bug %s." % (pluralize("reviewed patch", len(patches)), bug_id))
       
   169             all_patches += patches
       
   170         return all_patches
       
   171 
       
   172 
       
   173 class CheckStyle(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
       
   174     name = "check-style"
       
   175     help_text = "Run check-webkit-style on the specified attachments"
       
   176     argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
       
   177     main_steps = [
       
   178         steps.CleanWorkingDirectory,
       
   179         steps.Update,
       
   180         steps.ApplyPatch,
       
   181         steps.CheckStyle,
       
   182     ]
       
   183 
       
   184 
       
   185 class BuildAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
       
   186     name = "build-attachment"
       
   187     help_text = "Apply and build patches from bugzilla"
       
   188     argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
       
   189     main_steps = [
       
   190         steps.CleanWorkingDirectory,
       
   191         steps.Update,
       
   192         steps.ApplyPatch,
       
   193         steps.Build,
       
   194     ]
       
   195 
       
   196 
       
   197 class PostAttachmentToRietveld(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
       
   198     name = "post-attachment-to-rietveld"
       
   199     help_text = "Uploads a bugzilla attachment to rietveld"
       
   200     arguments_names = "ATTACHMENTID"
       
   201     main_steps = [
       
   202         steps.CleanWorkingDirectory,
       
   203         steps.Update,
       
   204         steps.ApplyPatch,
       
   205         steps.PostCodeReview,
       
   206     ]
       
   207 
       
   208 
       
   209 class AbstractPatchApplyingCommand(AbstractPatchSequencingCommand):
       
   210     prepare_steps = [
       
   211         steps.EnsureLocalCommitIfNeeded,
       
   212         steps.CleanWorkingDirectoryWithLocalCommits,
       
   213         steps.Update,
       
   214     ]
       
   215     main_steps = [
       
   216         steps.ApplyPatchWithLocalCommit,
       
   217     ]
       
   218     long_help = """Updates the working copy.
       
   219 Downloads and applies the patches, creating local commits if necessary."""
       
   220 
       
   221 
       
   222 class ApplyAttachment(AbstractPatchApplyingCommand, ProcessAttachmentsMixin):
       
   223     name = "apply-attachment"
       
   224     help_text = "Apply an attachment to the local working directory"
       
   225     argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
       
   226     show_in_main_help = True
       
   227 
       
   228 
       
   229 class ApplyFromBug(AbstractPatchApplyingCommand, ProcessBugsMixin):
       
   230     name = "apply-from-bug"
       
   231     help_text = "Apply reviewed patches from provided bugs to the local working directory"
       
   232     argument_names = "BUGID [BUGIDS]"
       
   233     show_in_main_help = True
       
   234 
       
   235 
       
   236 class AbstractPatchLandingCommand(AbstractPatchSequencingCommand):
       
   237     prepare_steps = [
       
   238         steps.EnsureBuildersAreGreen,
       
   239     ]
       
   240     main_steps = [
       
   241         steps.CleanWorkingDirectory,
       
   242         steps.Update,
       
   243         steps.ApplyPatch,
       
   244         steps.ValidateReviewer,
       
   245         steps.Build,
       
   246         steps.RunTests,
       
   247         steps.Commit,
       
   248         steps.ClosePatch,
       
   249         steps.CloseBug,
       
   250     ]
       
   251     long_help = """Checks to make sure builders are green.
       
   252 Updates the working copy.
       
   253 Applies the patch.
       
   254 Builds.
       
   255 Runs the layout tests.
       
   256 Commits the patch.
       
   257 Clears the flags on the patch.
       
   258 Closes the bug if no patches are marked for review."""
       
   259 
       
   260 
       
   261 class LandAttachment(AbstractPatchLandingCommand, ProcessAttachmentsMixin):
       
   262     name = "land-attachment"
       
   263     help_text = "Land patches from bugzilla, optionally building and testing them first"
       
   264     argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
       
   265     show_in_main_help = True
       
   266 
       
   267 
       
   268 class LandFromBug(AbstractPatchLandingCommand, ProcessBugsMixin):
       
   269     name = "land-from-bug"
       
   270     help_text = "Land all patches on the given bugs, optionally building and testing them first"
       
   271     argument_names = "BUGID [BUGIDS]"
       
   272     show_in_main_help = True
       
   273 
       
   274 
       
   275 class AbstractRolloutPrepCommand(AbstractSequencedCommand):
       
   276     argument_names = "REVISION REASON"
       
   277 
       
   278     def _commit_info(self, revision):
       
   279         commit_info = self.tool.checkout().commit_info_for_revision(revision)
       
   280         if commit_info and commit_info.bug_id():
       
   281             # Note: Don't print a bug URL here because it will confuse the
       
   282             #       SheriffBot because the SheriffBot just greps the output
       
   283             #       of create-rollout for bug URLs.  It should do better
       
   284             #       parsing instead.
       
   285             log("Preparing rollout for bug %s." % commit_info.bug_id())
       
   286         else:
       
   287             log("Unable to parse bug number from diff.")
       
   288         return commit_info
       
   289 
       
   290     def _prepare_state(self, options, args, tool):
       
   291         revision = args[0]
       
   292         commit_info = self._commit_info(revision)
       
   293         cc_list = sorted([party.bugzilla_email()
       
   294                           for party in commit_info.responsible_parties()
       
   295                           if party.bugzilla_email()])
       
   296         return {
       
   297             "revision": revision,
       
   298             "bug_id": commit_info.bug_id(),
       
   299             # FIXME: We should used the list as the canonical representation.
       
   300             "bug_cc": ",".join(cc_list),
       
   301             "reason": args[1],
       
   302         }
       
   303 
       
   304 
       
   305 class PrepareRollout(AbstractRolloutPrepCommand):
       
   306     name = "prepare-rollout"
       
   307     help_text = "Revert the given revision in the working copy and prepare ChangeLogs with revert reason"
       
   308     long_help = """Updates the working copy.
       
   309 Applies the inverse diff for the provided revision.
       
   310 Creates an appropriate rollout ChangeLog, including a trac link and bug link.
       
   311 """
       
   312     steps = [
       
   313         steps.CleanWorkingDirectory,
       
   314         steps.Update,
       
   315         steps.RevertRevision,
       
   316         steps.PrepareChangeLogForRevert,
       
   317     ]
       
   318 
       
   319 
       
   320 class CreateRollout(AbstractRolloutPrepCommand):
       
   321     name = "create-rollout"
       
   322     help_text = "Creates a bug to track a broken SVN revision and uploads a rollout patch."
       
   323     steps = [
       
   324         steps.CleanWorkingDirectory,
       
   325         steps.Update,
       
   326         steps.RevertRevision,
       
   327         steps.CreateBug,
       
   328         steps.PrepareChangeLogForRevert,
       
   329         steps.PostDiffForRevert,
       
   330     ]
       
   331 
       
   332     def _prepare_state(self, options, args, tool):
       
   333         state = AbstractRolloutPrepCommand._prepare_state(self, options, args, tool)
       
   334         # Currently, state["bug_id"] points to the bug that caused the
       
   335         # regression.  We want to create a new bug that blocks the old bug
       
   336         # so we move state["bug_id"] to state["bug_blocked"] and delete the
       
   337         # old state["bug_id"] so that steps.CreateBug will actually create
       
   338         # the new bug that we want (and subsequently store its bug id into
       
   339         # state["bug_id"])
       
   340         state["bug_blocked"] = state["bug_id"]
       
   341         del state["bug_id"]
       
   342         state["bug_title"] = "REGRESSION(r%s): %s" % (state["revision"], state["reason"])
       
   343         state["bug_description"] = "%s broke the build:\n%s" % (view_source_url(state["revision"]), state["reason"])
       
   344         # FIXME: If we had more context here, we could link to other open bugs
       
   345         #        that mention the test that regressed.
       
   346         if options.parent_command == "sheriff-bot":
       
   347             state["bug_description"] += """
       
   348 
       
   349 This is an automatic bug report generated by the sheriff-bot. If this bug
       
   350 report was created because of a flaky test, please file a bug for the flaky
       
   351 test (if we don't already have one on file) and dup this bug against that bug
       
   352 so that we can track how often these flaky tests case pain.
       
   353 
       
   354 "Only you can prevent forest fires." -- Smokey the Bear
       
   355 """
       
   356         return state
       
   357 
       
   358 
       
   359 class Rollout(AbstractRolloutPrepCommand):
       
   360     name = "rollout"
       
   361     show_in_main_help = True
       
   362     help_text = "Revert the given revision in the working copy and optionally commit the revert and re-open the original bug"
       
   363     long_help = """Updates the working copy.
       
   364 Applies the inverse diff for the provided revision.
       
   365 Creates an appropriate rollout ChangeLog, including a trac link and bug link.
       
   366 Opens the generated ChangeLogs in $EDITOR.
       
   367 Shows the prepared diff for confirmation.
       
   368 Commits the revert and updates the bug (including re-opening the bug if necessary)."""
       
   369     steps = [
       
   370         steps.CleanWorkingDirectory,
       
   371         steps.Update,
       
   372         steps.RevertRevision,
       
   373         steps.PrepareChangeLogForRevert,
       
   374         steps.EditChangeLog,
       
   375         steps.ConfirmDiff,
       
   376         steps.Build,
       
   377         steps.Commit,
       
   378         steps.ReopenBugAfterRollout,
       
   379     ]