Skip to content

feat(#319): add optional change_type_order #323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 1, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
- [x] hook after changelog is generated (api calls)
- [x] add support for change_type maps
"""

import os
import re
from collections import defaultdict
from collections import OrderedDict, defaultdict
from datetime import date
from typing import Callable, Dict, Iterable, List, Optional

Expand Down Expand Up @@ -98,7 +99,7 @@ def generate_tree_from_commits(
"date": current_tag_date,
"changes": changes,
}
# TODO: Check if tag matches the version pattern, otherwie skip it.
# TODO: Check if tag matches the version pattern, otherwise skip it.
# This in order to prevent tags that are not versions.
current_tag_name = commit_tag.name
current_tag_date = commit_tag.date
Expand Down Expand Up @@ -128,6 +129,19 @@ def generate_tree_from_commits(
yield {"version": current_tag_name, "date": current_tag_date, "changes": changes}


def order_changelog_tree(tree: Iterable, change_type_order: List[str]) -> Iterable:
sorted_tree = []
for entry in tree:
entry_change_types = sorted(entry["changes"].keys())
ordered_change_types = []
for ct in change_type_order + entry_change_types:
if ct in entry_change_types and ct not in ordered_change_types:
ordered_change_types.append(ct)
changes = [(ct, entry["changes"][ct]) for ct in ordered_change_types]
sorted_tree.append({**entry, **{"changes": OrderedDict(changes)}})
return sorted_tree


def render_changelog(tree: Iterable) -> str:
loader = PackageLoader("commitizen", "templates")
env = Environment(loader=loader, trim_blocks=True)
Expand Down
5 changes: 5 additions & 0 deletions commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ def __init__(self, config: BaseConfig, args):
self.change_type_map = (
self.config.settings.get("change_type_map") or self.cz.change_type_map
)
self.change_type_order = (
self.config.settings.get("change_type_order") or self.cz.change_type_order
)

def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
"""Try to find the 'start_rev'.
Expand Down Expand Up @@ -109,6 +112,8 @@ def __call__(self):
change_type_map=change_type_map,
changelog_message_builder_hook=changelog_message_builder_hook,
)
if self.change_type_order:
tree = changelog.order_changelog_tree(tree, self.change_type_order)
changelog_out = changelog.render_changelog(tree)
changelog_out = changelog_out.lstrip("\n")

Expand Down
1 change: 1 addition & 0 deletions commitizen/cz/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class BaseCommitizen(metaclass=ABCMeta):
commit_parser: Optional[str] = r"(?P<message>.*)"
changelog_pattern: Optional[str] = r".*"
change_type_map: Optional[Dict[str, str]] = None
change_type_order: Optional[List[str]] = None

# Executed per message parsed by the commitizen
changelog_message_builder_hook: Optional[
Expand Down
5 changes: 5 additions & 0 deletions commitizen/cz/customize/customize.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
class CustomizeCommitsCz(BaseCommitizen):
bump_pattern = defaults.bump_pattern
bump_map = defaults.bump_map
change_type_order = defaults.change_type_order

def __init__(self, config: BaseConfig):
super(CustomizeCommitsCz, self).__init__(config)
Expand All @@ -32,6 +33,10 @@ def __init__(self, config: BaseConfig):
if custom_bump_map:
self.bump_map = custom_bump_map

custom_change_type_order = self.custom_settings.get("change_type_order")
if custom_change_type_order:
self.change_type_order = custom_change_type_order

def questions(self) -> List[Dict[str, Any]]:
return self.custom_settings.get("questions")

Expand Down
2 changes: 2 additions & 0 deletions commitizen/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,7 @@
)
bump_message = "bump: version $current_version → $new_version"

change_type_order = ["BREAKING CHANGE", "feat", "fix", "refactor", "perf"]

