Skip to content
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

Add ignored commits feature #114

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
16 changes: 16 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'When specified, contains a file that contains a list' -- That sentence is a bit weird. Explain it needs a path, and relative to the projects root (mkdocs.yml file?).

when determining the most recent updated time. The format of the file is the same as the format of
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometime we use git blame to determine the creation date also. So replace determining 'the most recent updated time' with 'the revision date'.

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
```
1 change: 1 addition & 0 deletions mkdocs_git_revision_date_localized_plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
41 changes: 36 additions & 5 deletions 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 Any, Dict
from typing import Any, Dict, List

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

Expand All @@ -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)
Expand Down Expand Up @@ -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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With 150k downloads per month, someone is bound to stumble on this. It should ignore those commits everywhere.

Best to refactor, using a separate function that takes in a list of commits and return the last or first one that is not in the ignored commits list. You can use that function for both creation and most recent update.

We also need a proper fallback inside that function. What if all commits are ignored (f.e. because there is only 1)? You would think that never happens, but it could happen for new repos. Or, some users, f.e. when using CI, use a shallow clone, that only retrieves the last commit. If that commits happens to be an ignored commit -- there are no more commits left to determine the build date.

Probably the best fallback for that scenario is to then use ignored commits anyway. We'd need to document that. And we probably want to inform the user that that happened. It should log a warning when the strict option is True.

if is_first_commit:
# diff_filter="A" will select the commit that created the file
commit_timestamp = git.log(
Expand All @@ -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 <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

except (InvalidGitRepositoryError, NoSuchPathError) as err:
if self.config.get('fallback_to_build_date'):
log(
Expand Down Expand Up @@ -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:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we should specify UTF-8 encoding. This is standard for mkdocs. See f.e. https://github.com/mkdocs/mkdocs/blob/df3739d51903ab56771ac071a05b5aa9cdf9e129/mkdocs/commands/new.py#L43

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what if this file is not found, because a user specified the wrong path? Can we give a helpful error, showing with path was tried and which parameter in mkdocs.yml should be changed?

for line in f:
line = line.split("#", 1)[0].strip()
if not line:
continue
result.append(line)
return result
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
45 changes: 42 additions & 3 deletions tests/test_builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -672,3 +677,37 @@ def test_mkdocs_genfiles_plugin(tmp_path):
validate_build(
testproject_path, plugin_config
)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well done on writing these unit tests!


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
16 changes: 16 additions & 0 deletions tests/test_parse_git_ignore_revs.py
Original file line number Diff line number Diff line change
@@ -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