Skip to content

Commit b5125cb

Browse files
committed
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
1 parent 9b5f311 commit b5125cb

File tree

3 files changed

+103
-4
lines changed

3 files changed

+103
-4
lines changed

commitizen/commands/changelog.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,7 @@ def __call__(self):
144144
tag_format=self.tag_format,
145145
)
146146

147-
commits = git.get_commits(
148-
start=start_rev, end=end_rev, args="--author-date-order"
149-
)
147+
commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order")
150148
if not commits:
151149
raise NoCommitsFoundError("No commits found")
152150

tests/commands/test_changelog_command.py

+77-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@
1313
NotAGitProjectError,
1414
NotAllowed,
1515
)
16-
from tests.utils import create_file_and_commit, wait_for_tag
16+
from tests.utils import (
17+
create_branch,
18+
create_file_and_commit,
19+
get_current_branch,
20+
merge_branch,
21+
switch_branch,
22+
wait_for_tag,
23+
)
1724

1825

1926
@pytest.mark.usefixtures("tmp_commitizen_project")
@@ -268,6 +275,75 @@ def test_changelog_hook_customize(mocker: MockFixture, config_customize):
268275
changelog_hook_mock.assert_called_with(full_changelog, full_changelog)
269276

270277

278+
@pytest.mark.usefixtures("tmp_commitizen_project")
279+
def test_changelog_with_non_linear_merges_commit_order(
280+
mocker: MockFixture, config_customize
281+
):
282+
"""Test that commits merged non-linearly are correctly ordered in the changelog
283+
284+
A typical scenario is having two branches from main like so:
285+
* feat: I will be merged first - (2023-03-01 11:35:51 +0100) | (branchB)
286+
| * feat: I will be merged second - (2023-03-01 11:35:22 +0100) | (branchA)
287+
|/
288+
* feat: initial commit - (2023-03-01 11:34:54 +0100) | (HEAD -> main)
289+
290+
And merging them, for example in the reverse order they were created on would give the following:
291+
* Merge branch 'branchA' - (2023-03-01 11:42:59 +0100) | (HEAD -> main)
292+
|\
293+
| * feat: I will be merged second - (2023-03-01 11:35:22 +0100) | (branchA)
294+
* | feat: I will be merged first - (2023-03-01 11:35:51 +0100) | (branchB)
295+
|/
296+
* feat: initial commit - (2023-03-01 11:34:54 +0100) |
297+
298+
In this case we want the changelog to reflect the topological order of commits,
299+
i.e. the order in which they were merged into the main branch
300+
301+
So the above example should result in the following:
302+
## Unreleased
303+
304+
### Feat
305+
- I will be merged second
306+
- I will be merged first
307+
- initial commit
308+
"""
309+
changelog_hook_mock = mocker.Mock()
310+
changelog_hook_mock.return_value = "cool changelog hook"
311+
312+
create_file_and_commit("feat: initial commit")
313+
314+
main_branch = get_current_branch()
315+
316+
create_branch("branchA")
317+
create_branch("branchB")
318+
319+
switch_branch("branchA")
320+
create_file_and_commit("feat: I will be merged second")
321+
322+
switch_branch("branchB")
323+
create_file_and_commit("feat: I will be merged first")
324+
325+
# Note we merge branches opposite order than author_date
326+
switch_branch(main_branch)
327+
merge_branch("branchB")
328+
merge_branch("branchA")
329+
330+
changelog = Changelog(
331+
config_customize,
332+
{"unreleased_version": None, "incremental": True, "dry_run": False},
333+
)
334+
mocker.patch.object(changelog.cz, "changelog_hook", changelog_hook_mock)
335+
changelog()
336+
full_changelog = "\
337+
## Unreleased\n\n\
338+
\
339+
### Feat\n\n\
340+
- I will be merged second\n\
341+
- I will be merged first\n\
342+
- initial commit\n"
343+
344+
changelog_hook_mock.assert_called_with(full_changelog, full_changelog)
345+
346+
271347
@pytest.mark.usefixtures("tmp_commitizen_project")
272348
def test_changelog_multiple_incremental_do_not_add_new_lines(
273349
mocker: MockFixture, capsys, changelog_path, file_regression

tests/utils.py

+25
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,31 @@ def create_file_and_commit(message: str, filename: Optional[str] = None):
2626
raise exceptions.CommitError(c.err)
2727

2828

29+
def create_branch(name: str):
30+
c = cmd.run(f"git branch {name}")
31+
if c.return_code != 0:
32+
raise exceptions.GitCommandError(c.err)
33+
34+
35+
def switch_branch(branch: str):
36+
c = cmd.run(f"git switch {branch}")
37+
if c.return_code != 0:
38+
raise exceptions.GitCommandError(c.err)
39+
40+
41+
def merge_branch(branch: str):
42+
c = cmd.run(f"git merge {branch}")
43+
if c.return_code != 0:
44+
raise exceptions.GitCommandError(c.err)
45+
46+
47+
def get_current_branch() -> str:
48+
c = cmd.run("git rev-parse --abbrev-ref HEAD")
49+
if c.return_code != 0:
50+
raise exceptions.GitCommandError(c.err)
51+
return c.out
52+
53+
2954
def create_tag(tag: str):
3055
c = git.tag(tag)
3156
if c.return_code != 0:

0 commit comments

Comments
 (0)