~ubuntu-branches/ubuntu/raring/qtwebkit-source/raring-proposed

« back to all changes in this revision

Viewing changes to Tools/Scripts/webkitpy/tool/commands/download.py

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-02-18 14:24:18 UTC
  • Revision ID: package-import@ubuntu.com-20130218142418-eon0jmjg3nj438uy
Tags: upstream-2.3
ImportĀ upstreamĀ versionĀ 2.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2009, 2011 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 logging
 
31
 
 
32
from webkitpy.tool import steps
 
33
 
 
34
from webkitpy.common.checkout.changelog import ChangeLog
 
35
from webkitpy.common.config import urls
 
36
from webkitpy.common.system.executive import ScriptError
 
37
from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand
 
38
from webkitpy.tool.commands.stepsequence import StepSequence
 
39
from webkitpy.tool.comments import bug_comment_from_commit_text
 
40
from webkitpy.tool.grammar import pluralize
 
41
from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
 
42
 
 
43
_log = logging.getLogger(__name__)
 
44
 
 
45
 
 
46
class Clean(AbstractSequencedCommand):
 
47
    name = "clean"
 
48
    help_text = "Clean the working copy"
 
49
    steps = [
 
50
        steps.CleanWorkingDirectory,
 
51
    ]
 
52
 
 
53
    def _prepare_state(self, options, args, tool):
 
54
        options.force_clean = True
 
55
 
 
56
 
 
57
class Update(AbstractSequencedCommand):
 
58
    name = "update"
 
59
    help_text = "Update working copy (used internally)"
 
60
    steps = [
 
61
        steps.CleanWorkingDirectory,
 
62
        steps.Update,
 
63
    ]
 
64
 
 
65
 
 
66
class Build(AbstractSequencedCommand):
 
67
    name = "build"
 
68
    help_text = "Update working copy and build"
 
69
    steps = [
 
70
        steps.CleanWorkingDirectory,
 
71
        steps.Update,
 
72
        steps.Build,
 
73
    ]
 
74
 
 
75
    def _prepare_state(self, options, args, tool):
 
76
        options.build = True
 
77
 
 
78
 
 
79
class BuildAndTest(AbstractSequencedCommand):
 
80
    name = "build-and-test"
 
81
    help_text = "Update working copy, build, and run the tests"
 
82
    steps = [
 
83
        steps.CleanWorkingDirectory,
 
84
        steps.Update,
 
85
        steps.Build,
 
86
        steps.RunTests,
 
87
    ]
 
88
 
 
89
 
 
90
class Land(AbstractSequencedCommand):
 
91
    name = "land"
 
92
    help_text = "Land the current working directory diff and updates the associated bug if any"
 
93
    argument_names = "[BUGID]"
 
94
    show_in_main_help = True
 
95
    steps = [
 
96
        steps.AddSvnMimetypeForPng,
 
97
        steps.UpdateChangeLogsWithReviewer,
 
98
        steps.ValidateReviewer,
 
99
        steps.ValidateChangeLogs, # We do this after UpdateChangeLogsWithReviewer to avoid not having to cache the diff twice.
 
100
        steps.Build,
 
101
        steps.RunTests,
 
102
        steps.Commit,
 
103
        steps.CloseBugForLandDiff,
 
104
    ]
 
105
    long_help = """land commits the current working copy diff (just as svn or git commit would).
 
106
land will NOT build and run the tests before committing, but you can use the --build option for that.
 
107
If a bug id is provided, or one can be found in the ChangeLog land will update the bug after committing."""
 
108
 
 
109
    def _prepare_state(self, options, args, tool):
 
110
        changed_files = self._tool.scm().changed_files(options.git_commit)
 
111
        return {
 
112
            "changed_files": changed_files,
 
113
            "bug_id": (args and args[0]) or tool.checkout().bug_id_for_this_commit(options.git_commit, changed_files),
 
114
        }
 
115
 
 
116
 
 
117
class LandCowboy(AbstractSequencedCommand):
 
118
    name = "land-cowboy"
 
119
    help_text = "Prepares a ChangeLog and lands the current working directory diff."
 
