commit-queue should check for oops in changelog entries
[WebKit-https.git] / Tools / Scripts / webkitpy / tool / steps / validatechangelogs.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 #     * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 #     * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
12 # distribution.
13 #     * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 import logging
30 import sys
31
32 from optparse import make_option
33 from webkitpy.tool.steps.abstractstep import AbstractStep
34 from webkitpy.tool.steps.options import Options
35 from webkitpy.common.checkout.diff_parser import DiffParser
36
37 _log = logging.getLogger(__name__)
38
39
40 # This is closely related to the ValidateReviewer step and the CommitterValidator class.
41 # We may want to unify more of this code in one place.
42 class ValidateChangeLogs(AbstractStep):
43     @classmethod
44     def options(cls):
45         return AbstractStep.options() + [
46             make_option("--check-oops", action="store_true", default=False, help="Check there are no OOPS left in change log"),
47             Options.non_interactive,
48         ]
49
50     def _check_changelog_diff(self, diff_file):
51         # Each line is a tuple, the first value is the deleted line number
52         # Date, reviewer, bug title, bug url, and empty lines could all be
53         # identical in the most recent entries.  If the diff starts any
54         # later than that, assume that the entry is wrong.
55         if diff_file.lines[0][0] < 8:
56             return True
57         if self._options.non_interactive:
58             return False
59
60         _log.info("The diff to %s looks wrong. Are you sure your ChangeLog entry is at the top of the file?" % (diff_file.filename))
61         # FIXME: Do we need to make the file path absolute?
62         self._tool.scm().diff_for_file(diff_file.filename)
63         if self._tool.user.confirm("OK to continue?", default='n'):
64             return True
65         return False
66
67     def _changelog_contains_oops(self, diff_file):
68         for diff_line in diff_file.lines:
69             if 'OOPS!' in diff_line[2]:
70                 return True
71         return False
72
73     def run(self, state):
74         changed_files = self.cached_lookup(state, "changed_files")
75         for filename in changed_files:
76             if not self._tool.checkout().is_path_to_changelog(filename):
77                 continue
78             # Diff ChangeLogs directly because svn-create-patch will move
79             # ChangeLog entries to the # top automatically, defeating our
80             # validation here.
81             # FIXME: Should we diff all the ChangeLogs at once?
82             diff = self._tool.scm().diff_for_file(filename)
83             parsed_diff = DiffParser(diff.splitlines())
84             for filename, diff_file in parsed_diff.files.items():
85                 if not self._tool.checkout().is_path_to_changelog(diff_file.filename):
86                     continue
87                 if not self._check_changelog_diff(diff_file):
88                     _log.error("ChangeLog entry in %s is not at the top of the file." % diff_file.filename)
89                     sys.exit(1)
90                 if self._options.check_oops and self._changelog_contains_oops(diff_file):
91                     _log.error("ChangeLog entry in %s contains OOPS!." % diff_file.filename)
92                     sys.exit(1)