Skip to content

Commit

Permalink
Improvements (#10)
Browse files Browse the repository at this point in the history
* Fix the case where an update of end_year would lead to a subsequent update taking place if the tool were run again.

* Remove print output at the end

* Add shallow repo detection
  • Loading branch information
thomasmarwitz authored Jan 25, 2024
1 parent 8f30755 commit d886712
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 35 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
- name: Checkout code 🛎️
uses: actions/checkout@v3

- name: Check shallow env
run: git rev-parse --is-shallow-repository

- name: Set up Python ${{ matrix.python-version }} 🔧
uses: actions/setup-python@v3
with:
Expand Down
14 changes: 14 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@
Changelog
=========


Insert-license-header 1.3.0
================================================
* Fix issue #9: Avoid needing to re-run insert-license-header tool in cases where a
modification would lead to a newer git end year that is not yet present in the file.
* Detect whether in a shallow git repo, if yes, don't trust GIT
* Remove verbose output logs


Insert-license-header 1.2.0
================================================
* Added debugging functionality, can be triggered by providing `--debug`.


Insert-license-header 1.1.0
================================================
* Re-implement behaviour `--dynamic-years`
Expand Down
107 changes: 83 additions & 24 deletions insert_license_header/insert_license.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,18 +157,6 @@ def main(argv=None):
logging.debug(f"Changed files: {changed_files}\n")

if check_failed:
print("")
if changed_files:
print(f"Some sources were modified by the hook {changed_files}")
if todo_files:
print(
f"Some sources contain TODO about inconsistent licenses: {todo_files}"
)
print("Now aborting the commit.")
print(
'You should check the changes made. Then simply "git add --update ." and re-commit'
)
print("")
return 1
return 0

Expand Down Expand Up @@ -253,23 +241,45 @@ def process_files(args, changed_files, todo_files, license_info: LicenseInfo):

license_info = plain_license_info

last_year = datetime.now().year
current_end_year = datetime.now().year
logging.debug(f"Current end year: {current_end_year}")
if args.dynamic_years:
year_range = _get_git_file_year_range(src_filepath)
logging.debug(f"Git year range: {year_range}")

year_start, year_end = (
(year_range[0].year, year_range[1].year)
if year_range is not None
existing_year_range = _get_existing_year_range(src_filepath)
logging.debug(f"Existing year range: {existing_year_range}")
git_year_range = _get_git_file_year_range(src_filepath)
logging.debug(f"Git year range: {git_year_range}")
git_year_start, git_year_end = (
(git_year_range[0].year, git_year_range[1].year)
if git_year_range is not None
else (datetime.now().year, PLACEHOLDER_END_YEAR)
)
last_year = year_end
logging.debug(f"Formatted year range: {year_start}-{year_end}")

PREFER_GIT_OVER_CURRENT_YEAR = True

if existing_year_range is not None:
_, existing_year_end = existing_year_range
if existing_year_end < git_year_end and git_year_end < current_end_year:
# If the existing year range is smaller than the git year range,
# this would lead to an update of the existing year range.
# If the git year range is smaller than the current year,
# this would lead to a subsequent update being necessary once
# the changes are committed. `insert-license` would have
# to be run again.
PREFER_GIT_OVER_CURRENT_YEAR = False
logging.debug(
"Existing year range is smaller than git year range."
"Git year range is smaller than current year."
"Setting 'PREFER_GIT_OVER_CURRENT_YEAR = False'."
)

current_end_year = (
git_year_end if PREFER_GIT_OVER_CURRENT_YEAR else current_end_year
)

prefixed_license = [
line.format(
year_start=year_start,
year_end=year_end,
year_start=git_year_start,
year_end=git_year_end,
) # this assumes '{year_start}' and '{year_end}' appear in your license
for line in license_info.prefixed_license
]
Expand Down Expand Up @@ -324,7 +334,7 @@ def process_files(args, changed_files, todo_files, license_info: LicenseInfo):
src_file_content=src_file_content,
src_filepath=src_filepath,
encoding=encoding,
last_year=last_year,
last_year=current_end_year,
):
logging.debug(f"License found in {src_filepath}, updating...")
changed_files.append(src_filepath)
Expand Down Expand Up @@ -801,6 +811,49 @@ def get_license_candidate_string(candidate_array, license_info):
return license_string_candidate.strip(), found_license_offset


def _get_existing_year_range(filepath: str) -> tuple[int, int] | None:
"""Uses regex to extract start and end year from the license header.
Take the start year from the first year and the end year from the last.
If the file has no license header, return None.
:param filepath: path to file
:type filepath: str
:return: year of creation
:rtype: int
"""

with open(filepath, encoding="utf8", newline="") as src_file:
src_file_content = src_file.readlines()

for line in src_file_content:
matches = _YEAR_RANGE_PATTERN.findall(line)
if matches:
match = matches[0]
start_year = int(match[:4])
end_year = match[5:].lstrip(" -,")
if end_year:
return start_year, int(end_year)
return start_year, PLACEHOLDER_END_YEAR

return None # File exists but no license header found


def _is_shallow_git_repo() -> bool:
"""Check if the current directory is a shallow git repo.
If it is, we cannot use git log to get the year range of the file.
"""
command = "git rev-parse --is-shallow-repository"

try:
result = subprocess.run(
command, shell=True, text=True, capture_output=True, check=True
)
except subprocess.CalledProcessError:
return False

return result.stdout.strip() == "true"


def _get_git_file_year_range(filepath: str) -> tuple[datetime, datetime] | None:
"""Uses git log formatting to extract start and end year from the commits.
Take the start year from the first commit and the end year from the last.
Expand All @@ -813,6 +866,12 @@ def _get_git_file_year_range(filepath: str) -> tuple[datetime, datetime] | None:
"""
command = f'git log --follow --format="%aI" -- "{filepath}"'

if _is_shallow_git_repo():
# Shallow git repo, don't trust git log as the life cycle of a file
# may not be fully captured. In this case, just pretend the file
# is not tracked with Git.
return None

try:
result = subprocess.run(
command, shell=True, text=True, capture_output=True, check=True
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "hatchling.build"
name = "insert-license-header"
description = "Tool to insert license headers at the beginning of text-based files."
readme = "README.md"
version = "1.2.0"
version = "1.3.0"
license = "MIT"
authors = [
{name = "Thomas Marwitz", email = "[email protected]"},
Expand Down
28 changes: 18 additions & 10 deletions tests/insert_license_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from itertools import chain, product

import pytest

from insert_license_header.insert_license import (
LicenseInfo,
_get_git_file_year_range,
Expand Down Expand Up @@ -704,8 +703,10 @@ def get_datetime_range(year_range: str):
# GIT tracked: Test END_YEAR.
# -> GIT tracking or year in file should always have precedence over current year
################################################################################
("2020-2022", "2020-2023", "2024", "2020-2023"),
# --> Update 'end_year' if git is newer, prioritize git > current year
("2020-2022", "2020-2023", "2024", "2020-2024"),
# --> Update 'end_year' if git is newer, prioritize current year
# Because otherwise, the updated file would be modified, i.e. git end year is
# now also current year.
("2020-2023", "2020-2022", "2024", "2020-2023"),
# --> Keep 'end_year' if git is older, prioritize 'year in file' > current year
("2020-2022", "2020-2022", "2024", "2020-2022"),
Expand Down Expand Up @@ -805,14 +806,21 @@ def test_dynamic_years_with_existing_license_header(
assert updated_content == expected_content


# TESTCASES:
# File's last_year is now 2024 (prev 2023)
# File's last_year is still 2023 (current year = 2024)
# File's start_year
def test_git_ignored_in_shallow_repo(monkeypatch, tmp_path):
"""Mock subprocess.run to throw an exception. Expect the returned datetime to have this year!"""

def mock_shallow_test(cmd, **kwargs):
assert "git rev-parse --is-shallow-repository" in cmd
return type("mock", (), {"stdout": "true"})

monkeypatch.setattr(
subprocess,
"run",
mock_shallow_test,
)

# 1. File has no license header:
# - Git tracked: take from git
# - Not Git tracked: take current-current
result = _get_git_file_year_range("some-non-existant-file.py")
assert result is None


def test_git_file_creation_date(monkeypatch):
Expand Down

0 comments on commit d886712

Please sign in to comment.