Changeset 140491 in webkit


Ignore:
Timestamp:
Jan 22, 2013 5:36:37 PM (11 years ago)
Author:
commit-queue@webkit.org
Message:

Add functions to ChangeLog - parse bug desc/changed functions, delete/prepend entries
https://bugs.webkit.org/show_bug.cgi?id=107478

Patch by Timothy Loh <timloh@chromium.com> on 2013-01-22
Reviewed by Eric Seidel.

On the road to resolving Bug 74358, we need a few more functions in
changelog.py.
To make things easier to mock, change @staticmethods to @classmethods.

  • Scripts/webkitpy/common/checkout/changelog.py:

(ChangeLogEntry):
(ChangeLogEntry._parse_reviewer_text):
(ChangeLogEntry._split_contributor_names):
(ChangeLogEntry._parse_author_name_and_email):
(ChangeLogEntry._parse_author_text):
(ChangeLogEntry._parse_touched_functions):
(ChangeLogEntry._parse_bug_description):
(ChangeLogEntry._parse_entry):
(ChangeLogEntry.date_line):
(ChangeLogEntry.bug_description):
(ChangeLogEntry.touched_functions):
(ChangeLogEntry.touched_files_text):
(ChangeLogEntry.is_touched_files_text_clean):
(ChangeLog):
(ChangeLog.parse_latest_entry_from_file):
(ChangeLog._separate_revision_and_line):
(ChangeLog.parse_entries_from_file):
(ChangeLog.set_short_description_and_bug_url):
(ChangeLog.delete_entries):
(ChangeLog.prepend_text):

  • Scripts/webkitpy/common/checkout/changelog_unittest.py:

(test_parse_log_entries_from_changelog):
(test_latest_entry_parse):
(test_set_short_description_and_bug_url):
(test_delete_entries):
(test_prepend_text):

Location:
trunk/Tools
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/Tools/ChangeLog

    r140476 r140491  
     12013-01-22  Timothy Loh  <timloh@chromium.com>
     2
     3        Add functions to ChangeLog - parse bug desc/changed functions, delete/prepend entries
     4        https://bugs.webkit.org/show_bug.cgi?id=107478
     5
     6        Reviewed by Eric Seidel.
     7
     8        On the road to resolving Bug 74358, we need a few more functions in
     9        changelog.py.
     10        To make things easier to mock, change @staticmethods to @classmethods.
     11
     12        * Scripts/webkitpy/common/checkout/changelog.py:
     13        (ChangeLogEntry):
     14        (ChangeLogEntry._parse_reviewer_text):
     15        (ChangeLogEntry._split_contributor_names):
     16        (ChangeLogEntry._parse_author_name_and_email):
     17        (ChangeLogEntry._parse_author_text):
     18        (ChangeLogEntry._parse_touched_functions):
     19        (ChangeLogEntry._parse_bug_description):
     20        (ChangeLogEntry._parse_entry):
     21        (ChangeLogEntry.date_line):
     22        (ChangeLogEntry.bug_description):
     23        (ChangeLogEntry.touched_functions):
     24        (ChangeLogEntry.touched_files_text):
     25        (ChangeLogEntry.is_touched_files_text_clean):
     26        (ChangeLog):
     27        (ChangeLog.parse_latest_entry_from_file):
     28        (ChangeLog._separate_revision_and_line):
     29        (ChangeLog.parse_entries_from_file):
     30        (ChangeLog.set_short_description_and_bug_url):
     31        (ChangeLog.delete_entries):
     32        (ChangeLog.prepend_text):
     33        * Scripts/webkitpy/common/checkout/changelog_unittest.py:
     34        (test_parse_log_entries_from_changelog):
     35        (test_latest_entry_parse):
     36        (test_set_short_description_and_bug_url):
     37        (test_delete_entries):
     38        (test_prepend_text):
     39
    1402013-01-22  Lucas Forschler  <lforschler@apple.com>
    241
  • trunk/Tools/Scripts/webkitpy/common/checkout/changelog.py

    r135912 r140491  
    6565    # e.g. * Source/WebCore/page/EventHandler.cpp: Implement FooBarQuux.
    6666    touched_files_regexp = r'^\s*\*\s*(?P<file>[A-Za-z0-9_\-\./\\]+)\s*\:'
     67    # e.g. (ChangeLogEntry.touched_functions): Added.
     68    touched_functions_regexp = r'^\s*\((?P<function>[^)]*)\):'
    6769
    6870    # e.g. Reviewed by Darin Adler.
     
    109111        self._parse_entry()
    110112
    111     @staticmethod
    112     def _parse_reviewer_text(text):
     113    @classmethod
     114    def _parse_reviewer_text(cls, text):
    113115        match = re.search(ChangeLogEntry.reviewed_by_regexp, text, re.MULTILINE | re.IGNORECASE)
    114116        if not match:
     
    138140        return reviewer_text, reviewer_list
    139141
    140     @staticmethod
    141     def _split_contributor_names(text):
     142    @classmethod
     143    def _split_contributor_names(cls, text):
    142144        return re.split(r'\s*(?:,(?:\s+and\s+|&)?|(?:^|\s+)and\s+|&&|[/+&])\s*', text)
    143145
     
    149151        return [reviewers[0] for reviewers in list_of_reviewers if len(reviewers) == 1]
    150152
    151     @staticmethod
    152     def _parse_author_name_and_email(author_name_and_email):
     153    @classmethod
     154    def _parse_author_name_and_email(cls, author_name_and_email):
    153155        match = re.match(r'(?P<name>.+?)\s+<(?P<email>[^>]+)>', author_name_and_email)
    154156        return {'name': match.group("name"), 'email': match.group("email")}
    155157
    156     @staticmethod
    157     def _parse_author_text(text):
     158    @classmethod
     159    def _parse_author_text(cls, text):
    158160        if not text:
    159161            return []
    160         authors = ChangeLogEntry._split_contributor_names(text)
     162        authors = cls._split_contributor_names(text)
    161163        assert(authors and len(authors) >= 1)
    162         return [ChangeLogEntry._parse_author_name_and_email(author) for author in authors]
     164        return [cls._parse_author_name_and_email(author) for author in authors]
     165
     166    @classmethod
     167    def _parse_touched_functions(cls, text):
     168        result = {}
     169        cur_file = None
     170        for line in text.splitlines():
     171            file_match = re.match(cls.touched_files_regexp, line)
     172            if file_match:
     173                cur_file = file_match.group("file")
     174                result[cur_file] = []
     175            func_match = re.match(cls.touched_functions_regexp, line)
     176            if func_match and cur_file:
     177                result[cur_file].append(func_match.group("function"))
     178        return result
     179
     180    @classmethod
     181    def _parse_bug_description(cls, text):
     182        # If line 4 is a bug url, line 3 is the bug description.
     183        # It's too hard to guess in other cases, so we return None.
     184        lines = text.splitlines()
     185        if len(lines) < 4:
     186            return None
     187        for bug_url in (config_urls.bug_url_short, config_urls.bug_url_long):
     188            if re.match("^\s*" + bug_url + "$", lines[3]):
     189                return lines[2].strip()
     190        return None
    163191
    164192    def _parse_entry(self):
     
    167195            _log.warning("Creating invalid ChangeLogEntry:\n%s" % self._contents)
    168196
     197        self._date_line = match.group()
     198        self._bug_description = self._parse_bug_description(self._contents)
     199
    169200        # FIXME: group("name") does not seem to be Unicode?  Probably due to self._contents not being unicode.
    170201        self._author_text = match.group("authors") if match else None
     
    176207
    177208        self._touched_files = re.findall(self.touched_files_regexp, self._contents, re.MULTILINE)
     209        self._touched_functions = self._parse_touched_functions(self._contents)
     210
     211    def date_line(self):
     212        return self._date_line
    178213
    179214    def author_text(self):
     
    221256        return parse_bug_id_from_changelog(self._contents)
    222257
     258    def bug_description(self):
     259        return self._bug_description
     260
    223261    def touched_files(self):
    224262        return self._touched_files
    225263
     264    # Returns a dict from file name to lists of function names.
     265    def touched_functions(self):
     266        return self._touched_functions
     267
     268    def touched_files_text(self):
     269        match = re.search(self.touched_files_regexp, self._contents, re.MULTILINE)
     270        return self._contents[match.start():].lstrip("\n\r") if match else ""
     271
     272    # Determine if any text has been added to the section on touched files
     273    def is_touched_files_text_clean(self):
     274        for line in self.touched_files_text().splitlines():
     275            if re.match(self.touched_files_regexp + "$", line):
     276                continue
     277            if re.match(self.touched_functions_regexp + "$", line):
     278                continue
     279            return False
     280        return True
    226281
    227282# FIXME: Various methods on ChangeLog should move into ChangeLogEntry instead.
     
    233288    _changelog_indent = " " * 8
    234289
    235     @staticmethod
    236     def parse_latest_entry_from_file(changelog_file):
     290    @classmethod
     291    def parse_latest_entry_from_file(cls, changelog_file):
    237292        """changelog_file must be a file-like object which returns
    238293        unicode strings.  Use codecs.open or StringIO(unicode())
     
    258313    svn_blame_regexp = re.compile(r'^(\s*(?P<revision>\d+) [^ ]+)\s*(?P<line>.*?\n)')
    259314
    260     @staticmethod
    261     def _separate_revision_and_line(line):
    262         match = ChangeLog.svn_blame_regexp.match(line)
     315    @classmethod
     316    def _separate_revision_and_line(cls, line):
     317        match = cls.svn_blame_regexp.match(line)
    263318        if not match:
    264319            return None, line
    265320        return int(match.group('revision')), match.group('line')
    266321
    267     @staticmethod
    268     def parse_entries_from_file(changelog_file):
     322    @classmethod
     323    def parse_entries_from_file(cls, changelog_file):
    269324        """changelog_file must be a file-like object which returns
    270325        unicode strings.  Use codecs.open or StringIO(unicode())
     
    274329
    275330        # The first line should be a date line.
    276         revision, first_line = ChangeLog._separate_revision_and_line(changelog_file.readline())
     331        revision, first_line = cls._separate_revision_and_line(changelog_file.readline())
    277332        assert(isinstance(first_line, unicode))
    278         if not date_line_regexp.match(ChangeLog.svn_blame_regexp.sub('', first_line)):
     333        if not date_line_regexp.match(cls.svn_blame_regexp.sub('', first_line)):
    279334            raise StopIteration
    280335
     
    283338        for line in changelog_file:
    284339            if revisions_in_entry:
    285                 revision, line = ChangeLog._separate_revision_and_line(line)
     340                revision, line = cls._separate_revision_and_line(line)
    286341
    287342            if rolled_over_regexp.match(line):
     
    378433            if line != bug_boilerplate:
    379434                print line,
     435
     436    def delete_entries(self, num_entries):
     437        date_line_regexp = re.compile(ChangeLogEntry.date_line_regexp)
     438        rolled_over_regexp = re.compile(ChangeLogEntry.rolled_over_regexp)
     439        entries = 0
     440        for line in fileinput.FileInput(self.path, inplace=1):
     441            if date_line_regexp.match(line):
     442                entries += 1
     443            elif rolled_over_regexp.match(line):
     444                entries = num_entries + 1
     445            if entries > num_entries:
     446                print line,
     447
     448    def prepend_text(self, text):
     449        data = codecs.open(self.path, "r", "utf-8").read()
     450        codecs.open(self.path, "w", "utf-8").write(text + data)
  • trunk/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py

    r134636 r140491  
    237237        parsed_entries = list(ChangeLog.parse_entries_from_file(changelog_file))
    238238        self.assertEqual(len(parsed_entries), 9)
     239        self.assertEqual(parsed_entries[0].date_line(), u"2009-08-17  Tor Arne Vestb\xf8  <vestbo@webkit.org>")
    239240        self.assertEqual(parsed_entries[0].reviewer_text(), "David Levin")
     241        self.assertEqual(parsed_entries[0].is_touched_files_text_clean(), False)
    240242        self.assertEqual(parsed_entries[1].author_email(), "ddkilzer@apple.com")
     243        self.assertEqual(parsed_entries[1].touched_files_text(), "        * Scripts/bugzilla-tool:\n        * Scripts/modules/scm.py:\n")
     244        self.assertEqual(parsed_entries[1].is_touched_files_text_clean(), True)
    241245        self.assertEqual(parsed_entries[2].reviewer_text(), "Mark Rowe")
    242246        self.assertEqual(parsed_entries[2].touched_files(), ["DumpRenderTree/mac/DumpRenderTreeWindow.mm"])
     247        self.assertEqual(parsed_entries[2].touched_functions(), {"DumpRenderTree/mac/DumpRenderTreeWindow.mm": ["-[DumpRenderTreeWindow close]"]})
     248        self.assertEqual(parsed_entries[2].is_touched_files_text_clean(), False)
    243249        self.assertEqual(parsed_entries[3].author_name(), "Benjamin Poulain")
    244250        self.assertEqual(parsed_entries[3].touched_files(), ["platform/cf/KURLCFNet.cpp", "platform/mac/KURLMac.mm",
    245251            "WebCoreSupport/ChromeClientEfl.cpp", "ewk/ewk_private.h", "ewk/ewk_view.cpp"])
     252        self.assertEqual(parsed_entries[3].touched_functions(), {"platform/cf/KURLCFNet.cpp": ["WebCore::createCFURLFromBuffer", "WebCore::KURL::createCFURL"],
     253            "platform/mac/KURLMac.mm": ["WebCore::KURL::operator NSURL *", "WebCore::KURL::createCFURL"],
     254            "WebCoreSupport/ChromeClientEfl.cpp": ["WebCore::ChromeClientEfl::closeWindowSoon"], "ewk/ewk_private.h": [], "ewk/ewk_view.cpp": []})
     255        self.assertEqual(parsed_entries[3].bug_description(), "[Mac] ResourceRequest's nsURLRequest() does not differentiate null and empty URLs with CFNetwork")
    246256        self.assertEqual(parsed_entries[4].reviewer_text(), "David Hyatt")
     257        self.assertEqual(parsed_entries[4].bug_description(), None)
    247258        self.assertEqual(parsed_entries[5].reviewer_text(), "Adam Roben")
    248259        self.assertEqual(parsed_entries[6].reviewer_text(), "Tony Chang")
     
    463474        self.assertEqual(latest_entry.author_email(), "pkasting@google.com")
    464475        self.assertEqual(latest_entry.reviewer_text(), u"Tor Arne Vestb\xf8")
    465         self.assertEqual(latest_entry.touched_files(), ["DumpRenderTree/win/DumpRenderTree.vcproj", "DumpRenderTree/win/ImageDiff.vcproj", "DumpRenderTree/win/TestNetscapePlugin/TestNetscapePlugin.vcproj"])
     476        touched_files = ["DumpRenderTree/win/DumpRenderTree.vcproj", "DumpRenderTree/win/ImageDiff.vcproj", "DumpRenderTree/win/TestNetscapePlugin/TestNetscapePlugin.vcproj"]
     477        self.assertEqual(latest_entry.touched_files(), touched_files)
     478        self.assertEqual(latest_entry.touched_functions(), dict((f, []) for f in touched_files))
    466479
    467480        self.assertTrue(latest_entry.reviewer())  # Make sure that our UTF8-based lookup of Tor works.
     
    584597        os.remove(changelog_path)
    585598        self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
     599
     600    def test_delete_entries(self):
     601        changelog_path = self._write_tmp_file_with_contents(self._example_changelog.encode("utf-8"))
     602        ChangeLog(changelog_path).delete_entries(8)
     603        actual_contents = self._read_file_contents(changelog_path, "utf-8")
     604        expected_contents = """2011-10-11  Antti Koivisto  <antti@apple.com>
     605
     606       Resolve regular and visited link style in a single pass
     607       https://bugs.webkit.org/show_bug.cgi?id=69838
     608
     609       Reviewed by Darin Adler
     610
     611       We can simplify and speed up selector matching by removing the recursive matching done
     612       to generate the style for the :visited pseudo selector. Both regular and visited link style
     613       can be generated in a single pass through the style selector.
     614
     615== Rolled over to ChangeLog-2009-06-16 ==
     616"""
     617        self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
     618
     619        ChangeLog(changelog_path).delete_entries(2)
     620        actual_contents = self._read_file_contents(changelog_path, "utf-8")
     621        expected_contents = "== Rolled over to ChangeLog-2009-06-16 ==\n"
     622        self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
     623
     624        os.remove(changelog_path)
     625
     626    def test_prepend_text(self):
     627        changelog_path = self._write_tmp_file_with_contents(self._example_changelog.encode("utf-8"))
     628        ChangeLog(changelog_path).prepend_text(self._example_entry + "\n")
     629        actual_contents = self._read_file_contents(changelog_path, "utf-8")
     630        expected_contents = self._example_entry + "\n" + self._example_changelog
     631        self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
     632        os.remove(changelog_path)
Note: See TracChangeset for help on using the changeset viewer.