Skip to content

Add ignored_commits_file option #157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Thanks for considering to contribute to this project! Some guidelines:
Make sure to install an editable version before running the tests:

```python
pip install -r requirement_dev.txt
pip install -r requirements_dev.txt
pip install -e .
pytest --cov=mkdocs_git_revision_date_localized_plugin --cov-report term-missing tests/
```
Expand Down
16 changes: 16 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ You can customize the plugin by setting options in `mkdocs.yml`. For example:
enable_git_follow: true
enabled: true
strict: true
ignored_commits_file: .git-blame-ignore-revs
```

## `type`
Expand Down Expand Up @@ -155,3 +156,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`. You can specify a file path (relative to your `mkdocs.yml` directory) that contains a list of commit hashes to ignore
when determining the revision date. The format of the file is the same as the format of
git `blame.ignoreRevsFile`. This can be useful to ignore specific commits that apply 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
```
5 changes: 3 additions & 2 deletions src/mkdocs_git_revision_date_localized_plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ 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)),
("enable_git_follow", config_options.Type(bool, default=True))
("enable_git_follow", 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]:
Expand All @@ -66,7 +67,7 @@ def on_config(self, config: config_options.Config, **kwargs) -> Dict[str, Any]:

assert self.config['type'] in ["date","datetime","iso_date","iso_datetime","timeago","custom"]

self.util = Util(config=self.config)
self.util = Util(config=self.config, mkdocs_dir=os.path.abspath(os.path.dirname(config.get('config_file_path'))))

# Save last commit timestamp for entire site
self.last_site_revision_timestamp = self.util.get_git_commit_timestamp(
Expand Down
57 changes: 54 additions & 3 deletions src/mkdocs_git_revision_date_localized_plugin/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
NoSuchPathError,
)

from typing import Dict
from typing import Any, Dict, List

logger = logging.getLogger("mkdocs.plugins")

Expand All @@ -27,11 +27,15 @@ class Util:
This helps find git and calculate relevant dates.
"""

def __init__(self, config={}):
def __init__(self, config: Dict, mkdocs_dir: str):
"""Initialize utility class."""
self.config = config
self.repo_cache = {}

ignore_commits_file = self.config.get("ignored_commits_file")
ignore_commits_filepath = os.path.join(mkdocs_dir, ignore_commits_file) if ignore_commits_file else None
self.ignored_commits = self.parse_git_ignore_revs(ignore_commits_filepath) if ignore_commits_file else []

def _get_repo(self, path: str) -> Git:
if not os.path.isdir(path):
path = os.path.dirname(path)
Expand Down Expand Up @@ -63,6 +67,7 @@ def get_git_commit_timestamp(
int: commit date in unix timestamp, starts with the most recent commit.
"""
commit_timestamp = ""
n_ignored_commits = 0

# Determine the logging level
# Only log warnings when plugin is set to strict.
Expand All @@ -82,6 +87,7 @@ def get_git_commit_timestamp(

follow_option=self.config.get('enable_git_follow')

# 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(
Expand All @@ -100,6 +106,25 @@ def get_git_commit_timestamp(
ignore_all_space=True, ignore_blank_lines=True
)

# Retrieve the history for the file in the format <hash> <timestamp>
# 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
else:
n_ignored_commits += 1


except (InvalidGitRepositoryError, NoSuchPathError) as err:
if self.config.get('fallback_to_build_date'):
log(
Expand Down Expand Up @@ -145,10 +170,13 @@ def get_git_commit_timestamp(
# create timestamp
if commit_timestamp == "":
commit_timestamp = time.time()
log(
msg = (
"[git-revision-date-localized-plugin] '%s' has no git logs, using current timestamp"
% path
)
if n_ignored_commits:
msg += f" (ignored {n_ignored_commits} commits)"
log(msg)

return int(commit_timestamp)

Expand Down Expand Up @@ -193,3 +221,26 @@ def add_spans(date_formats: Dict[str, str]) -> Dict[str, str]:
% (date_type, datetime_string, 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 = []

try:
with open(filename, "rt", encoding='utf-8') as f:
for line in f:
line = line.split("#", 1)[0].strip()
if not line:
continue
result.append(line)
except FileNotFoundError:
logger.error(f"File not found: {filename}")
except Exception as e:
logger.error(f"An error occurred while reading the file {filename}: {e}")

return result
1 change: 1 addition & 0 deletions tests/fixtures/basic_project/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
site/
8 changes: 8 additions & 0 deletions tests/fixtures/basic_project/mkdocs_ignored_commits.yml
Original file line number Diff line number Diff line change
@@ -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
49 changes: 44 additions & 5 deletions tests/test_builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,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")
Expand Down Expand Up @@ -249,7 +254,7 @@ def validate_build(testproject_path, plugin_config: dict = {}):
contents = page_with_tag.read_text(encoding="utf8")
assert re.search(r"renders as\:\s[<span>|\w].+", contents)

repo = Util(config=plugin_config)
repo = Util(config=plugin_config, mkdocs_dir=testproject_path)
date_formats = repo.get_date_formats_for_timestamp(
commit_timestamp=repo.get_git_commit_timestamp(
path=str(testproject_path / "docs/page_with_tag.md"),
Expand Down Expand Up @@ -373,10 +378,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
u = Util(config={}, mkdocs_dir=os.getcwd())
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)
Expand Down Expand Up @@ -667,3 +672,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", encoding="utf-8") 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
commit_hash = repo.git.log("docs/page_with_tag.md", format="%H", n=1)

with open(str(testproject_path / "ignored-commits.txt"), "wt", encoding="utf-8") as fp:
fp.write(commit_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
20 changes: 20 additions & 0 deletions tests/test_parse_git_ignore_revs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from mkdocs_git_revision_date_localized_plugin.util import Util
import pytest
import tempfile
import os

TEST_PARAMS = [
("abc123\n", ["abc123"]),
("abc123 # comments are ignored\n", ["abc123"]),
("\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(mode='w', encoding='utf-8', delete=False) as fp:
fp.write(test_input)
temp_file_name = fp.name
try:
assert Util.parse_git_ignore_revs(temp_file_name) == expected
finally:
os.remove(temp_file_name)
Loading