def _ensure_bug_url(self, state): if not state.get("bug_id"): return bug_id = state.get("bug_id") changelogs = self.cached_lookup(state, "changelogs") for changelog_path in changelogs: changelog = ChangeLog(changelog_path) if not changelog.latest_entry().bug_id(): changelog.set_short_description_and_bug_url( self.cached_lookup(state, "bug_title"), self._tool.bugs.bug_url_for_bug_id(bug_id))
def _assert_message_for_revert_output(self, args, expected_entry): changelog_contents = u"%s\n%s" % (changelog_unittest.ChangeLogTest._new_entry_boilerplate, changelog_unittest.ChangeLogTest._example_changelog) changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8")) changelog = ChangeLog(changelog_path) changelog.update_with_unreviewed_message(PrepareChangeLogForRevert._message_for_revert(*args)) actual_entry = changelog.latest_entry() os.remove(changelog_path) self.assertEqual(actual_entry.contents(), expected_entry) self.assertEqual(actual_entry.reviewer_text(), None) # These checks could be removed to allow this to work on other entries: self.assertEqual(actual_entry.author_name(), "Eric Seidel") self.assertEqual(actual_entry.author_email(), "*****@*****.**")
def run(self, state): # FIXME: For now we disable this check when a user is driving the script # this check is too draconian (and too poorly tested) to foist upon users. if not self._options.non_interactive: return for changelog_path in self.cached_lookup(state, "changelogs"): changelog_entry = ChangeLog(changelog_path).latest_entry() if self._has_valid_reviewer(changelog_entry): continue reviewer_text = changelog_entry.reviewer_text() if reviewer_text: log("%s found in %s does not appear to be a valid reviewer according to committers.py." % (reviewer_text, changelog_path)) error('%s neither lists a valid reviewer nor contains the string "Unreviewed" or "Rubber stamp" (case insensitive).' % changelog_path)
def run(self, state): # FIXME: For now we disable this check when a user is driving the script # this check is too draconian (and too poorly tested) to foist upon users. if not self._options.non_interactive: return # FIXME: We should figure out how to handle the current working # directory issue more globally. os.chdir(self._tool.scm().checkout_root) for changelog_path in self._tool.checkout().modified_changelogs(self._options.git_commit, self._options.squash): changelog_entry = ChangeLog(changelog_path).latest_entry() if self._has_valid_reviewer(changelog_entry): continue reviewer_text = changelog_entry.reviewer_text() if reviewer_text: log("%s found in %s does not appear to be a valid reviewer according to committers.py." % (reviewer_text, changelog_path)) error('%s neither lists a valid reviewer nor contains the string "Unreviewed" or "Rubber stamp" (case insensitive).' % changelog_path)
def commit_message_for_this_commit(self, git_commit, changed_files=None): changelog_paths = self.modified_changelogs(git_commit, changed_files) if not len(changelog_paths): raise ScriptError(message="Found no modified ChangeLogs, cannot create a commit message.\n" "All changes require a ChangeLog. See:\n %s" % urls.contribution_guidelines) changelog_messages = [] for changelog_path in changelog_paths: log("Parsing ChangeLog: %s" % changelog_path) changelog_entry = ChangeLog(changelog_path).latest_entry() if not changelog_entry: raise ScriptError(message="Failed to parse ChangeLog: %s" % os.path.abspath(changelog_path)) changelog_messages.append(changelog_entry.contents()) # FIXME: We should sort and label the ChangeLog messages like commit-log-editor does. return CommitMessage("".join(changelog_messages).splitlines())
def _latest_entry_for_changelog_at_revision(self, changelog_path, revision): changelog_contents = self._scm.contents_at_revision(changelog_path, revision) # contents_at_revision returns a byte array (str()), but we know # that ChangeLog files are utf-8. parse_latest_entry_from_file # expects a file-like object which vends unicode(), so we decode here. changelog_file = StringIO.StringIO(changelog_contents.decode("utf-8")) return ChangeLog.parse_latest_entry_from_file(changelog_file)
def _latest_entry_for_changelog_at_revision(self, changelog_path, revision): changelog_contents = self._scm.contents_at_revision(changelog_path, revision) # contents_at_revision returns a byte array (str()), but we know # that ChangeLog files are utf-8. parse_latest_entry_from_file # expects a file-like object which vends unicode(), so we decode here. # Old revisions of Sources/WebKit/wx/ChangeLog have some invalid utf8 characters. changelog_file = StringIO.StringIO(changelog_contents.decode("utf-8", "ignore")) return ChangeLog.parse_latest_entry_from_file(changelog_file)
def analyze(self): for path in self._changelog_paths: self._set_filename(self._filesystem.relpath(path, self._scm.checkout_root)) with self._filesystem.open_text_file_for_reading(path) as changelog: self._print_status('Parsing entries...') number_of_parsed_entries = self._analyze_entries(ChangeLog.parse_entries_from_file(changelog), path) self._print_status('Done (%d entries)' % number_of_parsed_entries) print self._summary['contributors'] = len(self._contributors_statistics) self._summary['contributors_with_reviews'] = sum([1 for contributor in self._contributors_statistics.values() if contributor['reviews']['total']]) self._summary['contributors_without_reviews'] = self._summary['contributors'] - self._summary['contributors_with_reviews']
def test_parse_log_entries_from_annotated_file(self): # Note that there are trailing spaces on some of the lines intentionally. changelog_file = StringIO( u"100000 [email protected] 2011-11-11 Csaba Osztrogon\u00e1c <*****@*****.**>\n" u"100000 [email protected]\n" u"100000 [email protected] 100,000 !!!\n" u"100000 [email protected] \n" u"100000 [email protected] Reviewed by Zoltan Herczeg.\n" u"100000 [email protected] \n" u"100000 [email protected] * ChangeLog: Point out revision 100,000.\n" u"100000 [email protected] \n" u"93798 [email protected] 2011-08-25 Alexey Proskuryakov <*****@*****.**>\n" u"93798 [email protected] \n" u"93798 [email protected] Fix build when GCC 4.2 is not installed.\n" u"93798 [email protected] \n" u"93798 [email protected] * gtest/xcode/Config/CompilerVersion.xcconfig: Copied from Source/WebCore/Configurations/CompilerVersion.xcconfig.\n" u"93798 [email protected] * gtest/xcode/Config/General.xcconfig:\n" u"93798 [email protected] Use the same compiler version as other projects do.\n" u"93798 [email protected]\n" u"99491 [email protected] 2011-11-03 Andreas Kling <*****@*****.**>\n" u"99491 [email protected] \n" u"99190 [email protected] Unreviewed build fix, sigh.\n" u"99190 [email protected] \n" u"99190 [email protected] * css/CSSFontFaceRule.h:\n" u"99190 [email protected] * css/CSSMutableStyleDeclaration.h:\n" u"99190 [email protected]\n" u"99190 [email protected] 2011-11-03 Andreas Kling <*****@*****.**>\n" u"99190 [email protected] \n" u"99187 [email protected] Unreviewed build fix, out-of-line StyleSheet::parentStyleSheet()\n" u"99187 [email protected] again since there's a cycle in the includes between CSSRule/StyleSheet.\n" u"99187 [email protected] \n" u"99187 [email protected] * css/StyleSheet.cpp:\n" u"99187 [email protected] (WebCore::StyleSheet::parentStyleSheet):\n" u"99187 [email protected] * css/StyleSheet.h:\n" u"99187 [email protected] \n") parsed_entries = list( ChangeLog.parse_entries_from_file(changelog_file)) self.assertEqual(parsed_entries[0].revision(), 100000) self.assertEqual(parsed_entries[0].reviewer_text(), "Zoltan Herczeg") self.assertEqual(parsed_entries[0].author_name(), u"Csaba Osztrogon\u00e1c") self.assertEqual(parsed_entries[0].author_email(), "*****@*****.**") self.assertEqual(parsed_entries[1].revision(), 93798) self.assertEqual(parsed_entries[1].author_name(), "Alexey Proskuryakov") self.assertEqual(parsed_entries[2].revision(), 99190) self.assertEqual(parsed_entries[2].author_name(), "Andreas Kling") self.assertEqual(parsed_entries[3].revision(), 99187) self.assertEqual(parsed_entries[3].author_name(), "Andreas Kling")
def run(self, state): # This could move to prepare-ChangeLog by adding a --revert= option. self._tool.executive.run_and_throw_if_fail( self._tool.deprecated_port().prepare_changelog_command(), cwd=self._tool.scm().checkout_root) changelog_paths = self._tool.checkout().modified_changelogs( git_commit=None) bug_url = self._tool.bugs.bug_url_for_bug_id( state["bug_id"]) if state["bug_id"] else None message = self._message_for_revert(state["revision_list"], state["reason"], bug_url) for changelog_path in changelog_paths: # FIXME: Seems we should prepare the message outside of changelogs.py and then just pass in # text that we want to use to replace the reviewed by line. ChangeLog(changelog_path).update_with_unreviewed_message(message)
def analyze(self): for path in self._changelog_paths: self._set_filename( self._filesystem.relpath(path, self._scm.checkout_root)) with self._filesystem.open_text_file_for_reading( path) as changelog: self._print_status('Parsing entries...') number_of_parsed_entries = self._analyze_entries( ChangeLog.parse_entries_from_file(changelog), path) self._print_status('Done (%d entries)' % number_of_parsed_entries) print self._summary['contributors'] = len(self._contributors_statistics) self._summary['contributors_with_reviews'] = sum([ 1 for contributor in self._contributors_statistics.values() if contributor['reviews']['total'] ]) self._summary['contributors_without_reviews'] = self._summary[ 'contributors'] - self._summary['contributors_with_reviews']
def run(self, state): bug_id = state.get("bug_id") if not bug_id and state.get("patch"): bug_id = state.get("patch").bug_id() reviewer = self._options.reviewer if not reviewer: if not bug_id: log("No bug id provided and --reviewer= not provided. Not updating ChangeLogs with reviewer.") return reviewer = self._guess_reviewer_from_bug(bug_id) if not reviewer: log("Failed to guess reviewer from bug %s and --reviewer= not provided. Not updating ChangeLogs with reviewer." % bug_id) return os.chdir(self._tool.scm().checkout_root) for changelog_path in self._tool.checkout().modified_changelogs(self._options.git_commit, self._options.squash): ChangeLog(changelog_path).set_reviewer(reviewer)
def _ensure_bug_url(self, state): if not state.get("bug_id"): return bug_id = state.get("bug_id") changelogs = self.cached_lookup(state, "changelogs") for changelog_path in changelogs: changelog = ChangeLog(changelog_path, self._tool.filesystem) if not changelog.latest_entry(): _log.error('Invalid ChangeLog at: {}'.format(changelog_path)) sys.exit(1) if not changelog.latest_entry().bug_id(): changelog.set_short_description_and_bug_url( self.cached_lookup(state, "bug_title"), self._tool.bugs.bug_url_for_bug_id(bug_id))
def test_latest_entry_parse(self): changelog_contents = u"%s\n%s" % (self._example_entry, self._example_changelog) changelog_file = StringIO(changelog_contents) latest_entry = ChangeLog.parse_latest_entry_from_file(changelog_file) self.assertEqual(latest_entry.contents(), self._example_entry) self.assertEqual(latest_entry.author_name(), "Peter Kasting") self.assertEqual(latest_entry.author_email(), "*****@*****.**") self.assertEqual(latest_entry.reviewer_text(), u"Fr\u00e9d\u00e9ric Wang") touched_files = [ "DumpRenderTree/win/DumpRenderTree.vcproj", "DumpRenderTree/win/ImageDiff.vcproj", "DumpRenderTree/win/TestNetscapePlugin/TestNetscapePlugin.vcproj" ] self.assertEqual(latest_entry.touched_files(), touched_files) self.assertEqual(latest_entry.touched_functions(), dict((f, []) for f in touched_files)) self.assertTrue(latest_entry.reviewer() ) # Make sure that our UTF8-based lookup of Tor works.
def run(self, state): bug_id = state.get("bug_id") if not bug_id and state.get("patch"): bug_id = state.get("patch").bug_id() reviewer = self._options.reviewer if not reviewer: if not bug_id: log("No bug id provided and --reviewer= not provided. Not updating ChangeLogs with reviewer.") return reviewer = self._guess_reviewer_from_bug(bug_id) if not reviewer: log("Failed to guess reviewer from bug %s and --reviewer= not provided. Not updating ChangeLogs with reviewer." % bug_id) return # cached_lookup("changelogs") is always absolute paths. for changelog_path in self.cached_lookup(state, "changelogs"): ChangeLog(changelog_path).set_reviewer(reviewer) # Tell the world that we just changed something on disk so that the cached diff is invalidated. self.did_modify_checkout(state)
def _resolve_existing_entry(self, changelog_path): # When this is called, the top entry in the ChangeLog was just created # by prepare-ChangeLog, as an clean updated version of the one below it. with self._tool.filesystem.open_text_file_for_reading(changelog_path) as changelog_file: entries_gen = ChangeLog.parse_entries_from_file(changelog_file) entries = zip(entries_gen, range(2)) if not len(entries): raise Exception("Expected to find at least two ChangeLog entries in %s but found none." % changelog_path) if len(entries) == 1: # If we get here, it probably means we've just rolled over to a # new CL file, so we don't have anything to resolve. return (new_entry, _), (old_entry, _) = entries final_entry = self._merge_entries(old_entry, new_entry) changelog = ChangeLog(changelog_path, self._tool.filesystem) changelog.delete_entries(2) changelog.prepend_text(final_entry)
def _latest_entry_for_changelog_at_revision(self, changelog_path, revision): changelog_contents = self._scm.contents_at_revision(changelog_path, revision) return ChangeLog.parse_latest_entry_from_file(StringIO.StringIO(changelog_contents))
def test_parse_log_entries_from_changelog(self): changelog_file = StringIO(self._example_changelog) parsed_entries = list( ChangeLog.parse_entries_from_file(changelog_file)) self.assertEqual(len(parsed_entries), 9) self.assertEqual( parsed_entries[0].date_line(), u"2009-08-17 Fr\u00e9d\u00e9ric Wang <*****@*****.**>") self.assertEqual(parsed_entries[0].date(), "2009-08-17") self.assertEqual(parsed_entries[0].reviewer_text(), "David Levin") self.assertEqual(parsed_entries[0].is_touched_files_text_clean(), False) self.assertIsNone(parsed_entries[0].bug_description()) self.assertEqual(parsed_entries[1].date_line(), "2009-08-16 David Kilzer <*****@*****.**>") self.assertEqual(parsed_entries[1].date(), "2009-08-16") self.assertEqual(parsed_entries[1].author_email(), "*****@*****.**") self.assertEqual(parsed_entries[1].bug_description(), "Backed out r47343 which was mistakenly committed") self.assertEqual( parsed_entries[1].touched_files_text(), " * Scripts/bugzilla-tool:\n * Scripts/modules/scm.py:\n" ) self.assertEqual(parsed_entries[1].is_touched_files_text_clean(), True) self.assertEqual(parsed_entries[2].reviewer_text(), "Mark Rowe") self.assertEqual(parsed_entries[2].touched_files(), ["DumpRenderTree/mac/DumpRenderTreeWindow.mm"]) self.assertEqual( parsed_entries[2].touched_functions(), { "DumpRenderTree/mac/DumpRenderTreeWindow.mm": ["-[DumpRenderTreeWindow close]"] }) self.assertEqual(parsed_entries[2].is_touched_files_text_clean(), False) self.assertIsNone(parsed_entries[2].bug_description()) self.assertEqual(parsed_entries[3].author_name(), "Benjamin Poulain") self.assertEqual(parsed_entries[3].touched_files(), [ "platform/cf/KURLCFNet.cpp", "platform/mac/KURLMac.mm", "WebCoreSupport/ChromeClientEfl.cpp", "ewk/ewk_private.h", "ewk/ewk_view.cpp" ]) self.assertEqual( parsed_entries[3].touched_functions(), { "platform/cf/KURLCFNet.cpp": [ "WebCore::createCFURLFromBuffer", "WebCore::KURL::createCFURL" ], "platform/mac/KURLMac.mm": [ "WebCore::KURL::operator NSURL *", "WebCore::KURL::createCFURL" ], "WebCoreSupport/ChromeClientEfl.cpp": ["WebCore::ChromeClientEfl::closeWindowSoon"], "ewk/ewk_private.h": [], "ewk/ewk_view.cpp": [] }) self.assertEqual( parsed_entries[3].bug_description(), "[Mac] ResourceRequest's nsURLRequest() does not differentiate null and empty URLs with CFNetwork" ) self.assertEqual(parsed_entries[4].reviewer_text(), "David Hyatt") self.assertIsNone(parsed_entries[4].bug_description()) self.assertEqual(parsed_entries[5].reviewer_text(), "Adam Roben") self.assertIsNone(parsed_entries[5].bug_description()) self.assertEqual(parsed_entries[6].reviewer_text(), "Tony Chang") self.assertIsNone(parsed_entries[6].bug_description()) self.assertIsNone(parsed_entries[7].reviewer_text()) self.assertEqual(parsed_entries[7].bug_description(), "Unreviewed warning fix.") self.assertEqual(parsed_entries[8].reviewer_text(), 'Darin Adler') self.assertEqual( parsed_entries[8].bug_description(), 'Resolve regular and visited link style in a single pass')
def run(self, state): self._tool.executive.run_and_throw_if_fail(self._tool.port().prepare_changelog_command()) changelog_paths = self._tool.checkout().modified_changelogs(git_commit=None) for changelog_path in changelog_paths: ChangeLog(changelog_path).update_with_unreviewed_message("Rolled DEPS.\n\n")