120
    steps = [
 
121
        steps.PrepareChangeLog,
 
122
        steps.EditChangeLog,
 
123
        steps.CheckStyle,
 
124
        steps.ConfirmDiff,
 
125
        steps.Build,
 
126
        steps.RunTests,
 
127
        steps.Commit,
 
128
        steps.CloseBugForLandDiff,
 
129
    ]
 
130
 
 
131
    def _prepare_state(self, options, args, tool):
 
132
        options.check_style_filter = "-changelog"
 
133
 
 
134
 
 
135
class LandCowhand(LandCowboy):
 
136
    # Gender-blind term for cowboy, see: http://en.wiktionary.org/wiki/cowhand
 
137
    name = "land-cowhand"
 
138
 
 
139
 
 
140
class CheckStyleLocal(AbstractSequencedCommand):
 
141
    name = "check-style-local"
 
142
    help_text = "Run check-webkit-style on the current working directory diff"
 
143
    steps = [
 
144
        steps.CheckStyle,
 
145
    ]
 
146
 
 
147
 
 
148
class AbstractPatchProcessingCommand(AbstractDeclarativeCommand):
 
149
    # Subclasses must implement the methods below.  We don't declare them here
 
150
    # because we want to be able to implement them with mix-ins.
 
151
    #
 
152
    # pylint: disable-msg=E1101
 
153
    # def _fetch_list_of_patches_to_process(self, options, args, tool):
 
154
    # def _prepare_to_process(self, options, args, tool):
 
155
    # def _process_patch(self, options, args, tool):
 
156
 
 
157
    @staticmethod
 
158
    def _collect_patches_by_bug(patches):
 
159
        bugs_to_patches = {}
 
160
        for patch in patches:
 
161
            bugs_to_patches[patch.bug_id()] = bugs_to_patches.get(patch.bug_id(), []) + [patch]
 
162
        return bugs_to_patches
 
163
 
 
164
    def execute(self, options, args, tool):
 
165
        self._prepare_to_process(options, args, tool)
 
166
        patches = self._fetch_list_of_patches_to_process(options, args, tool)
 
167
 
 
168
        # It's nice to print out total statistics.
 
169
        bugs_to_patches = self._collect_patches_by_bug(patches)
 
170
        _log.info("Processing %s from %s." % (pluralize("patch", len(patches)), pluralize("bug", len(bugs_to_patches))))
 
171
 
 
172
        for patch in patches:
 
173
            self._process_patch(patch, options, args, tool)
 
174
 
 
175
 
 
176
class AbstractPatchSequencingCommand(AbstractPatchProcessingCommand):
 
177
    prepare_steps = None
 
178
    main_steps = None
 
179
 
 
180
    def __init__(self):
 
181
        options = []
 
182
        self._prepare_sequence = StepSequence(self.prepare_steps)
 
183
        self._main_sequence = StepSequence(self.main_steps)
 
184
        options = sorted(set(self._prepare_sequence.options() + self._main_sequence.options()))
 
185
        AbstractPatchProcessingCommand.__init__(self, options)
 
186
 
 
187
    def _prepare_to_process(self, options, args, tool):
 
188
        self._prepare_sequence.run_and_handle_errors(tool, options)
 
189
 
 
190
    def _process_patch(self, patch, options, args, tool):
 
191
        state = { "patch" : patch }
 
192
        self._main_sequence.run_and_handle_errors(tool, options, state)
 
193
 
 
194
 
 
195
class ProcessAttachmentsMixin(object):
 
196
    def _fetch_list_of_patches_to_process(self, options, args, tool):
 
197
        return map(lambda patch_id: tool.bugs.fetch_attachment(patch_id), args)
 
198
 
 
199
 
 
200
class ProcessBugsMixin(object):
 
201
    def _fetch_list_of_patches_to_process(self, options, args, tool):
 
202
        all_patches = []
 
203
        for bug_id in args:
 
204
            patches = tool.bugs.fetch_bug(bug_id).reviewed_patches()
 
205
            _log.info("%s found on bug %s." % (pluralize("reviewed patch", len(patches)), bug_id))
 
206
            all_patches += patches
 
