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.steps as steps 35 36 from webkitpy.bugzilla import parse_bug_id 37 # We could instead use from modules import buildsteps and then prefix every buildstep with "buildsteps." 38 from webkitpy.changelogs import ChangeLog 39 from webkitpy.commands.abstractsequencedcommand import AbstractSequencedCommand 40 from webkitpy.comments import bug_comment_from_commit_text 41 from webkitpy.executive import ScriptError 42 from webkitpy.grammar import pluralize 43 from webkitpy.webkit_logging import error, log 44 from webkitpy.multicommandtool import AbstractDeclarativeCommand 45 from webkitpy.stepsequence import StepSequence 46 47 48 class Build(AbstractSequencedCommand): 49 name = "build" 50 help_text = "Update working copy and build" 51 steps = [ 52 steps.CleanWorkingDirectory, 53 steps.Update, 54 steps.Build, 55 ] 56 57 58 class BuildAndTest(AbstractSequencedCommand): 59 name = "build-and-test" 60 help_text = "Update working copy, build, and run the tests" 61 steps = [ 62 steps.CleanWorkingDirectory, 63 steps.Update, 64 steps.Build, 65 steps.RunTests, 66 ] 67 68 69 class Land(AbstractSequencedCommand): 70 name = "land" 71 help_text = "Land the current working directory diff and updates the associated bug if any" 72 argument_names = "[BUGID]" 73 show_in_main_help = True 74 steps = [ 75 steps.EnsureBuildersAreGreen, 76 steps.UpdateChangeLogsWithReviewer, 77 steps.EnsureBuildersAreGreen, 78 steps.Build, 79 steps.RunTests, 80 steps.Commit, 81 steps.CloseBugForLandDiff, 82 ] 83 long_help = """land commits the current working copy diff (just as svn or git commit would). 84 land will build and run the tests before committing. 85 If a bug id is provided, or one can be found in the ChangeLog land will update the bug after committing.""" 86 87 def _prepare_state(self, options, args, tool): 88 return { 89 "bug_id" : (args and args[0]) or parse_bug_id(tool.scm().create_patch()), 90 } 91 92 93 class AbstractPatchProcessingCommand(AbstractDeclarativeCommand): 94 # Subclasses must implement the methods below. We don't declare them here 95 # because we want to be able to implement them with mix-ins. 96 # 97 # def _fetch_list_of_patches_to_process(self, options, args, tool): 98 # def _prepare_to_process(self, options, args, tool): 99 100 @staticmethod 101 def _collect_patches_by_bug(patches): 102 bugs_to_patches = {} 103 for patch in patches: 104 bugs_to_patches[patch.bug_id()] = bugs_to_patches.get(patch.bug_id(), []) + [patch] 105 return bugs_to_patches 106 107 def execute(self, options, args, tool): 108 self._prepare_to_process(options, args, tool) 109 patches = self._fetch_list_of_patches_to_process(options, args, tool) 110 111 # It's nice to print out total statistics. 112 bugs_to_patches = self._collect_patches_by_bug(patches) 113 log("Processing %s from %s." % (pluralize("patch", len(patches)), pluralize("bug", len(bugs_to_patches)))) 114 115 for patch in patches: 116 self._process_patch(patch, options, args, tool) 117 118 119 class AbstractPatchSequencingCommand(AbstractPatchProcessingCommand): 120 prepare_steps = None 121 main_steps = None 122 123 def __init__(self): 124 options = [] 125 self._prepare_sequence = StepSequence(self.prepare_steps) 126 self._main_sequence = StepSequence(self.main_steps) 127 options = sorted(set(self._prepare_sequence.options() + self._main_sequence.options())) 128 AbstractPatchProcessingCommand.__init__(self, options) 129 130 def _prepare_to_process(self, options, args, tool): 131 self._prepare_sequence.run_and_handle_errors(tool, options) 132 133 def _process_patch(self, patch, options, args, tool): 134 state = { "patch" : patch } 135 self._main_sequence.run_and_handle_errors(tool, options, state) 136 137 138 class ProcessAttachmentsMixin(object): 139 def _fetch_list_of_patches_to_process(self, options, args, tool): 140 return map(lambda patch_id: tool.bugs.fetch_attachment(patch_id), args) 141 142 143 class ProcessBugsMixin(object): 144 def _fetch_list_of_patches_to_process(self, options, args, tool): 145 all_patches = [] 146 for bug_id in args: 147 patches = tool.bugs.fetch_bug(bug_id).reviewed_patches() 148 log("%s found on bug %s." % (pluralize("reviewed patch", len(patches)), bug_id)) 149 all_patches += patches 150 return all_patches 151 152 153 class CheckStyle(AbstractPatchSequencingCommand, ProcessAttachmentsMixin): 154 name = "check-style" 155 help_text = "Run check-webkit-style on the specified attachments" 156 argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" 157 main_steps = [ 158 steps.CleanWorkingDirectory, 159 steps.Update, 160 steps.ApplyPatch, 161 steps.CheckStyle, 162 ] 163 164 165 class BuildAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin): 166 name = "build-attachment" 167 help_text = "Apply and build patches from bugzilla" 168 argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" 169 main_steps = [ 170 steps.CleanWorkingDirectory, 171 steps.Update, 172 steps.ApplyPatch, 173 steps.Build, 174 ] 175 176 177 class AbstractPatchApplyingCommand(AbstractPatchSequencingCommand): 178 prepare_steps = [ 179 steps.EnsureLocalCommitIfNeeded, 180 steps.CleanWorkingDirectoryWithLocalCommits, 181 steps.Update, 182 ] 183 main_steps = [ 184 steps.ApplyPatchWithLocalCommit, 185 ] 186 long_help = """Updates the working copy. 187 Downloads and applies the patches, creating local commits if necessary.""" 188 189 190 class ApplyAttachment(AbstractPatchApplyingCommand, ProcessAttachmentsMixin): 191 name = "apply-attachment" 192 help_text = "Apply an attachment to the local working directory" 193 argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" 194 show_in_main_help = True 195 196 197 class ApplyFromBug(AbstractPatchApplyingCommand, ProcessBugsMixin): 198 name = "apply-from-bug" 199 help_text = "Apply reviewed patches from provided bugs to the local working directory" 200 argument_names = "BUGID [BUGIDS]" 201 show_in_main_help = True 202 203 204 class AbstractPatchLandingCommand(AbstractPatchSequencingCommand): 205 prepare_steps = [ 206 steps.EnsureBuildersAreGreen, 207 ] 208 main_steps = [ 209 steps.CleanWorkingDirectory, 210 steps.Update, 211 steps.ApplyPatch, 212 steps.EnsureBuildersAreGreen, 213 steps.Build, 214 steps.RunTests, 215 steps.Commit, 216 steps.ClosePatch, 217 steps.CloseBug, 218 ] 219 long_help = """Checks to make sure builders are green. 220 Updates the working copy. 221 Applies the patch. 222 Builds. 223 Runs the layout tests. 224 Commits the patch. 225 Clears the flags on the patch. 226 Closes the bug if no patches are marked for review.""" 227 228 229 class LandAttachment(AbstractPatchLandingCommand, ProcessAttachmentsMixin): 230 name = "land-attachment" 231 help_text = "Land patches from bugzilla, optionally building and testing them first" 232 argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" 233 show_in_main_help = True 234 235 236 class LandFromBug(AbstractPatchLandingCommand, ProcessBugsMixin): 237 name = "land-from-bug" 238 help_text = "Land all patches on the given bugs, optionally building and testing them first" 239 argument_names = "BUGID [BUGIDS]" 240 show_in_main_help = True 241 242 243 class Rollout(AbstractSequencedCommand): 244 name = "rollout" 245 show_in_main_help = True 246 help_text = "Revert the given revision in the working copy and optionally commit the revert and re-open the original bug" 247 argument_names = "REVISION REASON" 248 long_help = """Updates the working copy. 249 Applies the inverse diff for the provided revision. 250 Creates an appropriate rollout ChangeLog, including a trac link and bug link. 251 Opens the generated ChangeLogs in $EDITOR. 252 Shows the prepared diff for confirmation. 253 Commits the revert and updates the bug (including re-opening the bug if necessary).""" 254 steps = [ 255 steps.CleanWorkingDirectory, 256 steps.Update, 257 steps.RevertRevision, 258 steps.PrepareChangeLogForRevert, 259 steps.EditChangeLog, 260 steps.ConfirmDiff, 261 steps.CompleteRollout, 262 ] 263 264 @staticmethod 265 def _parse_bug_id_from_revision_diff(tool, revision): 266 original_diff = tool.scm().diff_for_revision(revision) 267 return parse_bug_id(original_diff) 268 269 def execute(self, options, args, tool): 270 revision = args[0] 271 reason = args[1] 272 bug_id = self._parse_bug_id_from_revision_diff(tool, revision) 273 if options.complete_rollout: 274 if bug_id: 275 log("Will re-open bug %s after rollout." % bug_id) 276 else: 277 log("Failed to parse bug number from diff. No bugs will be updated/reopened after the rollout.") 278 279 state = { 280 "revision" : revision, 281 "bug_id" : bug_id, 282 "reason" : reason, 283 } 284 self._sequence.run_and_handle_errors(tool, options, state) 285