From b5125cb44540cfc24943363d6071796615a46034 Mon Sep 17 00:00:00 2001 From: sacha Date: Wed, 1 Mar 2023 12:57:36 +0100 Subject: [PATCH] fix(commands/changelog): use topological order for commit ordering Topological ordering should be used when ordering commits in changelog history. This allows commits to be shown properly in the order they were added to the codebase, even if non-linear merges were used --- commitizen/commands/changelog.py | 4 +- tests/commands/test_changelog_command.py | 78 +++++++++++++++++++++++- tests/utils.py | 25 ++++++++ 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index aab613fffc..d934ac2940 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -144,9 +144,7 @@ def __call__(self): tag_format=self.tag_format, ) - commits = git.get_commits( - start=start_rev, end=end_rev, args="--author-date-order" - ) + commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order") if not commits: raise NoCommitsFoundError("No commits found") diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py index b98ec6e503..7805c99ad5 100644 --- a/tests/commands/test_changelog_command.py +++ b/tests/commands/test_changelog_command.py @@ -13,7 +13,14 @@ NotAGitProjectError, NotAllowed, ) -from tests.utils import create_file_and_commit, wait_for_tag +from tests.utils import ( + create_branch, + create_file_and_commit, + get_current_branch, + merge_branch, + switch_branch, + wait_for_tag, +) @pytest.mark.usefixtures("tmp_commitizen_project") @@ -268,6 +275,75 @@ def test_changelog_hook_customize(mocker: MockFixture, config_customize): changelog_hook_mock.assert_called_with(full_changelog, full_changelog) +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_changelog_with_non_linear_merges_commit_order( + mocker: MockFixture, config_customize +): + """Test that commits merged non-linearly are correctly ordered in the changelog + + A typical scenario is having two branches from main like so: + * feat: I will be merged first - (2023-03-01 11:35:51 +0100) | (branchB) + | * feat: I will be merged second - (2023-03-01 11:35:22 +0100) | (branchA) + |/ + * feat: initial commit - (2023-03-01 11:34:54 +0100) | (HEAD -> main) + + And merging them, for example in the reverse order they were created on would give the following: + * Merge branch 'branchA' - (2023-03-01 11:42:59 +0100) | (HEAD -> main) + |\ + | * feat: I will be merged second - (2023-03-01 11:35:22 +0100) | (branchA) + * | feat: I will be merged first - (2023-03-01 11:35:51 +0100) | (branchB) + |/ + * feat: initial commit - (2023-03-01 11:34:54 +0100) | + + In this case we want the changelog to reflect the topological order of commits, + i.e. the order in which they were merged into the main branch + + So the above example should result in the following: + ## Unreleased + + ### Feat + - I will be merged second + - I will be merged first + - initial commit + """ + changelog_hook_mock = mocker.Mock() + changelog_hook_mock.return_value = "cool changelog hook" + + create_file_and_commit("feat: initial commit") + + main_branch = get_current_branch() + + create_branch("branchA") + create_branch("branchB") + + switch_branch("branchA") + create_file_and_commit("feat: I will be merged second") + + switch_branch("branchB") + create_file_and_commit("feat: I will be merged first") + + # Note we merge branches opposite order than author_date + switch_branch(main_branch) + merge_branch("branchB") + merge_branch("branchA") + + changelog = Changelog( + config_customize, + {"unreleased_version": None, "incremental": True, "dry_run": False}, + ) + mocker.patch.object(changelog.cz, "changelog_hook", changelog_hook_mock) + changelog() + full_changelog = "\ +## Unreleased\n\n\ +\ +### Feat\n\n\ +- I will be merged second\n\ +- I will be merged first\n\ +- initial commit\n" + + changelog_hook_mock.assert_called_with(full_changelog, full_changelog) + + @pytest.mark.usefixtures("tmp_commitizen_project") def test_changelog_multiple_incremental_do_not_add_new_lines( mocker: MockFixture, capsys, changelog_path, file_regression diff --git a/tests/utils.py b/tests/utils.py index a1e14ad88c..2efe13fa9e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -26,6 +26,31 @@ def create_file_and_commit(message: str, filename: Optional[str] = None): raise exceptions.CommitError(c.err) +def create_branch(name: str): + c = cmd.run(f"git branch {name}") + if c.return_code != 0: + raise exceptions.GitCommandError(c.err) + + +def switch_branch(branch: str): + c = cmd.run(f"git switch {branch}") + if c.return_code != 0: + raise exceptions.GitCommandError(c.err) + + +def merge_branch(branch: str): + c = cmd.run(f"git merge {branch}") + if c.return_code != 0: + raise exceptions.GitCommandError(c.err) + + +def get_current_branch() -> str: + c = cmd.run("git rev-parse --abbrev-ref HEAD") + if c.return_code != 0: + raise exceptions.GitCommandError(c.err) + return c.out + + def create_tag(tag: str): c = git.tag(tag) if c.return_code != 0: