From 92e21ee64299ee603e5922df6dee9398b337ff0e Mon Sep 17 00:00:00 2001 From: Allan Wirth Date: Fri, 7 Apr 2023 11:30:19 +0900 Subject: [PATCH] Add ignored commits feature --- docs/options.md | 16 +++++++ .../plugin.py | 1 + .../util.py | 41 ++++++++++++++--- .../basic_project/mkdocs_ignored_commits.yml | 8 ++++ tests/test_builds.py | 45 +++++++++++++++++-- tests/test_parse_git_ignore_revs.py | 16 +++++++ 6 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/basic_project/mkdocs_ignored_commits.yml create mode 100644 tests/test_parse_git_ignore_revs.py diff --git a/docs/options.md b/docs/options.md index 98c9f99..1c61030 100644 --- a/docs/options.md +++ b/docs/options.md @@ -17,6 +17,7 @@ You can customize the plugin by setting options in `mkdocs.yml`. For example: - index.md enabled: true strict: true + ignored_commits_file: .git-blame-ignore-revs ``` ## `type` @@ -147,3 +148,18 @@ Default is `true`. When enabled, the logs will show warnings when something is w - git-revision-date-localized: strict: true ``` + +## `ignored_commits_file` + +Default is `None`. When specified, contains a file that contains a list of commit hashes to ignore +when determining the most recent updated time. The format of the file is the same as the format of +git `blame.ignoreRevsFile`. This can be useful to ignore formatting updates or other mass changes to the documents. + + +=== ":octicons-file-code-16: mkdocs.yml" + + ```yaml + plugins: + - git-revision-date-localized: + ignored_commits_file: .git-blame-ignore-revs + ``` diff --git a/mkdocs_git_revision_date_localized_plugin/plugin.py b/mkdocs_git_revision_date_localized_plugin/plugin.py index d93f3df..4cdd7ca 100644 --- a/mkdocs_git_revision_date_localized_plugin/plugin.py +++ b/mkdocs_git_revision_date_localized_plugin/plugin.py @@ -44,6 +44,7 @@ class GitRevisionDateLocalizedPlugin(BasePlugin): ("enable_creation_date", config_options.Type(bool, default=False)), ("enabled", config_options.Type(bool, default=True)), ("strict", config_options.Type(bool, default=True)), + ("ignored_commits_file", config_options.Type(str, default=None)), ) def on_config(self, config: config_options.Config, **kwargs) -> Dict[str, Any]: diff --git a/mkdocs_git_revision_date_localized_plugin/util.py b/mkdocs_git_revision_date_localized_plugin/util.py index 370a3d3..7cf0aeb 100644 --- a/mkdocs_git_revision_date_localized_plugin/util.py +++ b/mkdocs_git_revision_date_localized_plugin/util.py @@ -16,7 +16,7 @@ NoSuchPathError, ) -from typing import Any, Dict +from typing import Any, Dict, List logger = logging.getLogger("mkdocs.plugins") @@ -32,6 +32,9 @@ def __init__(self, config={}): self.config = config self.repo_cache = {} + ignore_commits_file = self.config.get("ignored_commits_file") + self.ignored_commits = self.parse_git_ignore_revs(ignore_commits_file) if ignore_commits_file else [] + def _get_repo(self, path: str) -> Git: if not os.path.isdir(path): path = os.path.dirname(path) @@ -117,6 +120,7 @@ def get_git_commit_timestamp( realpath = os.path.realpath(path) git = self._get_repo(realpath) + # Ignored commits are only considered for the most recent update, not for creation if is_first_commit: # diff_filter="A" will select the commit that created the file commit_timestamp = git.log( @@ -128,10 +132,21 @@ def get_git_commit_timestamp( if commit_timestamp != "": commit_timestamp = commit_timestamp.split()[-1] else: - # Latest commit touching a specific file - commit_timestamp = git.log( - realpath, date="unix", format="%at", n=1, no_show_signature=True - ) + # Retrieve the history for the file in the format + # The maximum number of commits we will ever need to examine is 1 more than the number of ignored commits. + lines = git.log( + realpath, date="unix", format="%H %at", n=len(self.ignored_commits)+1, no_show_signature=True, + ).split("\n") + # process the commits for the file in reverse-chronological order. Ignore any commit that is on the + # ignored list. If the line is empty, we've reached the end and need to use the fallback behavior + for line in lines: + if not line: + commit_timestamp = "" + break + commit, commit_timestamp = line.split(" ") + if not any(commit.startswith(x) for x in self.ignored_commits): + break + except (InvalidGitRepositoryError, NoSuchPathError) as err: if self.config.get('fallback_to_build_date'): log( @@ -224,3 +239,19 @@ def add_spans(date_formats: Dict[str, str]) -> Dict[str, str]: % (date_type, date_string) ) return date_formats + + @staticmethod + def parse_git_ignore_revs(filename: str) -> List[str]: + """ + Parses a file that is the same format as git's blame.ignoreRevsFile and return the list of commit hashes. + + Whitespace, blanklines and comments starting with # are all ignored. + """ + result = [] + with open(filename, "rt") as f: + for line in f: + line = line.split("#", 1)[0].strip() + if not line: + continue + result.append(line) + return result diff --git a/tests/fixtures/basic_project/mkdocs_ignored_commits.yml b/tests/fixtures/basic_project/mkdocs_ignored_commits.yml new file mode 100644 index 0000000..6378e33 --- /dev/null +++ b/tests/fixtures/basic_project/mkdocs_ignored_commits.yml @@ -0,0 +1,8 @@ +site_name: test gitrevisiondatelocalized_plugin +use_directory_urls: true + +plugins: + - search + - git-revision-date-localized: + enable_creation_date: True + ignored_commits_file: ignored-commits.txt diff --git a/tests/test_builds.py b/tests/test_builds.py index 5af31c6..08fb25d 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -158,10 +158,15 @@ def setup_commit_history(testproject_path): repo.git.commit(message="add homepage", author=author, date="1500854705") # Mon Jul 24 2017 00:05:05 GMT+0000 file_name = os.path.join(testproject_path, "docs/page_with_tag.md") + with open(file_name, "a") as the_file: + the_file.write("test\n") + repo.git.add("docs/page_with_tag.md") + repo.git.commit(message="update homepage #1", author=author, date="1525475836") # Fri May 04 2018 23:17:16 GMT+0000 + with open(file_name, "a") as the_file: the_file.write("awa\n") repo.git.add("docs/page_with_tag.md") - repo.git.commit(message="update homepage", author=author, date="1642911026") # Sun Jan 23 2022 04:10:26 GMT+0000 + repo.git.commit(message="update homepage #2", author=author, date="1642911026") # Sun Jan 23 2022 04:10:26 GMT+0000 if os.path.exists("docs/page_with_renamed.md"): bf_file_name = os.path.join(testproject_path, "docs/page_with_renamed.md") @@ -382,10 +387,10 @@ def test_tags_are_replaced(tmp_path, mkdocs_file): pytest.skip("Not necessary to test the JS library") # Make sure count_commits() works - # We created 10 commits in setup_commit_history() + # We created 11 commits in setup_commit_history() with working_directory(testproject_path): u = Util() - assert commit_count(u._get_repo("docs/page_with_tag.md")) == 10 + assert commit_count(u._get_repo("docs/page_with_tag.md")) == 11 # the revision date was in 'setup_commit_history' was set to 1642911026 (Sun Jan 23 2022 04:10:26 GMT+0000) @@ -672,3 +677,37 @@ def test_mkdocs_genfiles_plugin(tmp_path): validate_build( testproject_path, plugin_config ) + + +def test_ignored_commits(tmp_path): + testproject_path = setup_clean_mkdocs_folder( + "tests/fixtures/basic_project/mkdocs_ignored_commits.yml", tmp_path + ) + repo = setup_commit_history(testproject_path) + + # First test that the middle commit doesn't show up by default + # January 23, 2022 is the date of the most recent commit + with open(str(testproject_path / "ignored-commits.txt"), "wt") as fp: + fp.write("") + + result = build_docs_setup(testproject_path) + assert result.exit_code == 0 + + page_with_tag = testproject_path / "site/page_with_tag/index.html" + contents = page_with_tag.read_text(encoding="utf8") + assert "January 23, 2022" in contents + + # Now mark the most recent change to page_with_tag as ignored + # May 4, 2018 is the date of the second most recent commit + hash = repo.git.log("docs/page_with_tag.md", format="%H", n=1) + + with open(str(testproject_path / "ignored-commits.txt"), "wt") as fp: + fp.write(hash) + + # should not raise warning + result = build_docs_setup(testproject_path) + assert result.exit_code == 0 + + page_with_tag = testproject_path / "site/page_with_tag/index.html" + contents = page_with_tag.read_text(encoding="utf8") + assert "May 4, 2018" in contents \ No newline at end of file diff --git a/tests/test_parse_git_ignore_revs.py b/tests/test_parse_git_ignore_revs.py new file mode 100644 index 0000000..93cf7fa --- /dev/null +++ b/tests/test_parse_git_ignore_revs.py @@ -0,0 +1,16 @@ +from mkdocs_git_revision_date_localized_plugin.util import Util +import pytest +import tempfile + +TEST_PARAMS = [ + (b"abc123\n", ["abc123"]), + (b"abc123 # comments are ignored\n", ["abc123"]), + (b"\n\n\n\n\nabc123\n\n\n\n\n", ["abc123"]), +] + +@pytest.mark.parametrize("test_input,expected", TEST_PARAMS) +def test_parse_git_ignore_revs(test_input, expected): + with tempfile.NamedTemporaryFile() as fp: + fp.write(test_input) + fp.flush() + assert Util.parse_git_ignore_revs(fp.name) == expected