Home | History | Annotate | Download | only in webkitpy
      1 # Copyright (C) 2009, 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 # WebKit's Python module for parsing and modifying ChangeLog files
     30 
     31 import fileinput # inplace file editing for set_reviewer_in_changelog
     32 import re
     33 import textwrap
     34 
     35 
     36 def view_source_url(revision_number):
     37     # FIMXE: This doesn't really belong in this file, but we don't have a
     38     # better home for it yet.
     39     # Maybe eventually a webkit_config.py?
     40     return "http://trac.webkit.org/changeset/%s" % revision_number
     41 
     42 
     43 class ChangeLog:
     44 
     45     def __init__(self, path):
     46         self.path = path
     47 
     48     _changelog_indent = " " * 8
     49 
     50     # e.g. 2009-06-03  Eric Seidel  <eric (at] webkit.org>
     51     date_line_regexp = re.compile('^(\d{4}-\d{2}-\d{2})' # Consume the date.
     52                                 + '\s+(.+)\s+' # Consume the name.
     53                                 + '<([^<>]+)>$') # And the email address.
     54 
     55     @staticmethod
     56     def _parse_latest_entry_from_file(changelog_file):
     57         entry_lines = []
     58         # The first line should be a date line.
     59         first_line = changelog_file.readline()
     60         if not ChangeLog.date_line_regexp.match(first_line):
     61             return None
     62         entry_lines.append(first_line)
     63 
     64         for line in changelog_file:
     65             # If we've hit the next entry, return.
     66             if ChangeLog.date_line_regexp.match(line):
     67                 # Remove the extra newline at the end
     68                 return ''.join(entry_lines[:-1])
     69             entry_lines.append(line)
     70         return None # We never found a date line!
     71 
     72     def latest_entry(self):
     73         changelog_file = open(self.path)
     74         try:
     75             return self._parse_latest_entry_from_file(changelog_file)
     76         finally:
     77             changelog_file.close()
     78 
     79     # _wrap_line and _wrap_lines exist to work around
     80     # http://bugs.python.org/issue1859
     81 
     82     def _wrap_line(self, line):
     83         return textwrap.fill(line,
     84                              width=70,
     85                              initial_indent=self._changelog_indent,
     86                              # Don't break urls which may be longer than width.
     87                              break_long_words=False,
     88                              subsequent_indent=self._changelog_indent)
     89 
     90     # Workaround as suggested by guido in
     91     # http://bugs.python.org/issue1859#msg60040
     92 
     93     def _wrap_lines(self, message):
     94         lines = [self._wrap_line(line) for line in message.splitlines()]
     95         return "\n".join(lines)
     96 
     97     # This probably does not belong in changelogs.py
     98     def _message_for_revert(self, revision, reason, bug_url):
     99         message = "No review, rolling out r%s.\n" % revision
    100         message += "%s\n" % view_source_url(revision)
    101         if bug_url:
    102             message += "%s\n" % bug_url
    103         # Add an extra new line after the rollout links, before any reason.
    104         message += "\n"
    105         if reason:
    106             message += "%s\n\n" % reason
    107         return self._wrap_lines(message)
    108 
    109     def update_for_revert(self, revision, reason, bug_url=None):
    110         reviewed_by_regexp = re.compile(
    111                 "%sReviewed by NOBODY \(OOPS!\)\." % self._changelog_indent)
    112         removing_boilerplate = False
    113         # inplace=1 creates a backup file and re-directs stdout to the file
    114         for line in fileinput.FileInput(self.path, inplace=1):
    115             if reviewed_by_regexp.search(line):
    116                 message_lines = self._message_for_revert(revision,
    117                                                          reason,
    118                                                          bug_url)
    119                 print reviewed_by_regexp.sub(message_lines, line),
    120                 # Remove all the ChangeLog boilerplate between the Reviewed by
    121                 # line and the first changed file.
    122                 removing_boilerplate = True
    123             elif removing_boilerplate:
    124                 if line.find('*') >= 0: # each changed file is preceded by a *
    125                     removing_boilerplate = False
    126 
    127             if not removing_boilerplate:
    128                 print line,
    129 
    130     def set_reviewer(self, reviewer):
    131         # inplace=1 creates a backup file and re-directs stdout to the file
    132         for line in fileinput.FileInput(self.path, inplace=1):
    133             # Trailing comma suppresses printing newline
    134             print line.replace("NOBODY (OOPS!)", reviewer.encode("utf-8")),
    135