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 all 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
26 changes: 24 additions & 2 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@
- [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

from jinja2 import Environment, PackageLoader

from commitizen import defaults
from commitizen.exceptions import InvalidConfigurationError
from commitizen.git import GitCommit, GitTag

CATEGORIES = [
Expand Down Expand Up @@ -98,7 +100,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 +130,26 @@ 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:
if len(set(change_type_order)) != len(change_type_order):
raise InvalidConfigurationError(
f"Change types contain duplicates types ({change_type_order})"
)

sorted_tree = []
for entry in tree:
ordered_change_types = change_type_order + sorted(
set(entry["changes"].keys()) - set(change_type_order)
)
changes = [
(ct, entry["changes"][ct])
for ct in ordered_change_types
if ct in entry["changes"]
]
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-]+)?)"
5 changes: 5 additions & 0 deletions commitizen/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class ExitCode(enum.IntEnum):
NO_REVISION = 16
CURRENT_VERSION_NOT_FOUND = 17
INVALID_COMMAND_ARGUMENT = 18
INVALID_CONFIGURATION = 19


class CommitizenException(Exception):
Expand Down Expand Up @@ -137,3 +138,7 @@ class NoCommandFoundError(CommitizenException):

class InvalidCommandArgumentError(CommitizenException):
exit_code = ExitCode.INVALID_COMMAND_ARGUMENT


class InvalidConfigurationError(CommitizenException):
exit_code = ExitCode.INVALID_CONFIGURATION
6 changes: 5 additions & 1 deletion 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 @@ -64,6 +65,7 @@ The equivalent example for a json config file:
"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 @@ -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
1 change: 1 addition & 0 deletions docs/exit_codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ These exit codes can be found in `commitizen/exceptions.py::ExitCode`.
| NoRevisionError | 16 | No revision found |
| CurrentVersionNotFoundError | 17 | current version cannot be found in *version_files* |
| InvalidCommandArgumentError | 18 | The argument provide to command is invalid (e.g. `cz check -commit-msg-file filename --rev-range master..`) |
| InvalidConfigurationError | 19 | An error was found in the Commitizen Configuration, such as duplicates in `change_type_order` |
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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