207
        if not all_patches:
 
208
            _log.info("No reviewed patches found, looking for unreviewed patches.")
 
209
            for bug_id in args:
 
210
                patches = tool.bugs.fetch_bug(bug_id).patches()
 
211
                _log.info("%s found on bug %s." % (pluralize("patch", len(patches)), bug_id))
 
212
                all_patches += patches
 
213
        return all_patches
 
214
 
 
215
 
 
216
class ProcessURLsMixin(object):
 
217
    def _fetch_list_of_patches_to_process(self, options, args, tool):
 
218
        all_patches = []
 
219
        for url in args:
 
220
            bug_id = urls.parse_bug_id(url)
 
221
            if bug_id:
 
222
                patches = tool.bugs.fetch_bug(bug_id).patches()
 
223
                _log.info("%s found on bug %s." % (pluralize("patch", len(patches)), bug_id))
 
224
                all_patches += patches
 
225
 
 
226
            attachment_id = urls.parse_attachment_id(url)
 
227
            if attachment_id:
 
228
                all_patches += tool.bugs.fetch_attachment(attachment_id)
 
229
 
 
230
        return all_patches
 
231
 
 
232
 
 
233
class CheckStyle(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
 
234
    name = "check-style"
 
235
    help_text = "Run check-webkit-style on the specified attachments"
 
236
    argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
 
237
    main_steps = [
 
238
        steps.CleanWorkingDirectory,
 
239
        steps.Update,
 
240
        steps.ApplyPatch,
 
241
        steps.CheckStyle,
 
242
    ]
 
243
 
 
244
 
 
245
class BuildAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
 
246
    name = "build-attachment"
 
247
    help_text = "Apply and build patches from bugzilla"
 
248
    argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
 
249
    main_steps = [
 
250
        steps.CleanWorkingDirectory,
 
251
        steps.Update,
 
252
        steps.ApplyPatch,
 
253
        steps.Build,
 
254
    ]
 
255
 
 
256
 
 
257
class BuildAndTestAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
 
258
    name = "build-and-test-attachment"
 
259
    help_text = "Apply, build, and test patches from bugzilla"
 
260
    argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
 
261
    main_steps = [
 
262
        steps.CleanWorkingDirectory,
 
263
        steps.Update,
 
264
        steps.ApplyPatch,
 
265
        steps.Build,
 
266
        steps.RunTests,
 
267
    ]
 
268
 
 
269
 
 
270
class AbstractPatchApplyingCommand(AbstractPatchSequencingCommand):
 
271
    prepare_steps = [
 
272
        steps.EnsureLocalCommitIfNeeded,
 
273
        steps.CleanWorkingDirectoryWithLocalCommits,
 
274
        steps.Update,
 
275
    ]
 
276
    main_steps = [
 
277
        steps.ApplyPatchWithLocalCommit,
 
278
    ]
 
279
    long_help = """Updates the working copy.
 
280
Downloads and applies the patches, creating local commits if necessary."""
 
281
 
 
282
 
 
283
class ApplyAttachment(AbstractPatchApplyingCommand, ProcessAttachmentsMixin):
 
284
    name = "apply-attachment"
 
285
    help_text = "Apply an attachment to the local working directory"
 
286
    argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
 
287
    show_in_main_help = True
 
288
 
 
289
 
 
290
class ApplyFromBug(AbstractPatchApplyingCommand, ProcessBugsMixin):
 
291
    name = "apply-from-bug"
 
292
    help_text = "Apply reviewed patches from provided bugs to the local working directory"
 
293
    argument_names = "BUGID [BUGIDS]"
 
294
    show_in_main_help = True
 
295
 
 
296
 
 
297
class ApplyWatchList(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
 
298
    name = "apply-watchlist"
 
299
    help_text = "Applies the watchlist to the specified attachments"
 
300
    argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
 
301
    main_steps = [
 
302
        steps.CleanWorkingDirectory,
 
303
        steps.Update,
 
304
        steps.ApplyPatch,
 
305
        steps.ApplyWatchList,
 
306
    ]
 
307
    long_help = """"Applies the watchlist to the specified attachments.
 
308
Downloads the attachment, applies it locally, runs the watchlist against it, and updates the bug with the result."""
 
309
 
 
310
 
 
311
class AbstractPatchLandingCommand(AbstractPatchSequencingCommand):
 
312
    main_steps = [
 
313
        steps.CleanWorkingDirectory,
 
314
        steps.Update,
 
315
        steps.ApplyPatch,
 
316
        steps.ValidateChangeLogs,
 
317
        steps.ValidateReviewer,
 
318
        steps.Build,
 
319
        steps.RunTests,
 
320
        steps.Commit,
 
321
        steps.ClosePatch,
 
322
        steps.CloseBug,
 
323
    ]
 
324
    long_help = """Checks to make sure builders are green.
 
325
Updates the working copy.
 
326
Applies the patch.
 
327
Builds.
 
328
Runs the layout tests.
 
329
Commits the patch.
 
330
Clears the flags on the patch.
 
331
Closes the bug if no patches are marked for review."""
 
332
 
 
333
 
 
334
class LandAttachment(AbstractPatchLandingCommand, ProcessAttachmentsMixin):
 
335
    name = "land-attachment"
 
336
    help_text = "Land patches from bugzilla, optionally building and testing them first"
 
337
    argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
 
338
    show_in_main_help = True
 
339
 
 
340
 
 
341
class LandFromBug(AbstractPatchLandingCommand, ProcessBugsMixin):
 
342
    name = "land-from-bug"
 
343
    help_text = "Land all patches on the given bugs, optionally building and testing them first"
 
344
    argument_names = "BUGID [BUGIDS]"
 
345
    show_in_main_help = True
 
346
 
 
347
 
 
348
class LandFromURL(AbstractPatchLandingCommand, ProcessURLsMixin):
 
349
    name = "land-from-url"
 
350
    help_text = "Land all patches on the given URLs, optionally building and testing them first"
 
351
    argument_names = "URL [URLS]"
 
352
 
 
353
 
 
354
class ValidateChangelog(AbstractSequencedCommand):
 
355
    name = "validate-changelog"
 
356
    help_text = "Validate that the ChangeLogs and reviewers look reasonable"
 
357
    long_help = """Examines the current diff to see whether the ChangeLogs
 
358
and the reviewers listed in the ChangeLogs look reasonable.
 
359
"""
 
360
    steps = [
 
361
        steps.ValidateChangeLogs,
 
362
        steps.ValidateReviewer,
 
363
    ]
 
364
 
 
365
 
 
366
class AbstractRolloutPrepCommand(AbstractSequencedCommand):
 
367
    argument_names = "REVISION [REVISIONS] REASON"
 
368
 
 
369
    def _commit_info(self, revision):
 
370
        commit_info = self._tool.checkout().commit_info_for_revision(revision)
 
371
        if commit_info and commit_info.bug_id():
 
372
            # Note: Don't print a bug URL here because it will confuse the
 
373
            #       SheriffBot because the SheriffBot just greps the output
 
374
            #       of create-rollout for bug URLs.  It should do better
 
375
            #       parsing instead.
 
376
            _log.info("Preparing rollout for bug %s." % commit_info.bug_id())
 
377
        else:
 
378
            _log.info("Unable to parse bug number from diff.")
 
379
        return commit_info
 
380
 
 
381
    def _prepare_state(self, options, args, tool):
 
382
        revision_list = []
 
383
        for revision in str(args[0]).split():
 
384
            if revision.isdigit():
 
385
                revision_list.append(int(revision))
 
386
            else:
 
387
                raise ScriptError(message="Invalid svn revision number: " + revision)
 
388
        revision_list.sort()
 
389
 
 
390
        # We use the earliest revision for the bug info
 
391
        earliest_revision = revision_list[0]
 
392
        state = {
 
393
            "revision": earliest_revision,
 
394
            "revision_list": revision_list,
 
395
            "reason": args[1],
 
396
        }
 
397
        commit_info = self._commit_info(earliest_revision)
 
398
        if commit_info:
 
399
            state["bug_id"] = commit_info.bug_id()
 
400
            cc_list = sorted([party.bugzilla_email()
 
401
                            for party in commit_info.responsible_parties()
 
402
                            if party.bugzilla_email()])
 
403
            # FIXME: We should used the list as the canonical representation.
 
404
            state["bug_cc"] = ",".join(cc_list)
 
405
        return state
 
406
 
 
407
 
 
408
class PrepareRollout(AbstractRolloutPrepCommand):
 
409
    name = "prepare-rollout"
 
410
    help_text = "Revert the given revision(s) in the working copy and prepare ChangeLogs with revert reason"
 
411
    long_help = """Updates the working copy.
 
412
Applies the inverse diff for the provided revision(s).
 
413
Creates an appropriate rollout ChangeLog, including a trac link and bug link.
 
414
"""
 
415
    steps = [
 
416
        steps.CleanWorkingDirectory,
 
417
        steps.Update,
 
418
        steps.RevertRevision,
 
419
        steps.PrepareChangeLogForRevert,
 
420
    ]
 
421
 
 
422
 
 
423
class CreateRollout(AbstractRolloutPrepCommand):
 
424
    name = "create-rollout"
 
425
    help_text = "Creates a bug to track the broken SVN revision(s) and uploads a rollout patch."
 
426
    steps = [
 
427
        steps.CleanWorkingDirectory,
 
428
        steps.Update,
 
429
        steps.RevertRevision,
 
430
        steps.CreateBug,
 
431
        steps.PrepareChangeLogForRevert,
 
432
        steps.PostDiffForRevert,
 
433
    ]
 
434
 
 
435
    def _prepare_state(self, options, args, tool):
 
436
        state = AbstractRolloutPrepCommand._prepare_state(self, options, args, tool)
 
437
        # Currently, state["bug_id"] points to the bug that caused the
 
438
        # regression.  We want to create a new bug that blocks the old bug
 
439
        # so we move state["bug_id"] to state["bug_blocked"] and delete the
 
440
        # old state["bug_id"] so that steps.CreateBug will actually create
 
441
        # the new bug that we want (and subsequently store its bug id into
 
442
        # state["bug_id"])
 
443
        state["bug_blocked"] = state["bug_id"]
 
444
        del state["bug_id"]
 
445
        state["bug_title"] = "REGRESSION(r%s): %s" % (state["revision"], state["reason"])
 
446
        state["bug_description"] = "%s broke the build:\n%s" % (urls.view_revision_url(state["revision"]), state["reason"])
 
447
        # FIXME: If we had more context here, we could link to other open bugs
 
448
        #        that mention the test that regressed.
 
449
        if options.parent_command == "sheriff-bot":
 
450
            state["bug_description"] += """
 
451
 
 
452
This is an automatic bug report generated by the sheriff-bot. If this bug
 
453
report was created because of a flaky test, please file a bug for the flaky
 
454
test (if we don't already have one on file) and dup this bug against that bug
 
455
so that we can track how often these flaky tests case pain.
 
456
 
 
457
"Only you can prevent forest fires." -- Smokey the Bear
 
458
"""
 
459
        return state
 
460
 
 
461
 
 
462
class Rollout(AbstractRolloutPrepCommand):
 
463
    name = "rollout"
 
464
    show_in_main_help = True
 
465
    help_text = "Revert the given revision(s) in the working copy and optionally commit the revert and re-open the original bug"
 
466
    long_help = """Updates the working copy.
 
467
Applies the inverse diff for the provided revision.
 
468
Creates an appropriate rollout ChangeLog, including a trac link and bug link.
 
469
Opens the generated ChangeLogs in $EDITOR.
 
470
Shows the prepared diff for confirmation.
 
471
Commits the revert and updates the bug (including re-opening the bug if necessary)."""
 
472
    steps = [
 
473
        steps.CleanWorkingDirectory,
 
474
        steps.Update,
 
475
        steps.RevertRevision,
 
476
        steps.PrepareChangeLogForRevert,
 
477
        steps.EditChangeLog,
 
478
        steps.ConfirmDiff,
 
479
        steps.Build,
 
480
        steps.Commit,
 
481
        steps.ReopenBugAfterRollout,
 
482
    ]