From 3398e322606f79b1b661700a183a861d2c81807f Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Sat, 9 Mar 2024 20:36:02 +0100 Subject: [PATCH 01/19] fix: Ignore bump on new git repo without unreleased commits --- src/git_changelog/build.py | 2 +- tests/helpers.py | 8 ++++++++ tests/test_build.py | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index f14a830..856d66c 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -443,7 +443,7 @@ def _bump(self, version: str) -> None: def _fix_single_version(self, version: str | None) -> None: last_version = self.versions_list[0] - if len(self.versions_list) == 1 and last_version.planned_tag is None: + if len(self.versions_list) == 1 and last_version.planned_tag is None and not last_version.tag: planned_tag = version if version and version not in {"auto", "major", "minor", "patch"} else "0.1.0" last_version.tag = planned_tag last_version.url += planned_tag diff --git a/tests/helpers.py b/tests/helpers.py index c1664d5..fac9f03 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -58,3 +58,11 @@ def commit(self, message: str) -> str: self.git("add", "-A") self.git("commit", "-m", message) return self.git("rev-parse", "HEAD") + + def tag(self, tagname: str) -> None: + """Create a new tag in the GIt repository. + + Parameters: + tagname: The name of the new tag. + """ + self.git("tag", tagname) diff --git a/tests/test_build.py b/tests/test_build.py index 78f2c8c..0b72570 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -22,8 +22,24 @@ def test_bump_with_semver_on_new_repo(repo: GitRepo, bump: str, expected: str) - Parameters: repo: GitRepo to a temporary repository. + bump: The bump parameter value. expected: Expected version for the new changelog entry. """ changelog = Changelog(repo.path, convention=AngularConvention, bump=bump) assert len(changelog.versions_list) == 1 assert changelog.versions_list[0].tag == expected + + +@pytest.mark.parametrize("bump", ["auto", "major", "minor", "2.0.0"]) +def test_no_bump_on_first_tag(repo: GitRepo, bump: str) -> None: + """Ignore bump on new git repo without unreleased commits. + + Parameters: + repo: GitRepo to a temporary repository. + bump: The bump parameter value. + """ + repo.tag("1.1.1") + + changelog = Changelog(repo.path, convention=AngularConvention, bump=bump) + assert len(changelog.versions_list) == 1 + assert changelog.versions_list[0].tag == "1.1.1" From 6ad9caf4cf88adcbd98c4262e93af75c26d411b3 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Tue, 5 Mar 2024 21:34:42 +0100 Subject: [PATCH 02/19] refactor: Assign versions to and group commits in one loop --- src/git_changelog/build.py | 71 +++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 856d66c..7458c97 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -240,8 +240,7 @@ def __init__( self.commits: list[Commit] = self.parse_commits() # apply dates to commits and group them by version - dates = self._apply_versions_to_commits() - v_list, v_dict = self._group_commits_by_version(dates) + v_list, v_dict = self._group_commits_by_version() self.versions_list = v_list self.versions_dict = v_dict @@ -361,49 +360,26 @@ def parse_commits(self) -> list[Commit]: return commits - def _apply_versions_to_commits(self) -> dict[str, datetime.date]: - versions_dates = {"": datetime.date.today()} # noqa: DTZ011 + def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]: version = None - for commit in self.commits: - if commit.version: - version = commit.version - versions_dates[version] = commit.committer_date.date() - elif version: - commit.version = version - return versions_dates - - def _group_commits_by_version( - self, - dates: dict[str, datetime.date], - ) -> tuple[list[Version], dict[str, Version]]: - versions_list = [] - versions_dict = {} - versions_types_dict: dict[str, dict[str, Section]] = {} next_version = None + versions_dict = {} + versions_list = [] for commit in self.commits: - if commit.version not in versions_dict: - version = Version(tag=commit.version, date=dates[commit.version]) + if not version or commit.version: + version = self._create_version(commit, next_version) versions_dict[commit.version] = version - if self.provider: - version.url = self.provider.get_tag_url(tag=commit.version) - if next_version: - version.next_version = next_version - next_version.previous_version = version - if self.provider: - next_version.compare_url = self.provider.get_compare_url( - base=version.tag, - target=next_version.tag or "HEAD", - ) - next_version = version versions_list.append(version) - versions_types_dict[commit.version] = {} - versions_dict[commit.version].commits.append(commit) - if "type" in commit.convention and commit.convention["type"] not in versions_types_dict[commit.version]: - section = Section(section_type=commit.convention["type"]) - versions_types_dict[commit.version][commit.convention["type"]] = section - versions_dict[commit.version].sections_list.append(section) - versions_dict[commit.version].sections_dict = versions_types_dict[commit.version] - versions_types_dict[commit.version][commit.convention["type"]].commits.append(commit) + next_version = version + else: + commit.version = version.tag + version.commits.append(commit) + _type = commit.convention["type"] + if "type" in commit.convention and _type not in version.sections_dict: + section = Section(section_type=_type) + version.sections_list.append(section) + version.sections_dict[_type] = section + version.sections_dict[_type].commits.append(commit) if next_version is not None and self.provider: next_version.compare_url = self.provider.get_compare_url( base=versions_list[-1].commits[-1].hash, @@ -411,6 +387,21 @@ def _group_commits_by_version( ) return versions_list, versions_dict + def _create_version(self, commit: Commit, next_version: Version | None) -> Version: + date = commit.committer_date.date() if commit.version else datetime.date.today() # noqa: DTZ011 + version = Version(tag=commit.version, date=date) + if self.provider: + version.url = self.provider.get_tag_url(tag=commit.version) + if next_version: + version.next_version = next_version + next_version.previous_version = version + if self.provider: + next_version.compare_url = self.provider.get_compare_url( + base=version.tag, + target=next_version.tag or "HEAD", + ) + return version + def _bump(self, version: str) -> None: last_version = self.versions_list[0] if not last_version.tag and last_version.previous_version: From 27ee2a169bbf3801aebed0b344589dce0cf53589 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Sat, 9 Mar 2024 21:59:11 +0100 Subject: [PATCH 03/19] fix: Assign commits to versions following their commit graph Before a commit was assigned the version/tag that happened next in the timeline. This leads to versions referencing commits that are not ancestors of it (and therefor not part of this version). Instead the versions should contain only all commits that are ancestors, up until a new version is encountered. --- src/git_changelog/build.py | 134 ++++++++++++++++++++++++++---------- src/git_changelog/commit.py | 7 ++ tests/helpers.py | 32 ++++++++- tests/test_build.py | 77 +++++++++++++++++++++ 4 files changed, 210 insertions(+), 40 deletions(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 7458c97..abc97c7 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -98,7 +98,6 @@ def __init__( self.url: str = url self.compare_url: str = compare_url self.previous_version: Version | None = None - self.next_version: Version | None = None self.planned_tag: str | None = None @property @@ -137,6 +136,21 @@ def is_minor(self) -> bool: """ return bool(self.tag.split(".", 2)[2]) + def add_commit(self, commit: Commit) -> None: + """Adds the given commit and assigns it a section based on the chosen commit convention. + + Arguments: + commit: The git commit. + """ + self.commits.append(commit) + commit.version = self.tag + _type = commit.convention["type"] + if "type" in commit.convention and _type not in self.sections_dict: + section = Section(section_type=_type) + self.sections_list.append(section) + self.sections_dict[_type] = section + self.sections_dict[_type].commits.append(commit) + class Changelog: """The main changelog class.""" @@ -151,6 +165,7 @@ class Changelog: r"%ce%n" # committer email r"%cd%n" # committer date r"%D%n" # tag + r"%P%n" # parent hashes r"%s%n" # subject r"%b%n" + MARKER # body ) @@ -237,12 +252,13 @@ def __init__( # get git log and parse it into list of commits self.raw_log: str = self.get_log() - self.commits: list[Commit] = self.parse_commits() + self.tag_commits: list[Commit] = self.parse_commits() # apply dates to commits and group them by version v_list, v_dict = self._group_commits_by_version() self.versions_list = v_list self.versions_dict = v_dict + self._assign_previous_versions() # TODO: remove at some point if bump_latest: @@ -312,16 +328,20 @@ def get_log(self) -> str: def parse_commits(self) -> list[Commit]: """Parse the output of 'git log' into a list of commits. + The commits build a Git commit graph by referencing their parent commits. + Commits are ordered from newest to oldest. + Returns: The list of commits. """ lines = self.raw_log.split("\n") size = len(lines) - 1 # don't count last blank line - commits = [] + commits: list[Commit] = [] + tag_commits: list[Commit] = [] pos = 0 while pos < size: # build body - nbl_index = 9 + nbl_index = 10 body = [] while lines[pos + nbl_index] != self.MARKER: body.append(lines[pos + nbl_index].strip("\r")) @@ -337,13 +357,21 @@ def parse_commits(self) -> list[Commit]: committer_email=lines[pos + 5], committer_date=lines[pos + 6], refs=lines[pos + 7], - subject=lines[pos + 8], + parent_hashes=lines[pos + 8], + subject=lines[pos + 9], body=body, parse_trailers=self.parse_trailers, ) pos += nbl_index + 1 + # find all commits that have this commit as a parent to build the commit graph + # TODO could be made more performant by keeping track which commits do have missing parent commits and only iterate over those + for child_commit in commits: + if commit.hash in child_commit.parent_hashes: + index = child_commit.parent_hashes.index(commit.hash) + child_commit.parent_commits.insert(index, commit) + # expand commit object with provider parsing if self.provider: commit.update_with_provider(self.provider, parse_refs=self.parse_provider_refs) @@ -358,49 +386,79 @@ def parse_commits(self) -> list[Commit]: commits.append(commit) - return commits + if commit.tag: + tag_commits.append(commit) + + # Add first commit to result, if it is part of an unreleased version + if not commits[0].tag: + tag_commits.insert(0, commits[0]) + + return tag_commits def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]: - version = None - next_version = None - versions_dict = {} - versions_list = [] - for commit in self.commits: - if not version or commit.version: - version = self._create_version(commit, next_version) - versions_dict[commit.version] = version - versions_list.append(version) - next_version = version - else: - commit.version = version.tag - version.commits.append(commit) - _type = commit.convention["type"] - if "type" in commit.convention and _type not in version.sections_dict: - section = Section(section_type=_type) - version.sections_list.append(section) - version.sections_dict[_type] = section - version.sections_dict[_type].commits.append(commit) - if next_version is not None and self.provider: - next_version.compare_url = self.provider.get_compare_url( - base=versions_list[-1].commits[-1].hash, - target=next_version.tag or "HEAD", - ) + """Groups commits into versions. + + Commits are assigned to the version, it was first released with. + A commit is assigned to exactly one version. + + Returns: + A tuple of: + - The list of versions order descending by timestamp + - A dict of versions with the tagname as keys + """ + versions_dict: dict[str, Version] = {} + versions_list: list[Version] = [] + # Iterate in reversed order (oldest to newest tag) to assigns commits to the first version it was released with + for tag_commit in reversed(self.tag_commits): + # Create new version object + version = self._create_version(tag_commit) + versions_dict[tag_commit.version] = version + versions_list.insert(0, version) + + # Find all commits for this version by following the commit graph + version.add_commit(tag_commit) + next_commits = tag_commit.parent_commits.copy() + while len(next_commits) > 0: + next_commit = next_commits.pop(0) + if not next_commit.tag and not next_commit.version: + version.add_commit(next_commit) + next_commits.extend(next_commit.parent_commits) return versions_list, versions_dict - def _create_version(self, commit: Commit, next_version: Version | None) -> Version: + def _create_version(self, commit: Commit) -> Version: date = commit.committer_date.date() if commit.version else datetime.date.today() # noqa: DTZ011 version = Version(tag=commit.version, date=date) if self.provider: version.url = self.provider.get_tag_url(tag=commit.version) - if next_version: - version.next_version = next_version - next_version.previous_version = version + return version + + def _assign_previous_versions(self) -> None: + """Assign each version its previous version and creates the compare url. + + The previous version is defined as the first version, that is found + by following the left branch of the commit graph. + + If no previous version is found, either because it is the first commit or + due to the commit filter excluding it, the compare url is created with the + first commit (oldest). + """ + for version in self.versions_list: + next_commit = version.commits[0] + previous_version: str = "" + while not previous_version: + if len(next_commit.parent_commits) == 0: + previous_version = next_commit.hash + else: + # Only follow the left branch of the commit tree + next_commit = next_commit.parent_commits[0] + if next_commit.version: + previous_version = next_commit.version if self.provider: - next_version.compare_url = self.provider.get_compare_url( - base=version.tag, - target=next_version.tag or "HEAD", + version.compare_url = self.provider.get_compare_url( + base=previous_version, + target=version.tag or "HEAD", ) - return version + version.previous_version = self.versions_dict.get(previous_version) def _bump(self, version: str) -> None: last_version = self.versions_list[0] diff --git a/src/git_changelog/commit.py b/src/git_changelog/commit.py index 26414cb..683a926 100644 --- a/src/git_changelog/commit.py +++ b/src/git_changelog/commit.py @@ -34,6 +34,7 @@ def __init__( committer_email: str = "", committer_date: str | datetime = "", refs: str = "", + parent_hashes: str | list[str] = "", subject: str = "", body: list[str] | None = None, url: str = "", @@ -85,6 +86,12 @@ def __init__( self.tag: str = tag self.version: str = tag + if isinstance(parent_hashes, str): + self.parent_hashes = parent_hashes.split(" ") + else: + self.parent_hashes = parent_hashes + self.parent_commits: list[Commit] = [] + self.text_refs: dict[str, list[Ref]] = {} self.convention: dict[str, Any] = {} diff --git a/tests/helpers.py b/tests/helpers.py index fac9f03..2fc15c9 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -28,7 +28,7 @@ def __init__(self, repo: Path) -> None: self.git("config", "user.name", "dummy") self.git("config", "user.email", "dummy@example.com") self.git("remote", "add", "origin", "git@github.com:example/example") - self.commit("chore: Initial repository creation") + self.first_hash = self.commit("chore: Initial repository creation") def git(self, *args: str) -> str: """Run a Git command in the repository. @@ -57,7 +57,7 @@ def commit(self, message: str) -> str: fh.write(str(random.randint(0, 1))) # noqa: S311 self.git("add", "-A") self.git("commit", "-m", message) - return self.git("rev-parse", "HEAD") + return self.git("rev-parse", "HEAD").rstrip() def tag(self, tagname: str) -> None: """Create a new tag in the GIt repository. @@ -66,3 +66,31 @@ def tag(self, tagname: str) -> None: tagname: The name of the new tag. """ self.git("tag", tagname) + + def branch(self, branchname: str) -> None: + """Create a new branch in the GIt repository. + + Parameters: + branchname: The name of the new branch. + """ + self.git("branch", branchname) + + def checkout(self, branchname: str) -> None: + """Checkout a branch. + + Parameters: + branchname: The name of the branch. + """ + self.git("checkout", branchname) + + def merge(self, branchname: str) -> str: + """Merge a branch into the current branch, creating a new merge commit. + + Parameters: + branchname: The name of the branch to merge. + + Returns: + The Git commit hash of the merge commit. + """ + self.git("merge", "--no-ff", "--commit", "-m", f"merge: Merge branch '{branchname}'", branchname) + return self.git("rev-parse", "HEAD").rstrip() diff --git a/tests/test_build.py b/tests/test_build.py index 0b72570..b9f0412 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -43,3 +43,80 @@ def test_no_bump_on_first_tag(repo: GitRepo, bump: str) -> None: changelog = Changelog(repo.path, convention=AngularConvention, bump=bump) assert len(changelog.versions_list) == 1 assert changelog.versions_list[0].tag == "1.1.1" + + +def test_one_release_branch_with_feat_branch(repo: GitRepo) -> None: + r"""Test parsing and grouping commits to versions. + + Commit graph: + 1.0.0 + | + main A-B---D + \ / + feat --C + + Expected: + - 1.0.0: D B C A + + Parameters: + repo: GitRepo to a temporary repository. + """ + commit_a = repo.first_hash + repo.branch("develop") + commit_b = repo.commit("fix: B") + repo.checkout("develop") + commit_c = repo.commit("feat: C") + repo.checkout("main") + commit_d = repo.merge("develop") + repo.tag("1.0.0") + + changelog = Changelog(repo.path, convention=AngularConvention) + + assert len(changelog.versions_list) == 1 + version = changelog.versions_list[0] + assert version.tag == "1.0.0" + assert len(version.commits) == 4 + hashes = [commit.hash for commit in version.commits] + assert hashes == [commit_d, commit_b, commit_c, commit_a] + + +def test_one_release_branch_with_two_versions(repo: GitRepo) -> None: + r"""Test parsing and grouping commits to versions. + + Commit graph: + 1.1.0 + 1.0.0 | + | | + main A-B---D + \ / + feat --C + + Expected: + - 1.1.0: D C + - 1.0.0: B A + + Parameters: + repo: GitRepo to a temporary repository. + """ + commit_a = repo.first_hash + repo.branch("develop") + commit_b = repo.commit("fix: B") + repo.tag("1.0.0") + repo.checkout("develop") + commit_c = repo.commit("feat: C") + repo.checkout("main") + commit_d = repo.merge("develop") + repo.tag("1.1.0") + + changelog = Changelog(repo.path, convention=AngularConvention) + + assert len(changelog.versions_list) == 2 + version = changelog.versions_list[0] + assert version.tag == "1.1.0" + hashes = [commit.hash for commit in version.commits] + assert hashes == [commit_d, commit_c] + + version = changelog.versions_list[1] + assert version.tag == "1.0.0" + hashes = [commit.hash for commit in version.commits] + assert hashes == [commit_b, commit_a] From 025bff96175207d016f782dae45dc4f29681caee Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Sat, 9 Mar 2024 22:51:32 +0100 Subject: [PATCH 04/19] test: Add test with two release branches --- tests/test_build.py | 73 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index b9f0412..cc0ed6d 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -2,6 +2,7 @@ from __future__ import annotations +from time import sleep from typing import TYPE_CHECKING import pytest @@ -10,6 +11,7 @@ from git_changelog.commit import AngularConvention if TYPE_CHECKING: + from git_changelog.build import Version from tests.helpers import GitRepo @@ -73,11 +75,11 @@ def test_one_release_branch_with_feat_branch(repo: GitRepo) -> None: changelog = Changelog(repo.path, convention=AngularConvention) assert len(changelog.versions_list) == 1 - version = changelog.versions_list[0] - assert version.tag == "1.0.0" - assert len(version.commits) == 4 - hashes = [commit.hash for commit in version.commits] - assert hashes == [commit_d, commit_b, commit_c, commit_a] + _assert_version( + changelog.versions_list[0], + expected_tag="1.0.0", + expected_commits=[commit_d, commit_b, commit_c, commit_a], + ) def test_one_release_branch_with_two_versions(repo: GitRepo) -> None: @@ -111,12 +113,59 @@ def test_one_release_branch_with_two_versions(repo: GitRepo) -> None: changelog = Changelog(repo.path, convention=AngularConvention) assert len(changelog.versions_list) == 2 - version = changelog.versions_list[0] - assert version.tag == "1.1.0" - hashes = [commit.hash for commit in version.commits] - assert hashes == [commit_d, commit_c] + _assert_version(changelog.versions_list[0], expected_tag="1.1.0", expected_commits=[commit_d, commit_c]) + _assert_version(changelog.versions_list[1], expected_tag="1.0.0", expected_commits=[commit_b, commit_a]) + + +def test_two_release_branches(repo: GitRepo) -> None: + r"""Test parsing and grouping commits to versions. + + Commit graph: + 1.1.0 + 1.0.0 | + | | + main A-B---D-----G + \ \ / + develop --C---E-F + | + 2.0.0 + Expected: + - Unreleased: G F + - 2.0.0: E C + - 1.1.0: D + - 1.0.0: B A + + Parameters: + repo: GitRepo to a temporary repository. + """ + commit_a = repo.first_hash + repo.branch("develop") + commit_b = repo.commit("fix: B") + repo.tag("1.0.0") + repo.checkout("develop") + commit_c = repo.commit("feat: C") + repo.checkout("main") + commit_d = repo.commit("fix: C") + repo.tag("1.1.0") + repo.checkout("develop") + sleep(1) # Git timestamp only has second precision, delay commit to ensure git log lists it before commit C + commit_e = repo.merge("main") + repo.tag("2.0.0") + commit_f = repo.commit("feat: F") + repo.checkout("main") + commit_g = repo.merge("develop") + + changelog = Changelog(repo.path, convention=AngularConvention) + + assert len(changelog.versions_list) == 4 + versions = iter(changelog.versions_list) + _assert_version(next(versions), expected_tag="", expected_commits=[commit_g, commit_f]) + _assert_version(next(versions), expected_tag="2.0.0", expected_commits=[commit_e, commit_c]) + _assert_version(next(versions), expected_tag="1.1.0", expected_commits=[commit_d]) + _assert_version(next(versions), expected_tag="1.0.0", expected_commits=[commit_b, commit_a]) + - version = changelog.versions_list[1] - assert version.tag == "1.0.0" +def _assert_version(version: Version, expected_tag: str, expected_commits: list[str]) -> None: + assert version.tag == expected_tag hashes = [commit.hash for commit in version.commits] - assert hashes == [commit_b, commit_a] + assert hashes == expected_commits From 84bee180ec326d3c56a78cb66b34fa3c0f784a8b Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Sun, 10 Mar 2024 07:58:27 +0100 Subject: [PATCH 05/19] test: Set initial branch name to fix failing tests --- tests/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers.py b/tests/helpers.py index 2fc15c9..c52e295 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -24,7 +24,7 @@ def __init__(self, repo: Path) -> None: repo: Path to the git repository. """ self.path = repo - self.git("init") + self.git("init", "-b", "main") self.git("config", "user.name", "dummy") self.git("config", "user.email", "dummy@example.com") self.git("remote", "add", "origin", "git@github.com:example/example") From 42d8e0c60057b428fb79f4343b62bbce83f3173c Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Sun, 10 Mar 2024 09:56:26 +0100 Subject: [PATCH 06/19] fixup! fix wrong compare URL --- src/git_changelog/build.py | 4 ++-- tests/test_build.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index abc97c7..67ec632 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -451,8 +451,8 @@ def _assign_previous_versions(self) -> None: else: # Only follow the left branch of the commit tree next_commit = next_commit.parent_commits[0] - if next_commit.version: - previous_version = next_commit.version + if next_commit.tag: + previous_version = next_commit.tag if self.provider: version.compare_url = self.provider.get_compare_url( base=previous_version, diff --git a/tests/test_build.py b/tests/test_build.py index cc0ed6d..60ea18b 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -159,13 +159,17 @@ def test_two_release_branches(repo: GitRepo) -> None: assert len(changelog.versions_list) == 4 versions = iter(changelog.versions_list) - _assert_version(next(versions), expected_tag="", expected_commits=[commit_g, commit_f]) - _assert_version(next(versions), expected_tag="2.0.0", expected_commits=[commit_e, commit_c]) - _assert_version(next(versions), expected_tag="1.1.0", expected_commits=[commit_d]) - _assert_version(next(versions), expected_tag="1.0.0", expected_commits=[commit_b, commit_a]) + _assert_version(next(versions), expected_tag="", expected_prev_tag="1.1.0", expected_commits=[commit_g, commit_f]) + _assert_version(next(versions), expected_tag="2.0.0", expected_prev_tag=None, expected_commits=[commit_e, commit_c]) + _assert_version(next(versions), expected_tag="1.1.0", expected_prev_tag="1.0.0", expected_commits=[commit_d]) + _assert_version(next(versions), expected_tag="1.0.0", expected_prev_tag=None, expected_commits=[commit_b, commit_a]) -def _assert_version(version: Version, expected_tag: str, expected_commits: list[str]) -> None: +def _assert_version(version: Version, expected_tag: str, expected_prev_tag: str, expected_commits: list[str]) -> None: assert version.tag == expected_tag + if expected_prev_tag: + assert version.previous_version.tag == expected_prev_tag + else: + assert version.previous_version == None hashes = [commit.hash for commit in version.commits] assert hashes == expected_commits From 7038fda3ca33d5a7a2fc75ae0be9f6b9a089acd6 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Sun, 10 Mar 2024 14:45:23 +0100 Subject: [PATCH 07/19] fixup! fix failing tests --- tests/test_build.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index 60ea18b..02cdf0d 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -78,6 +78,7 @@ def test_one_release_branch_with_feat_branch(repo: GitRepo) -> None: _assert_version( changelog.versions_list[0], expected_tag="1.0.0", + expected_prev_tag=None, expected_commits=[commit_d, commit_b, commit_c, commit_a], ) @@ -113,8 +114,18 @@ def test_one_release_branch_with_two_versions(repo: GitRepo) -> None: changelog = Changelog(repo.path, convention=AngularConvention) assert len(changelog.versions_list) == 2 - _assert_version(changelog.versions_list[0], expected_tag="1.1.0", expected_commits=[commit_d, commit_c]) - _assert_version(changelog.versions_list[1], expected_tag="1.0.0", expected_commits=[commit_b, commit_a]) + _assert_version( + changelog.versions_list[0], + expected_tag="1.1.0", + expected_prev_tag="1.0.0", + expected_commits=[commit_d, commit_c], + ) + _assert_version( + changelog.versions_list[1], + expected_tag="1.0.0", + expected_prev_tag=None, + expected_commits=[commit_b, commit_a], + ) def test_two_release_branches(repo: GitRepo) -> None: @@ -165,11 +176,17 @@ def test_two_release_branches(repo: GitRepo) -> None: _assert_version(next(versions), expected_tag="1.0.0", expected_prev_tag=None, expected_commits=[commit_b, commit_a]) -def _assert_version(version: Version, expected_tag: str, expected_prev_tag: str, expected_commits: list[str]) -> None: +def _assert_version( + version: Version, + expected_tag: str, + expected_prev_tag: str | None, + expected_commits: list[str], +) -> None: assert version.tag == expected_tag if expected_prev_tag: + assert version.previous_version is not None, f"Expected previous version '{expected_prev_tag}', but was None" assert version.previous_version.tag == expected_prev_tag else: - assert version.previous_version == None + assert version.previous_version is None hashes = [commit.hash for commit in version.commits] assert hashes == expected_commits From 8e1bac6afd4b8b4065ea0c141dc1cc7a896391dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 11 Mar 2024 12:44:38 +0100 Subject: [PATCH 08/19] Update src/git_changelog/build.py --- src/git_changelog/build.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 67ec632..66da327 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -144,12 +144,12 @@ def add_commit(self, commit: Commit) -> None: """ self.commits.append(commit) commit.version = self.tag - _type = commit.convention["type"] - if "type" in commit.convention and _type not in self.sections_dict: - section = Section(section_type=_type) + commit_type = commit.convention.get("type") + if commit_type not in self.sections_dict: + section = Section(section_type=commit_type) self.sections_list.append(section) - self.sections_dict[_type] = section - self.sections_dict[_type].commits.append(commit) + self.sections_dict[commit_type] = section + self.sections_dict[commit_type].commits.append(commit) class Changelog: From 8e1cbab218e3d4f6939ad5e5150c1a3c36180513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 11 Mar 2024 12:44:44 +0100 Subject: [PATCH 09/19] Update src/git_changelog/build.py --- src/git_changelog/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 66da327..137f491 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -137,7 +137,7 @@ def is_minor(self) -> bool: return bool(self.tag.split(".", 2)[2]) def add_commit(self, commit: Commit) -> None: - """Adds the given commit and assigns it a section based on the chosen commit convention. + """Register the given commit and add it to the relevant section based on its message convention. Arguments: commit: The git commit. From 7060cbc5fc947c1571ecf902439196b52d6c9809 Mon Sep 17 00:00:00 2001 From: dummy Date: Mon, 11 Mar 2024 12:47:11 +0100 Subject: [PATCH 10/19] fixup! fix: Assign commits to versions following their commit graph --- src/git_changelog/build.py | 69 +++++++++++++++++-------------------- src/git_changelog/commit.py | 14 ++++++-- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 137f491..1cb76bd 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -144,12 +144,12 @@ def add_commit(self, commit: Commit) -> None: """ self.commits.append(commit) commit.version = self.tag - commit_type = commit.convention.get("type") - if commit_type not in self.sections_dict: - section = Section(section_type=commit_type) - self.sections_list.append(section) - self.sections_dict[commit_type] = section - self.sections_dict[commit_type].commits.append(commit) + if commit_type := commit.convention.get("type"): + if commit_type not in self.sections_dict: + section = Section(section_type=commit_type) + self.sections_list.append(section) + self.sections_dict[commit_type] = section + self.sections_dict[commit_type].commits.append(commit) class Changelog: @@ -252,7 +252,9 @@ def __init__( # get git log and parse it into list of commits self.raw_log: str = self.get_log() - self.tag_commits: list[Commit] = self.parse_commits() + self.commits: list[Commit] = self.parse_commits() + self.tag_commits: list[Commit] = [commit for commit in self.commits[1:] if commit.tag] + self.tag_commits.insert(0, self.commits[0]) # apply dates to commits and group them by version v_list, v_dict = self._group_commits_by_version() @@ -335,19 +337,20 @@ def parse_commits(self) -> list[Commit]: The list of commits. """ lines = self.raw_log.split("\n") - size = len(lines) - 1 # don't count last blank line - commits: list[Commit] = [] - tag_commits: list[Commit] = [] + size = len(lines) - 1 # Don't count last blank line. pos = 0 + + commits_map: dict[str, Commit] = {} + while pos < size: - # build body + # Build message body. nbl_index = 10 body = [] while lines[pos + nbl_index] != self.MARKER: body.append(lines[pos + nbl_index].strip("\r")) nbl_index += 1 - # build commit + # Build commit object. commit = Commit( commit_hash=lines[pos], author_name=lines[pos + 1], @@ -358,6 +361,7 @@ def parse_commits(self) -> list[Commit]: committer_date=lines[pos + 6], refs=lines[pos + 7], parent_hashes=lines[pos + 8], + commits_map=commits_map, subject=lines[pos + 9], body=body, parse_trailers=self.parse_trailers, @@ -365,35 +369,21 @@ def parse_commits(self) -> list[Commit]: pos += nbl_index + 1 - # find all commits that have this commit as a parent to build the commit graph - # TODO could be made more performant by keeping track which commits do have missing parent commits and only iterate over those - for child_commit in commits: - if commit.hash in child_commit.parent_hashes: - index = child_commit.parent_hashes.index(commit.hash) - child_commit.parent_commits.insert(index, commit) - - # expand commit object with provider parsing + # Expand commit object with provider parsing. if self.provider: commit.update_with_provider(self.provider, parse_refs=self.parse_provider_refs) - # set the commit url based on remote_url (could be wrong) + # Set the commit url based on remote_url (could be wrong). elif self.remote_url: commit.url = self.remote_url + "/commit/" + commit.hash - # expand commit object with convention parsing + # Expand commit object with convention parsing. if self.convention: commit.update_with_convention(self.convention) - commits.append(commit) - - if commit.tag: - tag_commits.append(commit) + commits_map[commit.hash] = commit - # Add first commit to result, if it is part of an unreleased version - if not commits[0].tag: - tag_commits.insert(0, commits[0]) - - return tag_commits + return list(commits_map.values()) def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]: """Groups commits into versions. @@ -408,6 +398,7 @@ def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]: """ versions_dict: dict[str, Version] = {} versions_list: list[Version] = [] + # Iterate in reversed order (oldest to newest tag) to assigns commits to the first version it was released with for tag_commit in reversed(self.tag_commits): # Create new version object @@ -417,12 +408,13 @@ def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]: # Find all commits for this version by following the commit graph version.add_commit(tag_commit) - next_commits = tag_commit.parent_commits.copy() - while len(next_commits) > 0: + next_commits = tag_commit.parent_commits # Always new: we can mutate it. + while next_commits: next_commit = next_commits.pop(0) if not next_commit.tag and not next_commit.version: version.add_commit(next_commit) next_commits.extend(next_commit.parent_commits) + return versions_list, versions_dict def _create_version(self, commit: Commit) -> Version: @@ -433,24 +425,25 @@ def _create_version(self, commit: Commit) -> Version: return version def _assign_previous_versions(self) -> None: - """Assign each version its previous version and creates the compare url. + """Assign each version its previous version and create the compare URL. The previous version is defined as the first version, that is found by following the left branch of the commit graph. If no previous version is found, either because it is the first commit or - due to the commit filter excluding it, the compare url is created with the + due to the commit filter excluding it, the compare URL is created with the first commit (oldest). """ for version in self.versions_list: next_commit = version.commits[0] previous_version: str = "" while not previous_version: - if len(next_commit.parent_commits) == 0: + parent_commits = next_commit.parent_commits + if not parent_commits: previous_version = next_commit.hash else: - # Only follow the left branch of the commit tree - next_commit = next_commit.parent_commits[0] + # Only follow the left branch of the commit tree. + next_commit = parent_commits[0] if next_commit.tag: previous_version = next_commit.tag if self.provider: diff --git a/src/git_changelog/commit.py b/src/git_changelog/commit.py index 683a926..f96da2b 100644 --- a/src/git_changelog/commit.py +++ b/src/git_changelog/commit.py @@ -34,12 +34,13 @@ def __init__( committer_email: str = "", committer_date: str | datetime = "", refs: str = "", - parent_hashes: str | list[str] = "", subject: str = "", body: list[str] | None = None, url: str = "", *, parse_trailers: bool = False, + parent_hashes: str | list[str] = "", + commits_map: dict[str, Commit] | None = None, ): """Initialization method. @@ -90,7 +91,7 @@ def __init__( self.parent_hashes = parent_hashes.split(" ") else: self.parent_hashes = parent_hashes - self.parent_commits: list[Commit] = [] + self._commits_map = commits_map self.text_refs: dict[str, list[Ref]] = {} self.convention: dict[str, Any] = {} @@ -101,6 +102,15 @@ def __init__( if parse_trailers: self._parse_trailers() + @property + def parent_commits(self) -> list[Commit]: + """Parent commits of this commit.""" + if not self._commits_map: + return [] + return [ + self._commits_map[parent_hash] for parent_hash in self.parent_hashes if parent_hash in self._commits_map + ] + def update_with_convention(self, convention: CommitConvention) -> None: """Apply the convention-parsed data to this commit. From aa6b1f12350d6e5e7e0317a79745a1b3ba491329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 11 Mar 2024 13:08:12 +0100 Subject: [PATCH 11/19] Apply suggestions from code review --- src/git_changelog/build.py | 15 +++++++-------- src/git_changelog/commit.py | 5 ++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 1cb76bd..7e51992 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -386,27 +386,26 @@ def parse_commits(self) -> list[Commit]: return list(commits_map.values()) def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]: - """Groups commits into versions. + """Group commits into versions. - Commits are assigned to the version, it was first released with. + Commits are assigned to the version they were first released with. A commit is assigned to exactly one version. Returns: - A tuple of: - - The list of versions order descending by timestamp - - A dict of versions with the tagname as keys + versions_list: The list of versions order descending by timestamp. + versions_dict: A dictionary of versions with the tag name as keys. """ versions_dict: dict[str, Version] = {} versions_list: list[Version] = [] - # Iterate in reversed order (oldest to newest tag) to assigns commits to the first version it was released with + # Iterate in reversed order (oldest to newest tag) to assign commits to the first version they were released with. for tag_commit in reversed(self.tag_commits): - # Create new version object + # Create new version object. version = self._create_version(tag_commit) versions_dict[tag_commit.version] = version versions_list.insert(0, version) - # Find all commits for this version by following the commit graph + # Find all commits for this version by following the commit graph. version.add_commit(tag_commit) next_commits = tag_commit.parent_commits # Always new: we can mutate it. while next_commits: diff --git a/src/git_changelog/commit.py b/src/git_changelog/commit.py index f96da2b..d841e68 100644 --- a/src/git_changelog/commit.py +++ b/src/git_changelog/commit.py @@ -88,9 +88,8 @@ def __init__( self.version: str = tag if isinstance(parent_hashes, str): - self.parent_hashes = parent_hashes.split(" ") - else: - self.parent_hashes = parent_hashes + parent_hashes = parent_hashes.split() + self.parent_hashes = parent_hashes self._commits_map = commits_map self.text_refs: dict[str, list[Ref]] = {} From dc9c8ea44f1541e85d2727ac3eb5d38b2ebd862d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 11 Mar 2024 13:15:13 +0100 Subject: [PATCH 12/19] Apply suggestions from code review --- tests/helpers.py | 2 +- tests/test_build.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index c52e295..10c5c12 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -68,7 +68,7 @@ def tag(self, tagname: str) -> None: self.git("tag", tagname) def branch(self, branchname: str) -> None: - """Create a new branch in the GIt repository. + """Create a new branch in the Git repository. Parameters: branchname: The name of the new branch. diff --git a/tests/test_build.py b/tests/test_build.py index 02cdf0d..f5a5599 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -159,7 +159,9 @@ def test_two_release_branches(repo: GitRepo) -> None: commit_d = repo.commit("fix: C") repo.tag("1.1.0") repo.checkout("develop") - sleep(1) # Git timestamp only has second precision, delay commit to ensure git log lists it before commit C + # Git timestamp only has second precision, + # so we delay to ensure git-log lists it before commit C. + sleep(1) commit_e = repo.merge("main") repo.tag("2.0.0") commit_f = repo.commit("feat: F") From 72ac517f3b4fb7f771f681d36f243e8bc7eee7b6 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Tue, 12 Mar 2024 06:54:51 +0100 Subject: [PATCH 13/19] fixup! fix: Always assign previous version (does not depend on provider being present) --- src/git_changelog/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 7e51992..060ed51 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -450,7 +450,7 @@ def _assign_previous_versions(self) -> None: base=previous_version, target=version.tag or "HEAD", ) - version.previous_version = self.versions_dict.get(previous_version) + version.previous_version = self.versions_dict.get(previous_version) def _bump(self, version: str) -> None: last_version = self.versions_list[0] From 7e0bcf2ccc0ce5f74f5ea34f7416fdd8c3682dc7 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Tue, 12 Mar 2024 06:59:32 +0100 Subject: [PATCH 14/19] fix: Compute previous version by using the highest semver found in the commit graph --- src/git_changelog/build.py | 54 +++++++++++++++++++++++++------------- tests/test_build.py | 9 +++++-- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 060ed51..e048c00 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, ClassVar, Literal, Type, Union from urllib.parse import urlsplit, urlunsplit +from semver import Version as SemverVersion from semver import VersionInfo from git_changelog.commit import ( @@ -38,12 +39,7 @@ def bump(version: str, part: Literal["major", "minor", "patch"] = "patch", *, ze Returns: The bumped version. """ - prefix = "" - if version[0] == "v": - prefix = "v" - version = version[1:] - - semver_version = VersionInfo.parse(version) + semver_version, prefix = parse_version(version) if part == "major" and (semver_version.major != 0 or not zerover): semver_version = semver_version.bump_major() elif part == "minor" or (part == "major" and semver_version.major == 0): @@ -53,6 +49,23 @@ def bump(version: str, part: Literal["major", "minor", "patch"] = "patch", *, ze return prefix + str(semver_version) +def parse_version(version: str) -> tuple[SemverVersion, str]: + """Parse a version. + + Arguments: + version: The version to parse. + + Returns: + semver_version: The semantic version. + prefix: The version prefix. + """ + prefix = "" + if version[0] == "v": + prefix = "v" + version = version[1:] + return VersionInfo.parse(version), prefix + + class Section: """A list of commits grouped by section_type.""" @@ -426,25 +439,30 @@ def _create_version(self, commit: Commit) -> Version: def _assign_previous_versions(self) -> None: """Assign each version its previous version and create the compare URL. - The previous version is defined as the first version, that is found - by following the left branch of the commit graph. + The previous version is defined as the version with the highest semantic version, + that is found by following the the commit graph. If no previous version is found, either because it is the first commit or due to the commit filter excluding it, the compare URL is created with the first commit (oldest). """ for version in self.versions_list: - next_commit = version.commits[0] - previous_version: str = "" - while not previous_version: - parent_commits = next_commit.parent_commits - if not parent_commits: - previous_version = next_commit.hash - else: - # Only follow the left branch of the commit tree. - next_commit = parent_commits[0] - if next_commit.tag: + next_commits = version.commits[0].parent_commits.copy() + previous_semver: SemverVersion | None = None + previous_version = "" + while len(next_commits) > 0: + next_commit = next_commits.pop(0) + if next_commit.tag: + semver, _ = parse_version(next_commit.tag) + if not previous_semver or semver.compare(previous_semver) > 0: + previous_semver = semver previous_version = next_commit.tag + else: + next_commits = next_commit.parent_commits + next_commits + + if not previous_version: + previous_version = version.commits[-1].hash + if self.provider: version.compare_url = self.provider.get_compare_url( base=previous_version, diff --git a/tests/test_build.py b/tests/test_build.py index f5a5599..0532b4c 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -172,8 +172,13 @@ def test_two_release_branches(repo: GitRepo) -> None: assert len(changelog.versions_list) == 4 versions = iter(changelog.versions_list) - _assert_version(next(versions), expected_tag="", expected_prev_tag="1.1.0", expected_commits=[commit_g, commit_f]) - _assert_version(next(versions), expected_tag="2.0.0", expected_prev_tag=None, expected_commits=[commit_e, commit_c]) + _assert_version(next(versions), expected_tag="", expected_prev_tag="2.0.0", expected_commits=[commit_g, commit_f]) + _assert_version( + next(versions), + expected_tag="2.0.0", + expected_prev_tag="1.1.0", + expected_commits=[commit_e, commit_c], + ) _assert_version(next(versions), expected_tag="1.1.0", expected_prev_tag="1.0.0", expected_commits=[commit_d]) _assert_version(next(versions), expected_tag="1.0.0", expected_prev_tag=None, expected_commits=[commit_b, commit_a]) From 9a997e795033d821cec314d70ad286d4d95bf830 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Tue, 12 Mar 2024 07:00:58 +0100 Subject: [PATCH 15/19] fixup! Read Version.next_version field --- src/git_changelog/build.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index e048c00..881b5cc 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -111,6 +111,7 @@ def __init__( self.url: str = url self.compare_url: str = compare_url self.previous_version: Version | None = None + self.next_version: Version | None = None self.planned_tag: str | None = None @property @@ -463,12 +464,14 @@ def _assign_previous_versions(self) -> None: if not previous_version: previous_version = version.commits[-1].hash + version.previous_version = self.versions_dict.get(previous_version) + if version.previous_version: + version.previous_version.next_version = version if self.provider: version.compare_url = self.provider.get_compare_url( base=previous_version, target=version.tag or "HEAD", ) - version.previous_version = self.versions_dict.get(previous_version) def _bump(self, version: str) -> None: last_version = self.versions_list[0] From 1267b9a7a1a1ef952004136ca7ace842fc864578 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Wed, 13 Mar 2024 20:11:37 +0100 Subject: [PATCH 16/19] Update src/git_changelog/build.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timothée Mazzucotelli --- src/git_changelog/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 881b5cc..f856c6e 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -441,7 +441,7 @@ def _assign_previous_versions(self) -> None: """Assign each version its previous version and create the compare URL. The previous version is defined as the version with the highest semantic version, - that is found by following the the commit graph. + that is found by following the commit graph. If no previous version is found, either because it is the first commit or due to the commit filter excluding it, the compare URL is created with the From 585e820a2efb268589a01a5201b6c80821891293 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Wed, 13 Mar 2024 20:12:15 +0100 Subject: [PATCH 17/19] Update src/git_changelog/build.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timothée Mazzucotelli --- src/git_changelog/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index f856c6e..ff1549a 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -448,7 +448,7 @@ def _assign_previous_versions(self) -> None: first commit (oldest). """ for version in self.versions_list: - next_commits = version.commits[0].parent_commits.copy() + next_commits = version.commits[0].parent_commits # Always new: we can mutate it. previous_semver: SemverVersion | None = None previous_version = "" while len(next_commits) > 0: From 85c04fa06cf6a5f975c49cc79d6da775c7855b5a Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Wed, 13 Mar 2024 20:12:40 +0100 Subject: [PATCH 18/19] Update src/git_changelog/build.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timothée Mazzucotelli --- src/git_changelog/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index ff1549a..7ae7bc3 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -451,7 +451,7 @@ def _assign_previous_versions(self) -> None: next_commits = version.commits[0].parent_commits # Always new: we can mutate it. previous_semver: SemverVersion | None = None previous_version = "" - while len(next_commits) > 0: + while next_commits: next_commit = next_commits.pop(0) if next_commit.tag: semver, _ = parse_version(next_commit.tag) From 4603d80b2a9e46d26bc63189e923b7f107af1805 Mon Sep 17 00:00:00 2001 From: Christian Meffert Date: Wed, 13 Mar 2024 20:17:57 +0100 Subject: [PATCH 19/19] Exttend list of next_commits instead of creating a new list --- src/git_changelog/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git_changelog/build.py b/src/git_changelog/build.py index 7ae7bc3..741c5be 100644 --- a/src/git_changelog/build.py +++ b/src/git_changelog/build.py @@ -459,7 +459,7 @@ def _assign_previous_versions(self) -> None: previous_semver = semver previous_version = next_commit.tag else: - next_commits = next_commit.parent_commits + next_commits + next_commits.extend(next_commit.parent_commits) if not previous_version: previous_version = version.commits[-1].hash