commit_parser = r"^(?P<change_type>feat|fix|refactor|perf|BREAKING CHANGE)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?:\s(?P<message>.*)?" # noqa
version_parser = r"(?P<version>([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?)"
10 changes: 7 additions & 3 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ schema = "<type>: <body>"
schema_pattern = "(feature|bug fix):(\\s.*)"
bump_pattern = "^(break|new|fix|hotfix)"
bump_map = {"break" = "MAJOR", "new" = "MINOR", "fix" = "PATCH", "hotfix" = "PATCH"}
change_type_order = ["BREAKING CHANGE", "feat", "fix", "refactor", "perf"]
info_path = "cz_customize_info.txt"
info = """
This is customized info
Expand Down Expand Up @@ -51,7 +52,7 @@ The equivalent example for a json config file:
```json
{
"commitizen": {
"name": "cz_customize",
"name": "cz_customize",
"customize": {
"message_template": "{{change_type}}:{% if show_message %} {{message}}{% endif %}",
"example": "feature: this feature enable customize through config file",
Expand All @@ -64,7 +65,8 @@ The equivalent example for a json config file:
"fix": "PATCH",
"hotfix": "PATCH"
},
"info_path": "cz_customize_info.txt",
"change_type_order": ["BREAKING CHANGE", "feat", "fix", "refactor", "perf"],
"info_path": "cz_customize_info.txt",
"info": "This is customized info",
"questions": [
{
Expand Down Expand Up @@ -114,6 +116,7 @@ commitizen:
new: MINOR
fix: PATCH
hotfix: PATCH
change_type_order: ["BREAKING CHANGE", "feat", "fix", "refactor", "perf"]
info_path: cz_customize_info.txt
info: This is customized info
questions:
Expand Down Expand Up @@ -146,6 +149,7 @@ commitizen:
| `info` | `str` | `None` | (OPTIONAL) Explanation of the commit rules. Used by `cz info`. |
| `bump_map` | `dict` | `None` | (OPTIONAL) Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`) |
| `bump_pattern` | `str` | `None` | (OPTIONAL) Regex to extract information from commit (subject and body) |
| `change_type_order`| `str` | `None` | (OPTIONAL) List of strings used to order the Changelog. All other types will be sorted alphabetically. Default is `["BREAKING CHANGE", "feat", "fix", "refactor", "perf"]` |

#### Detailed `questions` content

Expand Down Expand Up @@ -298,7 +302,7 @@ You can customize it of course, and this are the variables you need to add to yo
| Parameter | Type | Required | Description |
| -------------------------------- | ------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `commit_parser` | `str` | NO | Regex which should provide the variables explained in the [changelog description][changelog-des] |
| `changelog_pattern` | `str` | NO | Regex to validate the commits, this is useful to skip commits that don't meet your rulling standards like a Merge. Usually the same as bump_pattern |
| `changelog_pattern` | `str` | NO | Regex to validate the commits, this is useful to skip commits that don't meet your ruling standards like a Merge. Usually the same as bump_pattern |
| `change_type_map` | `dict` | NO | Convert the title of the change type that will appear in the changelog, if a value is not found, the original will be provided |
| `changelog_message_builder_hook` | `method: (dict, git.GitCommit) -> dict` | NO | Customize with extra information your message output, like adding links, this function is executed per parsed commit. Each GitCommit contains the following attrs: `rev`, `title`, `body`, `author`, `author_email` |
| `changelog_hook` | `method: (full_changelog: str, partial_changelog: Optional[str]) -> str` | NO | Receives the whole and partial (if used incremental) changelog. Useful to send slack messages or notify a compliance department. Must return the full_changelog |
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ freezegun = "^0.3.15"
pydocstyle = "^5.0.2"
pre-commit = "^2.6.0"

# FIXME: Remove for submission (testing issues/319-only)
pytest-watch = "*"

[tool.poetry.scripts]
cz = "commitizen.cli:main"
git-cz = "commitizen.cli:main"
Expand All @@ -81,6 +84,7 @@ include_trailing_comma = true
force_grid_wrap = 0
combine_as_imports = true
line_length = 88
known_first_party = ["commitizen", "tests"]

[tool.coverage]
[tool.coverage.report]
Expand Down
1 change: 1 addition & 0 deletions tests/commands/test_changelog_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ def test_changelog_hook(mocker, config):
create_file_and_commit("refactor: is in changelog")
create_file_and_commit("Merge into master")

config.settings["change_type_order"] = ["Refactor", "Feat"]
changelog = Changelog(
config, {"unreleased_version": None, "incremental": True, "dry_run": False}
)
Expand Down
Loading