Skip to content

feat(template): add commit_groups filter for ordered group rendering#1518

Open
ChrisJr404 wants to merge 2 commits into
orhun:mainfrom
ChrisJr404:fix/group-order-from-parsers-9
Open

feat(template): add commit_groups filter for ordered group rendering#1518
ChrisJr404 wants to merge 2 commits into
orhun:mainfrom
ChrisJr404:fix/group-order-from-parsers-9

Conversation

@ChrisJr404
Copy link
Copy Markdown

Closes #9.

Background

commits | group_by(attribute="group") is Tera's built-in filter, and it backs the grouped result with a BTreeMap. That means groups always come out in alphabetical (or emoji-codepoint) order, regardless of the order in which they were declared in commit_parsers. The thread on #9 covers this and the workaround that's spread across the ecosystem: prefix group names with <!-- 0 -->, <!-- 1 -->, ..., then strip the comments with striptags. The project's own cliff.toml uses that workaround.

What this PR does

It leaves group_by alone (so existing templates keep working byte-for-byte) and adds a new filter, commit_groups, that returns an array of { name, commits } records instead of a map. Because the result is an array, iteration order is deterministic and template-defined.

The filter has two modes:

  • No groups argument: groups appear in the order they first occur in the commit list, which matches commit chronology.
  • With a groups=... argument (an array of group names): groups are sorted to match that order. Any group not in groups is appended afterward in first-appearance order.

To make the common case ergonomic, Changelog::build now injects the ordered list of group names from commit_parsers into the template context as commit_parsers_groups. So a user template can simply write:

{% for entry in commits | commit_groups(groups=commit_parsers_groups) %}
### {{ entry.name }}
{% for commit in entry.commits %}- {{ commit.message }}
{% endfor %}{% endfor %}

and get groups in the order they wrote them in cliff.toml, with no <!-- N --> hack and no striptags filter. With the same input, switching to the existing group_by filter still produces alphabetical order, so this is purely additive.

Reproducer from the issue

Given the configuration from arthrarnld's comment:

commit_parsers = [
  { message = "^feat", group = ":rocket: New features" },
  { message = "^fix",  group = ":bug: Bug fixes" },
  { message = "^perf", group = ":zap: Performance" },
  { message = "^chore", group = ":gear: Miscellaneous" },
]

and four commits in chore -> fix -> perf -> feat order, the existing group_by template renders:

### :bug: Bug fixes
### :gear: Miscellaneous
### :rocket: New features
### :zap: Performance

After this PR, switching the template to commit_groups(groups=commit_parsers_groups) renders:

### :rocket: New features
### :bug: Bug fixes
### :zap: Performance
### :gear: Miscellaneous

Tests

  • template::test::test_commit_groups_filter_preserves_first_appearance_when_no_groups
  • template::test::test_commit_groups_filter_uses_groups_argument
  • template::test::test_commit_groups_filter_appends_unknown_groups
  • template::test::test_commit_groups_filter_skips_null_groups
  • changelog::test::changelog_group_order_matches_commit_parsers (regression test using the issue reproducer end-to-end through Changelog::generate)

Notes

  • group_by is unchanged. Default templates are unchanged. Adoption is opt-in via the new filter.
  • The commit_parsers_groups context key is set by Changelog::build before any user-supplied context is added, so add_context("commit_parsers_groups", ...) still wins if a caller wants to override it.
  • This builds on the design discussion from the closed feat: add tera filter for ordered commit groups #339 PR (orhun's commit-groups-filter-improved branch) but keeps the state on the Template instance instead of a global static, and uses tera's filter-arg mechanism to plumb the order list through.

Test plan

  • cargo test --workspace --lib (all new tests pass; pre-existing repo::test::* failures reproduce on main and are env-dependent)
  • cargo clippy --all-targets --workspace introduces no new lints
  • cargo fmt --check introduces no new diffs against the existing baseline
  • End-to-end smoke test with the binary against a 4-commit repo confirms three modes: explicit-order (matches commit_parsers), no-args (chronology), and existing group_by (alphabetical, unchanged)

Tera's built-in `group_by(attribute="group")` filter backs its result with a
BTreeMap, so groups always come out in alphabetical (or emoji-codepoint)
order regardless of the order in which they were declared in
`commit_parsers`. This is the long-standing behavior tracked in orhun#9 and the
reason most users prefix group names with `<!-- N -->` HTML comments and
strip them with `striptags` to coerce a particular order. The project's own
`cliff.toml` does this too.

This change keeps `group_by` untouched and adds a new filter,
`commit_groups`, that yields entries as an array of `{ name, commits }`
records, so iteration order is deterministic and template-defined.

The filter has two modes:

* No `groups` argument: groups appear in the order they first occur in the
  input list, matching commit chronology.
* With a `groups=...` argument (an array of group names): groups are sorted
  to match that order, with any unlisted groups appended afterward in
  first-appearance order.

To make the common case ergonomic, `Changelog::build` now injects the
ordered list of group names from `commit_parsers` into the template context
as `commit_parsers_groups`, so users can write:

  {% for entry in commits | commit_groups(groups=commit_parsers_groups) %}
    ### {{ entry.name }}
    {% for commit in entry.commits %}- {{ commit.message }}
    {% endfor %}
  {% endfor %}

and get groups in the order they wrote them in `cliff.toml`. The default
templates and `group_by` callers are left as-is; this is purely additive.

A regression test using the reproducer from orhun#9 verifies the order, plus
unit tests cover the no-args case, the `groups=` argument, the unknown-
group fallthrough, and the null-group skip behavior.

Closes orhun#9
@ChrisJr404 ChrisJr404 requested a review from orhun as a code owner May 6, 2026 22:15
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 6, 2026

Codecov Report

❌ Patch coverage is 92.30769% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.88%. Comparing base (5d3a670) to head (9f0f501).

Files with missing lines Patch % Lines
git-cliff-core/src/changelog.rs 81.82% 2 Missing ⚠️
git-cliff-core/src/template.rs 96.43% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1518      +/-   ##
==========================================
+ Coverage   48.90%   49.88%   +0.98%     
==========================================
  Files          26       26              
  Lines        2272     2310      +38     
==========================================
+ Hits         1111     1152      +41     
+ Misses       1161     1158       -3     
Flag Coverage Δ
unit-tests 49.88% <92.31%> (+0.98%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Group order doesn't match the order of commit_parsers

2 participants