• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
30import os
31
32from optparse import make_option
33
34import webkitpy.steps as steps
35
36from webkitpy.bugzilla import parse_bug_id
37# We could instead use from modules import buildsteps and then prefix every buildstep with "buildsteps."
38from webkitpy.changelogs import ChangeLog
39from webkitpy.commands.abstractsequencedcommand import AbstractSequencedCommand
40from webkitpy.comments import bug_comment_from_commit_text
41from webkitpy.executive import ScriptError
42from webkitpy.grammar import pluralize
43from webkitpy.webkit_logging import error, log
44from webkitpy.multicommandtool import AbstractDeclarativeCommand
45from webkitpy.stepsequence import StepSequence
46
47
48class 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
58class 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
69class 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).
84land will build and run the tests before committing.
85If 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
93class 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
119class 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
138class 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
143class 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
153class 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
165class 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
177class 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.
187Downloads and applies the patches, creating local commits if necessary."""
188
189
190class 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
197class 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
204class 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.
220Updates the working copy.
221Applies the patch.
222Builds.
223Runs the layout tests.
224Commits the patch.
225Clears the flags on the patch.
226Closes the bug if no patches are marked for review."""
227
228
229class 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
236class 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
243class 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.
249Applies the inverse diff for the provided revision.
250Creates an appropriate rollout ChangeLog, including a trac link and bug link.
251Opens the generated ChangeLogs in $EDITOR.
252Shows the prepared diff for confirmation.
253Commits 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