diff --git a/.github/workflows/bumpversion.yml b/.github/workflows/bumpversion.yml
index f7559b420b..90c8af6373 100644
--- a/.github/workflows/bumpversion.yml
+++ b/.github/workflows/bumpversion.yml
@@ -32,7 +32,7 @@ jobs:
git pull origin master --tags
- name: Create bump
run: |
- cz bump --yes
+ cz bump --yes --changelog
git tag
- name: Push changes
uses: Woile/github-push-action@master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7d614abaf..b8736827ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,139 +1,504 @@
-# CHANGELOG
-## v1.12.0
-### Feature
+## Unreleased
+
+### Refactor
+
+- **changelog**: rename category to change_type to fit 'keep a changelog'
+- **templates**: rename as "keep_a_changelog_template.j2"
+- **templates**: remove unneeded __init__ file
+- **cli**: reorder commands
+- **templates**: move changelog_template from cz to templates
+- **tests/utils**: move create_file_and_commit to tests/utils
+- **commands/changelog**: remove redundant if statement
+- **commands/changelog**: use jinja2 template instead of string concatenation to build changelog
+
+### Fix
+
+- **cz/conventional_commits**: fix schema_pattern break due to rebase
+- **changelog_template**: fix list format
+- **commitizen/cz**: set changelog_map, changelog_pattern to none as default
+- **commands/changelog**: remove --skip-merge argument
+- **cli**: add changelog arguments
+
+### Feat
+
+- **commands/changelog**: make changelog_file an option in config
+- **commands/changelog**: exit when there is no commit exists
+- **commands/changlog**: add --start-rev argument to `cz changelog`
+- **changelog**: generate changelog based on git log
+- **commands/changelog**: generate changelog_tree from all past commits
+- **cz/conventinal_commits**: add changelog_map, changelog_pattern and implement process_commit
+- **cz/base**: add default process_commit for processing commit message
+- **changelog**: changelog tree generation from markdown
+
+## v1.17.0 (2020-03-15)
+
+### Refactor
+
+- **tests/bump**: use parameterize to group similliar tests
+- **cz/connventional_commit**: use \S to check scope
+- **git**: remove unnecessary dot between git range
+
+### Fix
+
+- **bump**: fix bump find_increment error
+
+### Feat
+
+- **commands/check**: add --rev-range argument for checking commits within some range
+
+## v1.16.4 (2020-03-03)
+
+### Fix
+
+- **commands/init**: fix clean up file when initialize commitizen config
+
+### Refactor
+
+- **defaults**: split config files into long term support and deprecated ones
+
+## v1.16.3 (2020-02-20)
+
+### Fix
+
+- replace README.rst with docs/README.md in config files
+
+### Refactor
+
+- **docs**: remove README.rst and use docs/README.md
+
+## v1.16.2 (2020-02-01)
+
+### Fix
+
+- **commands/check**: add bump into valid commit message of convention commit pattern
+
+## v1.16.1 (2020-02-01)
+
+### Fix
+
+- **pre-commit**: set pre-commit check stage to commit-msg
+
+## v1.16.0 (2020-01-21)
+
+### Refactor
+
+- **commands/bump**: rename parameter into bump_setting to distinguish bump_setting and argument
+- **git**: rename get tag function to distinguish return str and GitTag
+- **cmd**: reimplement how cmd is run
+- **git**: Use GitCommit, GitTag object to store commit and git information
+- **git**: make arguments other then start and end in get_commit keyword arguments
+- **git**: Change get_commits into returning commits instead of lines of messsages
+
+### Feat
+
+- **git**: get_commits default from first_commit
+
+## v1.15.1 (2020-01-20)
+
+## v1.15.0 (2020-01-20)
+
+### Refactor
+
+- **tests/commands/bump**: use tmp_dir to replace self implemented tmp dir behavior
+- **test_bump_command**: rename camel case variables
+- **tests/commands/check**: use pytest fixture tmpdir replace self implemented contextmanager
+- **test/commands/other**: replace unit test style mock with mocker fixture
+- **tests/commands**: separate command unit tests into modules
+- **tests/commands**: make commands related tests a module
+- **git**: make find_git_project_root return None if it's not a git project
+- **config/base_config**: make set_key not implemented
+- **error_codes**: move all the error_codes to a module
+- **config**: replace string type path with pathlib.Path
+
+### Fix
+
+- **cli**: fix --version not functional
+- **git**: remove breakline in the return value of find_git_project_root
+
+### Feat
+
+- **config**: look up configuration in git project root
+- **git**: add find_git_project_root
+
+## v1.14.2 (2020-01-14)
+
+### Fix
+
+- **github_workflow/pythonpublish**: use peaceiris/actions-gh-pages@v2 to publish docs
+
+## v1.14.1 (2020-01-11)
+
+## v1.14.0 (2020-01-06)
+
+### Refactor
+
+- **pre-commit-hooks**: add metadata for the check hook
+
+### Feat
+
+- **pre-commit-hooks**: add pre-commit hook
+
+### Fix
+
+- **cli**: fix the way default handled for name argument
+- **cli**: fix name cannot be overwritten through config in newly refactored config design
+
+## v1.13.1 (2019-12-31)
+
+### Fix
+
+- **github_workflow/pythonpackage**: set git config for unit testing
+- **scripts/test**: ensure the script fails once the first failure happens
+
+## v1.13.0 (2019-12-30)
+
+### Feat
+
+- add project version to command init
+
+## v1.12.0 (2019-12-30)
+
+### Feat
- new init command
-## v1.11.0
+## v1.10.3 (2019-12-29)
-Ignore this version
+### Refactor
-## v1.10.0
+- **commands/bump**: use "version_files" internally
+- **config**: set "files" to alias of "version_files"
-### Feature
+## v1.10.2 (2019-12-27)
-- new argument `--files-only` in bump
+### Refactor
-## v1.9.2
+- new config system where each config type has its own class
+- **config**: add type annotation to config property
+- **config**: fix wrongly type annoated functions
+- **config/ini_config**: move deprecation warning into class initialization
+- **config**: use add_path instead of directly assigning _path
+- **all**: replace all the _settings invoke with settings.update
+- **cz/customize**: remove unnecessary statement "raise NotImplementedError("Not Implemented yet")"
+- **config**: move default settings back to defaults
+- **config**: Make config a class and each type of config (e.g., toml, ini) a child class
### Fix
-- `--commit-msg-file` is now a required argument
+- **config**: handle empty config file
+- **config**: fix load global_conf even if it doesn't exist
+- **config/ini_config**: replase outdated _parse_ini_settings with _parse_settings
-## v1.9.1
+## v1.10.1 (2019-12-10)
### Fix
-- exception `AnswerRequiredException` not caught
+- **cli**: overwrite "name" only when it's not given
+- **config**: fix typo
-## v1.9.0
+## v1.10.0 (2019-11-28)
-### Feature
+### Feat
-- new `version` command. `--version` will be deprecated in `2.0.0`
-- new `git-cz` entrypoint. After installing `commitizen` you can run `git cz c` (#60)
-- new `--dry-run` argument in `commit` (#56)
-- new `cz check` command which checks if the message is valid with the rules (#59). Useful for git hooks.
-- create a commiting rule directly in the config file (#54)
-- support for multi-line body (#6)
-- support for jinja templates. Install doign `pip install -U commitizen[jinja2]`.
-- support for `.cz.toml`. The confs depending on `ConfigParser` will be deprecated in `2.0.0`.
+- support for different commitizens in `cz check`
+- **bump**: new argument --files-only
+## v1.9.2 (2019-11-23)
### Fix
-- tests were fixed
-- windows error when removing folders (#67)
-- typos in docs
+- **commands/check.py**: --commit-msg-file is now a required argument
-### Docs
-- tutorial for gitlab ci
-- tutorial for github actions
+## v1.9.1 (2019-11-23)
-## v1.8.0
+### Fix
-### Feature
+- **cz/exceptions**: exception AnswerRequiredException not caught (#89)
-- new custom exception for commitizen
-- commit is aborted if nothing in staging
+## v1.9.0 (2019-11-22)
-## v1.7.0
+### Feat
-### Feature
+- **Commands/check**: enforce the project to always use conventional commits
+- **config**: add deprecation warning for loading config from ini files
+- **cz/customize**: add jinja support to enhance template flexibility
+- **cz/filters**: add required_validator and multiple_line_breaker
+- **Commands/commit**: add ´--dry-run´ flag to the Commit command
+- **cz/cz_customize**: implement info to support info and info_path
+- **cz/cz_customize**: enable bump_pattern bump_map customization
+- **cz/cz_customize**: implement customizable cz
+- new 'git-cz' entrypoint
-- new styles for the prompt
-- new configuration option for the prompt styles
+### Refactor
-## v1.6.0
+- **config**: remove has_pyproject which is no longer used
+- **cz/customize**: make jinja2 a custom requirement. if not installed use string.Tempalte instead
+- **cz/utils**: rename filters as utils
+- **cli**: add back --version and remove subcommand required constraint
-### Feature
+### Fix
-- new retry argument to execute previous commit again
+- commit dry-run doesnt require staging to be clean
+- correct typo to spell "convention"
+- removing folder in windows throwing a PermissionError
+- **scripts**: add back the delelte poetry prefix
+- **test_cli**: testing the version command
-## v1.5.1
+## v1.8.0 (2019-11-12)
### Fix
-- issue in poetry add preventing the installation in py36
+- **commands/commit**: catch exception raised by customization cz
+- **cli**: handle the exception that command is not given
+- **cli**: enforce subcommand as required
+
+### Refactor
+
+- **cz/conventional_commit**: make NoSubjectException inherit CzException and add error message
+- **command/version**: use out.write instead of out.line
+- **command**: make version a command instead of an argument
+
+### Feat
+
+- **cz**: add a base exception for cz customization
+- **commands/commit**: abort commit if there is nothing to commit
+- **git**: add is_staging_clean to check if there is any file in git staging
+
+## v1.7.0 (2019-11-08)
+
+### Fix
+
+- **cz**: fix bug in BaseCommitizen.style
+- **cz**: fix merge_style usage error
+- **cz**: remove breakpoint
+
+### Refactor
+
+- **cz**: change the color of default style
+
+### Feat
+
+- **config**: update style instead of overwrite
+- **config**: parse style in config
+- **commit**: make style configurable for commit command
-## v1.5.0
+## v1.6.0 (2019-11-05)
-### Feature
+### Feat
-- it is possible to specify a pattern to be matched in configuration `files` when doing bump.
+- **commit**: new retry argument to execute previous commit again
-## v1.4.0
+## v1.5.1 (2019-06-04)
-### Feature
+### Fix
+
+- #28 allows poetry add on py36 envs
+
+## v1.5.0 (2019-05-11)
-- new argument (--yes) in bump to accept prompt questions
+### Feat
+
+- **bump**: it is now possible to specify a pattern in the files attr to replace the version
+
+## v1.4.0 (2019-04-26)
### Fix
-- error is shown when commiting fails.
+- **bump**: handle commit and create tag failure
+
+### Feat
-## v1.3.0
+- added argument yes to bump in order to accept questions
-### Feature
+## v1.3.0 (2019-04-24)
-- bump: new commit message template, useful when having to skip ci.
+### Feat
-## v1.2.1
+- **bump**: new commit message template
+
+## v1.2.1 (2019-04-21)
### Fix
-- prefixes like docs, build, etc no longer generate a PATCH
+- **bump**: prefixes like docs, build, etc no longer generate a PATCH
-## v1.2.0
+## v1.2.0 (2019-04-19)
-### Feature
+### Feat
- custom cz plugins now support bumping version
-## v1.1.1
+## v1.1.1 (2019-04-18)
+
+### Refactor
+
+- changed stdout statements
+- **schema**: command logic removed from commitizen base
+- **info**: command logic removed from commitizen base
+- **example**: command logic removed from commitizen base
+- **commit**: moved most of the commit logic to the commit command
+
+### Fix
+
+- **bump**: commit message now fits better with semver
+- conventional commit 'breaking change' in body instead of title
+
+## v1.1.0 (2019-04-14)
+
+### Feat
+
+- new working bump command
+- create version tag
+- update given files with new version
+- **config**: new set key, used to set version to cfg
+- support for pyproject.toml
+- first semantic version bump implementaiton
### Fix
-- breaking change is now part of the body, instead of being in the subject
+- removed all from commit
+- fix config file not working
+
+### Refactor
+
+- added commands folder, better integration with decli
+
+## v1.0.0 (2019-03-01)
+
+### Refactor
+
+- removed delegator, added decli and many tests
+
+## 1.0.0b2 (2019-01-18)
+
+## v1.0.0b1 (2019-01-17)
+
+### Feat
+
+- py3 only, tests and conventional commits 1.0
+
+## v0.9.11 (2018-12-17)
+
+### Fix
+
+- **config**: load config reads in order without failing if there is no commitizen section
+
+## v0.9.10 (2018-09-22)
+
+### Fix
+
+- parse scope (this is my punishment for not having tests)
+
+## v0.9.9 (2018-09-22)
+
+### Fix
+
+- parse scope empty
+
+## v0.9.8 (2018-09-22)
+
+### Fix
+
+- **scope**: parse correctly again
+
+## v0.9.7 (2018-09-22)
+
+### Fix
+
+- **scope**: parse correctly
+
+## v0.9.6 (2018-09-19)
+
+### Refactor
+
+- **conventionalCommit**: moved fitlers to questions instead of message
+
+### Fix
+
+- **manifest**: inluded missing files
+
+## v0.9.5 (2018-08-24)
+
+### Fix
+
+- **config**: home path for python versions between 3.0 and 3.5
+
+## v0.9.4 (2018-08-02)
+
+### Feat
+
+- **cli**: added version
+
+## v0.9.3 (2018-07-28)
+
+### Feat
+
+- **commiter**: conventional commit is a bit more intelligent now
+
+## v0.9.2 (2017-11-11)
+
+### Refactor
+
+- **renamed conventional_changelog to conventional_commits, not backward compatible**:
+
+## v0.9.1 (2017-11-11)
+
+### Fix
+
+- **setup.py**: future is now required for every python version
+
+## v0.9.0 (2017-11-08)
+
+### Refactor
+
+- python 2 support
+
+## v0.8.6 (2017-11-08)
+
+## v0.8.5 (2017-11-08)
+
+## v0.8.4 (2017-11-08)
+
+## v0.8.3 (2017-11-08)
+
+## v0.8.2 (2017-10-08)
+
+## v0.8.1 (2017-10-08)
+
+## v0.8.0 (2017-10-08)
+
+### Feat
+
+- **cz**: jira smart commits
+
+## v0.7.0 (2017-10-08)
+
+### Refactor
+
+- **cli**: renamed all to ls command
+- **cz**: renamed angular cz to conventional changelog cz
+
+## v0.6.0 (2017-10-08)
+
+### Feat
+
+- info command for angular
-## v1.1.0
+## v0.5.0 (2017-10-07)
-### Features
+## v0.4.0 (2017-10-07)
-- auto bump version based on conventional commits using sem ver
-- pyproject support (see [pyproject.toml](./pyproject.toml) for usage)
+## v0.3.0 (2017-10-07)
-## v1.0.0
+## v0.2.0 (2017-10-07)
-### Features
+### Feat
-- more documentation
-- added tests
-- support for conventional commits 1.0.0
+- **config**: new loads from ~/.cz and working project .cz .cz.cfg and setup.cfg
+- package discovery
-### BREAKING CHANGES
+### Refactor
-- use of questionary to generate the prompt (so we depend on promptkit 2.0)
-- python 3 only
+- **cz_angular**: improved messages
diff --git a/commitizen/changelog.py b/commitizen/changelog.py
new file mode 100644
index 0000000000..3990b4cfb5
--- /dev/null
+++ b/commitizen/changelog.py
@@ -0,0 +1,249 @@
+"""
+# DESIGN
+
+## Metadata CHANGELOG.md
+
+1. Identify irrelevant information (possible: changelog title, first paragraph)
+2. Identify Unreleased area
+3. Identify latest version (to be able to write on top of it)
+
+## Parse git log
+
+1. get commits between versions
+2. filter commits with the current cz rules
+3. parse commit information
+4. yield tree nodes
+5. format tree nodes
+6. produce full tree
+7. generate changelog
+
+Extra:
+- [x] Generate full or partial changelog
+- [x] Include in tree from file all the extra comments added manually
+- [ ] Add unreleased value
+- [ ] hook after message is parsed (add extra information like hyperlinks)
+- [ ] hook after changelog is generated (api calls)
+- [ ] add support for change_type maps
+"""
+import os
+import re
+from collections import defaultdict
+from typing import Dict, Iterable, List, Optional
+
+import pkg_resources
+from jinja2 import Template
+
+from commitizen import defaults
+from commitizen.git import GitCommit, GitTag
+
+CATEGORIES = [
+ ("fix", "fix"),
+ ("breaking", "BREAKING CHANGES"),
+ ("feat", "feat"),
+ ("refactor", "refactor"),
+ ("perf", "perf"),
+ ("test", "test"),
+ ("build", "build"),
+ ("ci", "ci"),
+ ("chore", "chore"),
+]
+
+
+def transform_change_type(change_type: str) -> str:
+ # TODO: Use again to parse, for this we have to wait until the maps get
+ # defined again.
+ _change_type_lower = change_type.lower()
+ for match_value, output in CATEGORIES:
+ if re.search(match_value, _change_type_lower):
+ return output
+ else:
+ raise ValueError(f"Could not match a change_type with {change_type}")
+
+
+def get_commit_tag(commit: GitCommit, tags: List[GitTag]) -> Optional[GitTag]:
+ """"""
+ return next((tag for tag in tags if tag.rev == commit.rev), None)
+
+
+def generate_tree_from_commits(
+ commits: List[GitCommit],
+ tags: List[GitTag],
+ commit_parser: str,
+ changelog_pattern: str = defaults.bump_pattern,
+ unreleased_version: Optional[str] = None,
+) -> Iterable[Dict]:
+ pat = re.compile(changelog_pattern)
+ map_pat = re.compile(commit_parser)
+ # Check if the latest commit is not tagged
+ latest_commit = commits[0]
+ current_tag: Optional[GitTag] = get_commit_tag(latest_commit, tags)
+
+ current_tag_name: str = unreleased_version or "Unreleased"
+ current_tag_date: str = ""
+ if current_tag is not None and current_tag.name:
+ current_tag_name = current_tag.name
+ current_tag_date = current_tag.date
+
+ changes: Dict = defaultdict(list)
+ used_tags: List = [current_tag]
+ for commit in commits:
+ commit_tag = get_commit_tag(commit, tags)
+
+ if commit_tag is not None and commit_tag not in used_tags:
+ used_tags.append(commit_tag)
+ yield {
+ "version": current_tag_name,
+ "date": current_tag_date,
+ "changes": changes,
+ }
+ # TODO: Check if tag matches the version pattern, otherwie skip it.
+ # This in order to prevent tags that are not versions.
+ current_tag_name = commit_tag.name
+ current_tag_date = commit_tag.date
+ changes = defaultdict(list)
+
+ matches = pat.match(commit.message)
+ if not matches:
+ continue
+
+ message = map_pat.match(commit.message)
+ message_body = map_pat.match(commit.body)
+ if message:
+ # TODO: add a post hook coming from a rule (CzBase)
+ parsed_message: Dict = message.groupdict()
+ # change_type becomes optional by providing None
+ change_type = parsed_message.pop("change_type", None)
+ changes[change_type].append(parsed_message)
+ if message_body:
+ parsed_message_body: Dict = message_body.groupdict()
+ change_type = parsed_message_body.pop("change_type", None)
+ changes[change_type].append(parsed_message_body)
+
+ yield {
+ "version": current_tag_name,
+ "date": current_tag_date,
+ "changes": changes,
+ }
+
+
+def render_changelog(tree: Iterable) -> str:
+ template_file = pkg_resources.resource_string(
+ __name__, "templates/keep_a_changelog_template.j2"
+ ).decode("utf-8")
+ jinja_template = Template(template_file, trim_blocks=True)
+ changelog: str = jinja_template.render(tree=tree)
+ return changelog
+
+
+def parse_version_from_markdown(value: str) -> Optional[str]:
+ if not value.startswith("#"):
+ return None
+ m = re.search(defaults.version_parser, value)
+ if not m:
+ return None
+ return m.groupdict().get("version")
+
+
+def parse_title_type_of_line(value: str) -> Optional[str]:
+ md_title_parser = r"^(?P
#+)"
+ m = re.search(md_title_parser, value)
+ if not m:
+ return None
+ return m.groupdict().get("title")
+
+
+def get_metadata(filepath: str) -> Dict:
+ unreleased_start: Optional[int] = None
+ unreleased_end: Optional[int] = None
+ unreleased_title: Optional[str] = None
+ latest_version: Optional[str] = None
+ latest_version_position: Optional[int] = None
+ if not os.path.isfile(filepath):
+ return {
+ "unreleased_start": None,
+ "unreleased_end": None,
+ "latest_version": None,
+ "latest_version_position": None,
+ }
+
+ with open(filepath, "r") as changelog_file:
+ for index, line in enumerate(changelog_file):
+ line = line.strip().lower()
+
+ unreleased: Optional[str] = None
+ if "unreleased" in line:
+ unreleased = parse_title_type_of_line(line)
+ # Try to find beginning and end lines of the unreleased block
+ if unreleased:
+ unreleased_start = index
+ unreleased_title = unreleased
+ continue
+ elif (
+ isinstance(unreleased_title, str)
+ and parse_title_type_of_line(line) == unreleased_title
+ ):
+ unreleased_end = index
+
+ # Try to find the latest release done
+ version = parse_version_from_markdown(line)
+ if version:
+ latest_version = version
+ latest_version_position = index
+ break # there's no need for more info
+ if unreleased_start is not None and unreleased_end is None:
+ unreleased_end = index
+ return {
+ "unreleased_start": unreleased_start,
+ "unreleased_end": unreleased_end,
+ "latest_version": latest_version,
+ "latest_version_position": latest_version_position,
+ }
+
+
+def incremental_build(new_content: str, lines: List, metadata: Dict) -> List:
+ """Takes the original lines and updates with new_content.
+
+ The metadata holds information enough to remove the old unreleased and
+ where to place the new content
+
+ Arguments:
+ lines -- the lines from the changelog
+ new_content -- this should be placed somewhere in the lines
+ metadata -- information about the changelog
+
+ Returns:
+ List -- updated lines
+ """
+ unreleased_start = metadata.get("unreleased_start")
+ unreleased_end = metadata.get("unreleased_end")
+ latest_version_position = metadata.get("latest_version_position")
+ skip = False
+ output_lines: List = []
+ for index, line in enumerate(lines):
+ if index == unreleased_start:
+ skip = True
+ elif index == unreleased_end:
+ skip = False
+ if (
+ latest_version_position is None
+ or isinstance(latest_version_position, int)
+ and isinstance(unreleased_end, int)
+ and latest_version_position > unreleased_end
+ ):
+ continue
+
+ if skip:
+ continue
+
+ if (
+ isinstance(latest_version_position, int)
+ and index == latest_version_position
+ ):
+
+ output_lines.append(new_content)
+ output_lines.append("\n")
+
+ output_lines.append(line)
+ if not isinstance(latest_version_position, int):
+ output_lines.append(new_content)
+ return output_lines
diff --git a/commitizen/changelog_parser.py b/commitizen/changelog_parser.py
new file mode 100644
index 0000000000..c945cbdb4f
--- /dev/null
+++ b/commitizen/changelog_parser.py
@@ -0,0 +1,133 @@
+"""
+# DESIGN
+
+## Parse CHANGELOG.md
+
+1. Get LATEST VERSION from CONFIG
+1. Parse the file version to version
+2. Build a dict (tree) of that particular version
+3. Transform tree into markdown again
+"""
+import re
+from collections import defaultdict
+from typing import Dict, Generator, Iterable, List
+
+MD_VERSION_RE = r"^##\s(?P[a-zA-Z0-9.+]+)\s?\(?(?P[0-9-]+)?\)?"
+MD_CHANGE_TYPE_RE = r"^###\s(?P[a-zA-Z0-9.+\s]+)"
+MD_MESSAGE_RE = (
+ r"^-\s(\*{2}(?P[a-zA-Z0-9]+)\*{2}:\s)?(?P.+)(?P!)?"
+)
+md_version_c = re.compile(MD_VERSION_RE)
+md_change_type_c = re.compile(MD_CHANGE_TYPE_RE)
+md_message_c = re.compile(MD_MESSAGE_RE)
+
+
+CATEGORIES = [
+ ("fix", "fix"),
+ ("breaking", "BREAKING CHANGES"),
+ ("feat", "feat"),
+ ("refactor", "refactor"),
+ ("perf", "perf"),
+ ("test", "test"),
+ ("build", "build"),
+ ("ci", "ci"),
+ ("chore", "chore"),
+]
+
+
+def find_version_blocks(filepath: str) -> Generator:
+ """
+ version block: contains all the information about a version.
+
+ E.g:
+ ```
+ ## 1.2.1 (2019-07-20)
+
+ ### Fix
+
+ - username validation not working
+
+ ### Feat
+
+ - new login system
+
+ ```
+ """
+ with open(filepath, "r") as f:
+ block: list = []
+ for line in f:
+ line = line.strip("\n")
+ if not line:
+ continue
+
+ if line.startswith("## "):
+ if len(block) > 0:
+ yield block
+ block = [line]
+ else:
+ block.append(line)
+ yield block
+
+
+def parse_md_version(md_version: str) -> Dict:
+ m = md_version_c.match(md_version)
+ if not m:
+ return {}
+ return m.groupdict()
+
+
+def parse_md_change_type(md_change_type: str) -> Dict:
+ m = md_change_type_c.match(md_change_type)
+ if not m:
+ return {}
+ return m.groupdict()
+
+
+def parse_md_message(md_message: str) -> Dict:
+ m = md_message_c.match(md_message)
+ if not m:
+ return {}
+ return m.groupdict()
+
+
+def transform_change_type(change_type: str) -> str:
+ # TODO: Use again to parse, for this we have to wait until the maps get
+ # defined again.
+ _change_type_lower = change_type.lower()
+ for match_value, output in CATEGORIES:
+ if re.search(match_value, _change_type_lower):
+ return output
+ else:
+ raise ValueError(f"Could not match a change_type with {change_type}")
+
+
+def generate_block_tree(block: List[str]) -> Dict:
+ # tree: Dict = {"commits": []}
+ changes: Dict = defaultdict(list)
+ tree: Dict = {"changes": changes}
+
+ change_type = None
+ for line in block:
+ if line.startswith("## "):
+ # version identified
+ change_type = None
+ tree = {**tree, **parse_md_version(line)}
+ elif line.startswith("### "):
+ # change_type identified
+ result = parse_md_change_type(line)
+ if not result:
+ continue
+ change_type = result.get("change_type", "").lower()
+
+ elif line.startswith("- "):
+ # message identified
+ commit = parse_md_message(line)
+ changes[change_type].append(commit)
+ else:
+ print("it's something else: ", line)
+ return tree
+
+
+def generate_full_tree(blocks: Iterable) -> Iterable[Dict]:
+ for block in blocks:
+ yield generate_block_tree(block)
diff --git a/commitizen/cli.py b/commitizen/cli.py
index 4ea45d431b..f5b41a2e2f 100644
--- a/commitizen/cli.py
+++ b/commitizen/cli.py
@@ -34,9 +34,9 @@
# "required": True,
"commands": [
{
- "name": "ls",
- "help": "show available commitizens",
- "func": commands.ListCz,
+ "name": ["init"],
+ "help": "init commitizen configuration",
+ "func": commands.Init,
},
{
"name": ["commit", "c"],
@@ -55,6 +55,11 @@
},
],
},
+ {
+ "name": "ls",
+ "help": "show available commitizens",
+ "func": commands.ListCz,
+ },
{
"name": "example",
"help": "show commit example",
@@ -81,6 +86,12 @@
"action": "store_true",
"help": "bump version in the files from the config",
},
+ {
+ "name": ["--changelog", "-ch"],
+ "action": "store_true",
+ "default": False,
+ "help": "generate the changelog for the newest version",
+ },
{
"name": "--yes",
"action": "store_true",
@@ -114,33 +125,45 @@
],
},
{
- "name": ["version"],
+ "name": ["changelog", "ch"],
"help": (
- "get the version of the installed commitizen or the current project"
- " (default: installed commitizen)"
+ "generate changelog (note that it will overwrite existing file)"
),
- "func": commands.Version,
+ "func": commands.Changelog,
"arguments": [
{
- "name": ["-p", "--project"],
- "help": "get the version of the current project",
+ "name": "--dry-run",
"action": "store_true",
- "exclusive_group": "group1",
+ "default": False,
+ "help": "show changelog to stdout",
},
{
- "name": ["-c", "--commitizen"],
- "help": "get the version of the installed commitizen",
- "action": "store_true",
- "exclusive_group": "group1",
+ "name": "--file-name",
+ "help": "file name of changelog (default: 'CHANGELOG.md')",
},
{
- "name": ["-v", "--verbose"],
+ "name": "--unreleased-version",
"help": (
- "get the version of both the installed commitizen "
- "and the current project"
+ "set the value for the new version (use the tag value), "
+ "instead of using unreleased"
),
+ },
+ {
+ "name": "--incremental",
"action": "store_true",
- "exclusive_group": "group1",
+ "default": False,
+ "help": (
+ "generates changelog from last created version, "
+ "useful if the changelog has been manually modified"
+ ),
+ },
+ {
+ "name": "--start-rev",
+ "default": None,
+ "help": (
+ "start rev of the changelog."
+ "If not set, it will generate changelog from the start"
+ ),
},
],
},
@@ -166,9 +189,35 @@
],
},
{
- "name": ["init"],
- "help": "init commitizen configuration",
- "func": commands.Init,
+ "name": ["version"],
+ "help": (
+ "get the version of the installed commitizen or the current project"
+ " (default: installed commitizen)"
+ ),
+ "func": commands.Version,
+ "arguments": [
+ {
+ "name": ["-p", "--project"],
+ "help": "get the version of the current project",
+ "action": "store_true",
+ "exclusive_group": "group1",
+ },
+ {
+ "name": ["-c", "--commitizen"],
+ "help": "get the version of the installed commitizen",
+ "action": "store_true",
+ "exclusive_group": "group1",
+ },
+ {
+ "name": ["-v", "--verbose"],
+ "help": (
+ "get the version of both the installed commitizen "
+ "and the current project"
+ ),
+ "action": "store_true",
+ "exclusive_group": "group1",
+ },
+ ],
},
],
},
diff --git a/commitizen/commands/__init__.py b/commitizen/commands/__init__.py
index b87906d8b4..806e384522 100644
--- a/commitizen/commands/__init__.py
+++ b/commitizen/commands/__init__.py
@@ -1,4 +1,5 @@
from .bump import Bump
+from .changelog import Changelog
from .check import Check
from .commit import Commit
from .example import Example
@@ -12,6 +13,7 @@
"Bump",
"Check",
"Commit",
+ "Changelog",
"Example",
"Info",
"ListCz",
diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py
index 90ec715744..1d0439e4b9 100644
--- a/commitizen/commands/bump.py
+++ b/commitizen/commands/bump.py
@@ -4,6 +4,7 @@
from packaging.version import Version
from commitizen import bump, factory, git, out
+from commitizen.commands.changelog import Changelog
from commitizen.config import BaseConfig
from commitizen.error_codes import (
COMMIT_FAILED,
@@ -29,6 +30,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
},
}
self.cz = factory.commiter_factory(self.config)
+ self.changelog = arguments["changelog"]
def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool:
"""Check if reading the whole git tree up to HEAD is needed."""
@@ -131,6 +133,17 @@ def __call__(self): # noqa: C901
if is_files_only:
raise SystemExit()
+ if self.changelog:
+ changelog = Changelog(
+ self.config,
+ {
+ "unreleased_version": new_tag_version,
+ "incremental": True,
+ "dry_run": dry_run,
+ },
+ )
+ changelog()
+
self.config.set_key("version", new_version.public)
c = git.commit(message, args="-a")
if c.err:
diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py
new file mode 100644
index 0000000000..12f43f775d
--- /dev/null
+++ b/commitizen/commands/changelog.py
@@ -0,0 +1,106 @@
+import os.path
+from difflib import SequenceMatcher
+from operator import itemgetter
+from typing import Dict, List
+
+from commitizen import changelog, factory, git, out
+from commitizen.config import BaseConfig
+from commitizen.error_codes import NO_COMMITS_FOUND, NO_PATTERN_MAP, NO_REVISION
+from commitizen.git import GitTag
+
+
+def similar(a, b):
+ return SequenceMatcher(None, a, b).ratio()
+
+
+class Changelog:
+ """Generate a changelog based on the commit history."""
+
+ def __init__(self, config: BaseConfig, args):
+ self.config: BaseConfig = config
+ self.cz = factory.commiter_factory(self.config)
+
+ self.start_rev = args.get("start_rev")
+ self.file_name = args.get("file_name") or self.config.settings.get(
+ "changelog_file"
+ )
+ self.incremental = args["incremental"]
+ self.dry_run = args["dry_run"]
+ self.unreleased_version = args["unreleased_version"]
+
+ def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
+ """Try to find the 'start_rev'.
+
+ We use a similarity approach. We know how to parse the version from the markdown
+ changelog, but not the whole tag, we don't even know how's the tag made.
+
+ This 'smart' function tries to find a similarity between the found version number
+ and the available tag.
+
+ The SIMILARITY_THRESHOLD is an empirical value, it may have to be adjusted based
+ on our experience.
+ """
+ SIMILARITY_THRESHOLD = 0.89
+ tag_ratio = map(
+ lambda tag: (SequenceMatcher(None, latest_version, tag.name).ratio(), tag),
+ tags,
+ )
+ try:
+ score, tag = max(tag_ratio, key=itemgetter(0))
+ except ValueError:
+ raise SystemExit(NO_REVISION)
+ if score < SIMILARITY_THRESHOLD:
+ raise SystemExit(NO_REVISION)
+ start_rev = tag.name
+ return start_rev
+
+ def __call__(self):
+ commit_parser = self.cz.commit_parser
+ changelog_pattern = self.cz.changelog_pattern
+ start_rev = self.start_rev
+ unreleased_version = self.unreleased_version
+ changelog_meta: Dict = {}
+
+ if not changelog_pattern or not commit_parser:
+ out.error(
+ f"'{self.config.settings['name']}' rule does not support changelog"
+ )
+ raise SystemExit(NO_PATTERN_MAP)
+
+ tags = git.get_tags()
+ if not tags:
+ tags = []
+
+ if self.incremental:
+ changelog_meta = changelog.get_metadata(self.file_name)
+ latest_version = changelog_meta.get("latest_version")
+ if latest_version:
+ start_rev = self._find_incremental_rev(latest_version, tags)
+
+ commits = git.get_commits(start=start_rev)
+ if not commits:
+ out.error("No commits found")
+ raise SystemExit(NO_COMMITS_FOUND)
+
+ tree = changelog.generate_tree_from_commits(
+ commits, tags, commit_parser, changelog_pattern, unreleased_version
+ )
+ changelog_out = changelog.render_changelog(tree)
+
+ if self.dry_run:
+ out.write(changelog_out)
+ raise SystemExit(0)
+
+ lines = []
+ if self.incremental and os.path.isfile(self.file_name):
+ with open(self.file_name, "r") as changelog_file:
+ lines = changelog_file.readlines()
+
+ with open(self.file_name, "w") as changelog_file:
+ if self.incremental:
+ new_lines = changelog.incremental_build(
+ changelog_out, lines, changelog_meta
+ )
+ changelog_file.writelines(new_lines)
+ else:
+ changelog_file.write(changelog_out)
diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py
index b428d696cd..ac73cc295b 100644
--- a/commitizen/commands/init.py
+++ b/commitizen/commands/init.py
@@ -32,7 +32,9 @@ def __call__(self):
values_to_add["version"] = Version(tag).public
values_to_add["tag_format"] = self._ask_tag_format(tag)
self._update_config_file(values_to_add)
- out.write("The configuration are all set.")
+ out.write("You can bump the version and create cangelog running:\n")
+ out.info("cz bump --changelog")
+ out.success("The configuration are all set.")
else:
# TODO: handle the case that config file exist but no value
out.line(f"Config file {self.config.path} already exists")
diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py
index cd0775e6f7..c6e74d95b0 100644
--- a/commitizen/cz/base.py
+++ b/commitizen/cz/base.py
@@ -22,6 +22,13 @@ class BaseCommitizen(metaclass=ABCMeta):
("disabled", "fg:#858585 italic"),
]
+ # The whole subject will be parsed as message by default
+ # This allows supporting changelog for any rule system.
+ # It can be modified per rule
+ commit_parser: Optional[str] = r"(?P.*)"
+ changelog_pattern: Optional[str] = r".*"
+ changelog_map: Optional[dict] = None # TODO: Use it
+
def __init__(self, config: BaseConfig):
self.config = config
if not self.config.settings.get("style"):
@@ -59,3 +66,10 @@ def schema_pattern(self) -> Optional[str]:
def info(self) -> Optional[str]:
"""Information about the standardized commit message."""
raise NotImplementedError("Not Implemented yet")
+
+ def process_commit(self, commit: str) -> str:
+ """Process commit for changelog.
+
+ If not overwritten, it returns the first line of commit.
+ """
+ return commit.split("\n")[0]
diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py
index fcf0df4b58..b932bd6682 100644
--- a/commitizen/cz/conventional_commits/conventional_commits.py
+++ b/commitizen/cz/conventional_commits/conventional_commits.py
@@ -1,4 +1,5 @@
import os
+import re
from typing import Any, Dict, List
from commitizen import defaults
@@ -29,6 +30,8 @@ def parse_subject(text):
class ConventionalCommitsCz(BaseCommitizen):
bump_pattern = defaults.bump_pattern
bump_map = defaults.bump_map
+ commit_parser = defaults.commit_parser
+ changelog_pattern = defaults.bump_pattern
def questions(self) -> List[Dict[str, Any]]:
questions: List[Dict[str, Any]] = [
@@ -173,7 +176,7 @@ def schema(self) -> str:
def schema_pattern(self) -> str:
PATTERN = (
r"(build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert|bump)"
- r"(\(\S+\))?:\s.*"
+ r"(\(\S+\))?:(\s.*)"
)
return PATTERN
@@ -183,3 +186,10 @@ def info(self) -> str:
with open(filepath, "r") as f:
content = f.read()
return content
+
+ def process_commit(self, commit: str) -> str:
+ pat = re.compile(self.schema_pattern())
+ m = re.match(pat, commit)
+ if m is None:
+ return ""
+ return m.group(3).strip()
diff --git a/commitizen/defaults.py b/commitizen/defaults.py
index 477a48e4a1..1f4adf52e6 100644
--- a/commitizen/defaults.py
+++ b/commitizen/defaults.py
@@ -13,6 +13,7 @@
"version_files": [],
"tag_format": None, # example v$version
"bump_message": None, # bumped v$current_version to $new_version
+ "changelog_file": "CHANGELOG.md",
}
MAJOR = "MAJOR"
@@ -31,3 +32,6 @@
)
)
bump_message = "bump: version $current_version → $new_version"
+
+commit_parser = r"^(?Pfeat|fix|refactor|perf|BREAKING CHANGE)(?:\((?P[^()\r\n]*)\)|\()?(?P!)?:\s(?P.*)?" # noqa
+version_parser = r"(?P([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?)"
diff --git a/commitizen/error_codes.py b/commitizen/error_codes.py
index 20cc54aa9e..9095399c3f 100644
--- a/commitizen/error_codes.py
+++ b/commitizen/error_codes.py
@@ -22,3 +22,6 @@
# Check
NO_COMMIT_MSG = 13
INVALID_COMMIT_MSG = 14
+
+# Changelog
+NO_REVISION = 16
diff --git a/commitizen/git.py b/commitizen/git.py
index 058ba89a00..5c3b5926ee 100644
--- a/commitizen/git.py
+++ b/commitizen/git.py
@@ -3,12 +3,23 @@
from tempfile import NamedTemporaryFile
from typing import List, Optional
+from typing_extensions import Protocol
+
from commitizen import cmd
+class GitProtocol(Protocol):
+ rev: str
+ name: str
+
+
class GitObject:
- def __eq__(self, other):
- if not isinstance(other, GitObject):
+ rev: str
+ name: str
+ date: str
+
+ def __eq__(self, other) -> bool:
+ if not hasattr(other, "rev"):
return False
return self.rev == other.rev
@@ -34,7 +45,7 @@ def __init__(self, name, rev, date):
self.date = date.strip()
def __repr__(self):
- return f"{self.name} ({self.rev})"
+ return f"GitTag('{self.name}', '{self.rev}', '{self.date}')"
def tag(tag: str):
diff --git a/commitizen/templates/keep_a_changelog_template.j2 b/commitizen/templates/keep_a_changelog_template.j2
new file mode 100644
index 0000000000..be0d0d26e6
--- /dev/null
+++ b/commitizen/templates/keep_a_changelog_template.j2
@@ -0,0 +1,19 @@
+{% for entry in tree %}
+
+## {{ entry.version }} {% if entry.date %}({{ entry.date }}){% endif %}
+
+{% for change_key, changes in entry.changes.items() %}
+
+{% if change_key %}
+### {{ change_key|title }}
+{% endif %}
+
+{% for change in changes %}
+{% if change.scope %}
+- **{{ change.scope }}**: {{ change.message }}
+{% elif change.message %}
+- {{ change.message }}
+{% endif %}
+{% endfor %}
+{% endfor %}
+{% endfor %}
diff --git a/docs/README.md b/docs/README.md
index c3f94ccf64..f05e03929b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -77,29 +77,34 @@ cz c
```bash
$ cz --help
usage: cz [-h] [--debug] [-n NAME] [--version]
- {ls,commit,c,example,info,schema,bump} ...
+ {init,commit,c,ls,example,info,schema,bump,changelog,ch,check,version}
+ ...
Commitizen is a cli tool to generate conventional commits.
For more information about the topic go to https://conventionalcommits.org/
optional arguments:
--h, --help show this help message and exit
---debug use debug mode
--n NAME, --name NAME use the given commitizen
---version get the version of the installed commitizen
+ -h, --help show this help message and exit
+ --debug use debug mode
+ -n NAME, --name NAME use the given commitizen (default:
+ cz_conventional_commits)
+ --version get the version of the installed commitizen
commands:
-{ls,commit,c,example,info,schema,bump}
- ls show available commitizens
+ {init,commit,c,ls,example,info,schema,bump,changelog,ch,check,version}
+ init init commitizen configuration
commit (c) create new commit
+ ls show available commitizens
example show commit example
info show information about the cz
schema show commit schema
bump bump semantic version based on the git log
+ changelog (ch) generate changelog (note that it will overwrite
+ existing file)
+ check validates that a commit message matches the commitizen
+ schema
version get the version of the installed commitizen or the
current project (default: installed commitizen)
- check validates that a commit message matches the commitizen schema
- init init commitizen configuration
```
## FAQ
diff --git a/docs/bump.md b/docs/bump.md
index 393baefd15..d9527135e4 100644
--- a/docs/bump.md
+++ b/docs/bump.md
@@ -54,7 +54,7 @@ Some examples:
```bash
$ cz bump --help
-usage: cz bump [-h] [--dry-run] [--files-only] [--yes]
+usage: cz bump [-h] [--dry-run] [--files-only] [--changelog] [--yes]
[--tag-format TAG_FORMAT] [--bump-message BUMP_MESSAGE]
[--prerelease {alpha,beta,rc}]
[--increment {MAJOR,MINOR,PATCH}]
@@ -63,10 +63,11 @@ optional arguments:
-h, --help show this help message and exit
--dry-run show output to stdout, no commit, no modified files
--files-only bump version in the files from the config
+ --changelog, -ch generate the changelog for the newest version
--yes accept automatically questions done
--tag-format TAG_FORMAT
- the format used to tag the commit and read it, use it in
- existing projects, wrap around simple quotes
+ the format used to tag the commit and read it, use it
+ in existing projects, wrap around simple quotes
--bump-message BUMP_MESSAGE
template used to create the release commmit, useful
when working with CI
@@ -78,6 +79,14 @@ optional arguments:
## Configuration
+### `changelog`
+
+Generate a **changelog** along with the new version and tag when bumping.
+
+```bash
+cz bump --changelog
+```
+
### `tag_format`
It is used to read the format from the git tags, and also to generate the tags.
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 0000000000..fab38ea2ad
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,133 @@
+## About
+
+This command will generate a changelog following the commiting rules established.
+
+## Usage
+
+```bash
+$ cz changelog --help
+usage: cz changelog [-h] [--dry-run] [--file-name FILE_NAME]
+ [--unreleased-version UNRELEASED_VERSION] [--incremental]
+ [--start-rev START_REV]
+
+optional arguments:
+ -h, --help show this help message and exit
+ --dry-run show changelog to stdout
+ --file-name FILE_NAME
+ file name of changelog (default: 'CHANGELOG.md')
+ --unreleased-version UNRELEASED_VERSION
+ set the value for the new version (use the tag value),
+ instead of using unreleased
+ --incremental generates changelog from last created version, useful
+ if the changelog has been manually modified
+ --start-rev START_REV
+ start rev of the changelog.If not set, it will
+ generate changelog from the start
+```
+
+### Examples
+
+```bash
+cz changelog
+```
+
+```bash
+cz ch
+```
+
+## Constrains
+
+changelog generation is constrained only to **markdown** files.
+
+## Description
+
+These are the variables used by the changelog generator.
+
+```md
+# ()
+
+##
+
+- ****:
+```
+
+It will create a full block like above per version found in the tags.
+And it will create a list of the commits found.
+The `change_type` and the `scope` are optional, they don't need to be provided,
+but if your regex does they will be rendered.
+
+The format followed by the changelog is the one from [keep a changelog][keepachangelog]
+and the following variables are expected:
+
+| Variable | Description | Source |
+| ------------- | ---------------------------------------------------------------------------------------------- | -------------- |
+| `version` | Version number which should follow [semver][semver] | `tags` |
+| `date` | Date in which the tag was created | `tags` |
+| `change_type` | The group where the commit belongs to, this is optional. Example: fix | `commit regex` |
+| `message`\* | Information extracted from the commit message | `commit regex` |
+| `scope` | Contextual information. Should be parsed using the regex from the message, it will be **bold** | `commit regex` |
+| `breaking` | Wether is a breaking change or not | `commit regex` |
+
+- **required**: is the only one required to be parsed by the regex
+
+## Configuration
+
+### `unreleased_version`
+
+There is usually an egg and chicken situation when automatically
+bumping the version and creating the changelog.
+If you bump the version first, you have no changelog, you have to
+create it later, and it won't be included in
+the release of the created version.
+
+If you create the changelog before bumping the version, then you
+usually don't have the latest tag, and the _Unreleased_ title appears.
+
+By introducing `unreleased_version` you can prevent this situation.
+
+Before bumping you can run:
+
+```bash
+cz changelog --unreleased_version="v1.0.0"
+```
+
+Remember to use the tag instead of the raw version number
+
+For example if the format of your tag includes a `v` (`v1.0.0`), then you should use that,
+if your tag is the same as the raw version, then ignore this.
+
+Alternatively you can directly bump the version and create the changelog by doing
+
+```bash
+cz bump --changelog
+```
+
+### `file-name`
+
+This value can be updated in the `toml` file with the key `changelog_file` under `tools.commitizen`
+
+Specify the name of the output file, remember that changelog only works with markdown.
+
+```bash
+cz changelog --file-name="CHANGES.md"
+```
+
+### `incremental`
+
+Benefits:
+
+- Build from latest version found in changelog, this is useful if you have a different changelog and want to use commitizen
+- Update unreleased area
+- Allows users to manually touch the changelog without being rewritten.
+
+```bash
+cz changelog --incremental
+```
+
+## TODO
+
+- [ ] support for hooks: this would allow introduction of custom information in the commiter, like a github or jira url. Eventually we could build a `CzConventionalGithub`, which would add links to commits
+- [ ] support for map: allow the usage of a `change_type` mapper, to convert from feat to feature for example.
+
+[keepachangelog]: https://keepachangelog.com/
+[semver]: https://semver.org/
diff --git a/docs/config.md b/docs/config.md
index d34c228ff9..67ce1f9a4f 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -67,8 +67,9 @@ The extra tab before the square brackets (`]`) at the end is required.
| -------- | ---- | ------- | ----------- |
| `name` | `str` | `"cz_conventional_commits"` | Name of the committing rules to use |
| `version` | `str` | `None` | Current version. Example: "0.1.2" |
-| `version_files` | `list` | `[ ]` | Files were the version will be updated. A pattern to match a line, can also be specified, separated by `:` [See more](https://commitizen-tools.github.io/commitizen/bump#files) |
-| `tag_format` | `str` | `None` | Format for the git tag, useful for old projects, that use a convention like `"v1.2.1"`. [See more](https://commitizen-tools.github.io/commitizen/bump#tag_format) |
-| `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more](https://commitizen-tools.github.io/commitizen/bump#bump_message) |
+| `version_files` | `list` | `[ ]` | Files were the version will be updated. A pattern to match a line, can also be specified, separated by `:` [See more](https://woile.github.io/commitizen/bump#files) |
+| `tag_format` | `str` | `None` | Format for the git tag, useful for old projects, that use a convention like `"v1.2.1"`. [See more](https://woile.github.io/commitizen/bump#tag_format) |
+| `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more](https://woile.github.io/commitizen/bump#bump_message) |
+| `changelog_file` | `str` | `CHANGELOG.md` | filename of exported changelog |
| `style` | `list` | see above | Style for the prompts (It will merge this value with default style.) [See More (Styling your prompts with your favorite colors)](https://github.com/tmbo/questionary#additional-features) |
| `customize` | `dict` | `None` | **This is only supported when config through `toml`.** Custom rules for committing and bumping. [See more](https://commitizen-tools.github.io/commitizen/customization/) |
diff --git a/pyproject.toml b/pyproject.toml
index 38f69fca9a..11306bb4bd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,7 +51,8 @@ colorama = "^0.4.1"
termcolor = "^1.1"
packaging = ">=19,<21"
tomlkit = "^0.5.3"
-jinja2 = {version = "^2.10.3", optional = true}
+jinja2 = "^2.10.3"
+typing = "^3.7.4"
[tool.poetry.dev-dependencies]
ipython = "^7.2"
@@ -65,6 +66,7 @@ mypy = "^0.770"
mkdocs = "^1.0"
mkdocs-material = "^4.1"
isort = "^4.3.21"
+freezegun = "^0.3.15"
[tool.poetry.scripts]
cz = "commitizen.cli:main"
diff --git a/tests/CHANGELOG_FOR_TEST.md b/tests/CHANGELOG_FOR_TEST.md
new file mode 100644
index 0000000000..7605bbed47
--- /dev/null
+++ b/tests/CHANGELOG_FOR_TEST.md
@@ -0,0 +1,129 @@
+
+## v1.2.0 (2019-04-19)
+
+### Feat
+
+- custom cz plugins now support bumping version
+
+## v1.1.1 (2019-04-18)
+
+### Refactor
+
+- changed stdout statements
+- **schema**: command logic removed from commitizen base
+- **info**: command logic removed from commitizen base
+- **example**: command logic removed from commitizen base
+- **commit**: moved most of the commit logic to the commit command
+
+### Fix
+
+- **bump**: commit message now fits better with semver
+- conventional commit 'breaking change' in body instead of title
+
+## v1.1.0 (2019-04-14)
+
+### Feat
+
+- new working bump command
+- create version tag
+- update given files with new version
+- **config**: new set key, used to set version to cfg
+- support for pyproject.toml
+- first semantic version bump implementaiton
+
+### Fix
+
+- removed all from commit
+- fix config file not working
+
+### Refactor
+
+- added commands folder, better integration with decli
+
+## v1.0.0 (2019-03-01)
+
+### Refactor
+
+- removed delegator, added decli and many tests
+
+### Breaking Change
+
+- API is stable
+
+## 1.0.0b2 (2019-01-18)
+
+## v1.0.0b1 (2019-01-17)
+
+### Feat
+
+- py3 only, tests and conventional commits 1.0
+
+## v0.9.11 (2018-12-17)
+
+### Fix
+
+- **config**: load config reads in order without failing if there is no commitizen section
+
+## v0.9.10 (2018-09-22)
+
+### Fix
+
+- parse scope (this is my punishment for not having tests)
+
+## v0.9.9 (2018-09-22)
+
+### Fix
+
+- parse scope empty
+
+## v0.9.8 (2018-09-22)
+
+### Fix
+
+- **scope**: parse correctly again
+
+## v0.9.7 (2018-09-22)
+
+### Fix
+
+- **scope**: parse correctly
+
+## v0.9.6 (2018-09-19)
+
+### Refactor
+
+- **conventionalCommit**: moved fitlers to questions instead of message
+
+### Fix
+
+- **manifest**: inluded missing files
+
+## v0.9.5 (2018-08-24)
+
+### Fix
+
+- **config**: home path for python versions between 3.0 and 3.5
+
+## v0.9.4 (2018-08-02)
+
+### Feat
+
+- **cli**: added version
+
+## v0.9.3 (2018-07-28)
+
+### Feat
+
+- **commiter**: conventional commit is a bit more intelligent now
+
+## v0.9.2 (2017-11-11)
+
+### Refactor
+
+- renamed conventional_changelog to conventional_commits, not backward compatible
+
+## v0.9.1 (2017-11-11)
+
+### Fix
+
+- **setup.py**: future is now required for every python version
diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py
index e5ef886f63..3907d19527 100644
--- a/tests/commands/test_bump_command.py
+++ b/tests/commands/test_bump_command.py
@@ -1,20 +1,9 @@
import sys
-import uuid
-from pathlib import Path
-from typing import Optional
import pytest
from commitizen import cli, cmd, git
-
-
-def create_file_and_commit(message: str, filename: Optional[str] = None):
- if not filename:
- filename = str(uuid.uuid4())
-
- Path(f"./{filename}").touch()
- cmd.run("git add .")
- git.commit(message)
+from tests.utils import create_file_and_commit
@pytest.mark.usefixtures("tmp_commitizen_project")
diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py
new file mode 100644
index 0000000000..d78d04bee2
--- /dev/null
+++ b/tests/commands/test_changelog_command.py
@@ -0,0 +1,236 @@
+import os
+import sys
+from datetime import date
+
+import pytest
+
+from commitizen import cli, git
+from tests.utils import create_file_and_commit
+
+
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_changlog_on_empty_project(mocker):
+ testargs = ["cz", "changelog", "--dry-run"]
+ mocker.patch.object(sys, "argv", testargs)
+
+ with pytest.raises(SystemExit):
+ cli.main()
+
+
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_changlog_from_version_zero_point_two(mocker, capsys):
+ create_file_and_commit("feat: new file")
+ create_file_and_commit("refactor: not in changelog")
+
+ # create tag
+ testargs = ["cz", "bump", "--yes"]
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+ capsys.readouterr()
+
+ create_file_and_commit("feat: after 0.2.0")
+ create_file_and_commit("feat: after 0.2")
+
+ testargs = ["cz", "changelog", "--start-rev", "0.2.0", "--dry-run"]
+ mocker.patch.object(sys, "argv", testargs)
+ with pytest.raises(SystemExit):
+ cli.main()
+
+ out, _ = capsys.readouterr()
+ assert out == "\n## Unreleased \n\n### Feat\n\n- after 0.2\n- after 0.2.0\n\n"
+
+
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_changlog_with_different_cz(mocker, capsys):
+ create_file_and_commit("JRA-34 #comment corrected indent issue")
+ create_file_and_commit("JRA-35 #time 1w 2d 4h 30m Total work logged")
+
+ testargs = ["cz", "-n", "cz_jira", "changelog", "--dry-run"]
+ mocker.patch.object(sys, "argv", testargs)
+
+ with pytest.raises(SystemExit):
+ cli.main()
+ out, _ = capsys.readouterr()
+ assert (
+ out
+ == "\n## Unreleased \n\n\n- JRA-35 #time 1w 2d 4h 30m Total work logged\n- JRA-34 #comment corrected indent issue\n\n"
+ )
+
+
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_changlog_from_start(mocker, capsys):
+ changelog_path = os.path.join(os.getcwd(), "CHANGELOG.md")
+ create_file_and_commit("feat: new file")
+ create_file_and_commit("refactor: is in changelog")
+ create_file_and_commit("Merge into master")
+
+ testargs = ["cz", "changelog"]
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ with open(changelog_path, "r") as f:
+ out = f.read()
+
+ assert (
+ out
+ == "\n## Unreleased \n\n### Refactor\n\n- is in changelog\n\n### Feat\n\n- new file\n"
+ )
+
+
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_changlog_replacing_unreleased_using_incremental(mocker, capsys):
+ changelog_path = os.path.join(os.getcwd(), "CHANGELOG.md")
+
+ create_file_and_commit("feat: add new output")
+ create_file_and_commit("fix: output glitch")
+ create_file_and_commit("Merge into master")
+
+ testargs = ["cz", "changelog"]
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ testargs = ["cz", "bump", "--yes"]
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ create_file_and_commit("fix: mama gotta work")
+ create_file_and_commit("feat: add more stuff")
+ create_file_and_commit("Merge into master")
+
+ testargs = ["cz", "changelog", "--incremental"]
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ with open(changelog_path, "r") as f:
+ out = f.read()
+
+ today = date.today().isoformat()
+ assert (
+ out
+ == f"\n\n## Unreleased \n\n### Feat\n\n- add more stuff\n\n### Fix\n\n- mama gotta work\n\n## 0.2.0 ({today})\n\n### Fix\n\n- output glitch\n\n### Feat\n\n- add new output\n"
+ )
+
+
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_changlog_is_persisted_using_incremental(mocker, capsys):
+ changelog_path = os.path.join(os.getcwd(), "CHANGELOG.md")
+
+ create_file_and_commit("feat: add new output")
+ create_file_and_commit("fix: output glitch")
+ create_file_and_commit("Merge into master")
+
+ testargs = ["cz", "bump", "--yes"]
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ testargs = ["cz", "changelog"]
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ with open(changelog_path, "a") as f:
+ f.write("\nnote: this should be persisted using increment\n")
+
+ create_file_and_commit("fix: mama gotta work")
+ create_file_and_commit("feat: add more stuff")
+ create_file_and_commit("Merge into master")
+
+ testargs = ["cz", "changelog", "--incremental"]
+
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ with open(changelog_path, "r") as f:
+ out = f.read()
+
+ today = date.today().isoformat()
+ assert (
+ out
+ == f"\n\n## Unreleased \n\n### Feat\n\n- add more stuff\n\n### Fix\n\n- mama gotta work\n\n## 0.2.0 ({today})\n\n### Fix\n\n- output glitch\n\n### Feat\n\n- add new output\n\nnote: this should be persisted using increment\n"
+ )
+
+
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_changlog_incremental_angular_sample(mocker, capsys):
+ changelog_path = os.path.join(os.getcwd(), "CHANGELOG.md")
+ with open(changelog_path, "w") as f:
+ f.write(
+ "# [10.0.0-next.3](https://github.com/angular/angular/compare/10.0.0-next.2...10.0.0-next.3) (2020-04-22)\n"
+ "\n"
+ "### Bug Fixes"
+ "\n"
+ "* **common:** format day-periods that cross midnight ([#36611](https://github.com/angular/angular/issues/36611)) ([c6e5fc4](https://github.com/angular/angular/commit/c6e5fc4)), closes [#36566](https://github.com/angular/angular/issues/36566)\n"
+ )
+ create_file_and_commit("irrelevant commit")
+ git.tag("10.0.0-next.3")
+
+ create_file_and_commit("feat: add new output")
+ create_file_and_commit("fix: output glitch")
+ create_file_and_commit("fix: mama gotta work")
+ create_file_and_commit("feat: add more stuff")
+ create_file_and_commit("Merge into master")
+
+ testargs = ["cz", "changelog", "--incremental"]
+
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ with open(changelog_path, "r") as f:
+ out = f.read()
+
+ assert (
+ out
+ == "\n## Unreleased \n\n### Feat\n\n- add more stuff\n- add new output\n\n### Fix\n\n- mama gotta work\n- output glitch\n\n# [10.0.0-next.3](https://github.com/angular/angular/compare/10.0.0-next.2...10.0.0-next.3) (2020-04-22)\n\n### Bug Fixes\n* **common:** format day-periods that cross midnight ([#36611](https://github.com/angular/angular/issues/36611)) ([c6e5fc4](https://github.com/angular/angular/commit/c6e5fc4)), closes [#36566](https://github.com/angular/angular/issues/36566)\n"
+ )
+
+
+KEEP_A_CHANGELOG = """# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [1.0.0] - 2017-06-20
+### Added
+- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8).
+- Version navigation.
+
+### Changed
+- Start using "changelog" over "change log" since it's the common usage.
+
+### Removed
+- Section about "changelog" vs "CHANGELOG".
+
+## [0.3.0] - 2015-12-03
+### Added
+- RU translation from [@aishek](https://github.com/aishek).
+"""
+
+
+@pytest.mark.usefixtures("tmp_commitizen_project")
+def test_changlog_incremental_keep_a_changelog_sample(mocker, capsys):
+ changelog_path = os.path.join(os.getcwd(), "CHANGELOG.md")
+ with open(changelog_path, "w") as f:
+ f.write(KEEP_A_CHANGELOG)
+ create_file_and_commit("irrelevant commit")
+ git.tag("1.0.0")
+
+ create_file_and_commit("feat: add new output")
+ create_file_and_commit("fix: output glitch")
+ create_file_and_commit("fix: mama gotta work")
+ create_file_and_commit("feat: add more stuff")
+ create_file_and_commit("Merge into master")
+
+ testargs = ["cz", "changelog", "--incremental"]
+
+ mocker.patch.object(sys, "argv", testargs)
+ cli.main()
+
+ with open(changelog_path, "r") as f:
+ out = f.read()
+
+ assert (
+ out
+ == """# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n\n## Unreleased \n\n### Feat\n\n- add more stuff\n- add new output\n\n### Fix\n\n- mama gotta work\n- output glitch\n\n## [1.0.0] - 2017-06-20\n### Added\n- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8).\n- Version navigation.\n\n### Changed\n- Start using "changelog" over "change log" since it\'s the common usage.\n\n### Removed\n- Section about "changelog" vs "CHANGELOG".\n\n## [0.3.0] - 2015-12-03\n### Added\n- RU translation from [@aishek](https://github.com/aishek).\n"""
+ )
diff --git a/tests/test_changelog.py b/tests/test_changelog.py
new file mode 100644
index 0000000000..94c3c47017
--- /dev/null
+++ b/tests/test_changelog.py
@@ -0,0 +1,703 @@
+import pytest
+
+from commitizen import changelog, defaults, git
+
+COMMITS_DATA = [
+ {
+ "rev": "141ee441c9c9da0809c554103a558eb17c30ed17",
+ "title": "bump: version 1.1.1 → 1.2.0",
+ "body": "",
+ },
+ {
+ "rev": "6c4948501031b7d6405b54b21d3d635827f9421b",
+ "title": "docs: how to create custom bumps",
+ "body": "",
+ },
+ {
+ "rev": "ddd220ad515502200fe2dde443614c1075d26238",
+ "title": "feat: custom cz plugins now support bumping version",
+ "body": "",
+ },
+ {
+ "rev": "ad17acff2e3a2e141cbc3c6efd7705e4e6de9bfc",
+ "title": "docs: added bump gif",
+ "body": "",
+ },
+ {
+ "rev": "56c8a8da84e42b526bcbe130bd194306f7c7e813",
+ "title": "bump: version 1.1.0 → 1.1.1",
+ "body": "",
+ },
+ {
+ "rev": "74c6134b1b2e6bb8b07ed53410faabe99b204f36",
+ "title": "refactor: changed stdout statements",
+ "body": "",
+ },
+ {
+ "rev": "cbc7b5f22c4e74deff4bc92d14e19bd93524711e",
+ "title": "fix(bump): commit message now fits better with semver",
+ "body": "",
+ },
+ {
+ "rev": "1ba46f2a63cb9d6e7472eaece21528c8cd28b118",
+ "title": "fix: conventional commit 'breaking change' in body instead of title",
+ "body": "closes #16",
+ },
+ {
+ "rev": "c35dbffd1bb98bb0b3d1593797e79d1c3366af8f",
+ "title": "refactor(schema): command logic removed from commitizen base",
+ "body": "",
+ },
+ {
+ "rev": "25313397a4ac3dc5b5c986017bee2a614399509d",
+ "title": "refactor(info): command logic removed from commitizen base",
+ "body": "",
+ },
+ {
+ "rev": "d2f13ac41b4e48995b3b619d931c82451886e6ff",
+ "title": "refactor(example): command logic removed from commitizen base",
+ "body": "",
+ },
+ {
+ "rev": "d839e317e5b26671b010584ad8cc6bf362400fa1",
+ "title": "refactor(commit): moved most of the commit logic to the commit command",
+ "body": "",
+ },
+ {
+ "rev": "12d0e65beda969f7983c444ceedc2a01584f4e08",
+ "title": "docs(README): updated documentation url)",
+ "body": "",
+ },
+ {
+ "rev": "fb4c85abe51c228e50773e424cbd885a8b6c610d",
+ "title": "docs: mkdocs documentation",
+ "body": "",
+ },
+ {
+ "rev": "17efb44d2cd16f6621413691a543e467c7d2dda6",
+ "title": "Bump version 1.0.0 → 1.1.0",
+ "body": "",
+ },
+ {
+ "rev": "6012d9eecfce8163d75c8fff179788e9ad5347da",
+ "title": "test: fixed issues with conf",
+ "body": "",
+ },
+ {
+ "rev": "0c7fb0ca0168864dfc55d83c210da57771a18319",
+ "title": "docs(README): some new information about bump",
+ "body": "",
+ },
+ {
+ "rev": "cb1dd2019d522644da5bdc2594dd6dee17122d7f",
+ "title": "feat: new working bump command",
+ "body": "",
+ },
+ {
+ "rev": "9c7450f85df6bf6be508e79abf00855a30c3c73c",
+ "title": "feat: create version tag",
+ "body": "",
+ },
+ {
+ "rev": "9f3af3772baab167e3fd8775d37f041440184251",
+ "title": "docs: added new changelog",
+ "body": "",
+ },
+ {
+ "rev": "b0d6a3defbfde14e676e7eb34946409297d0221b",
+ "title": "feat: update given files with new version",
+ "body": "",
+ },
+ {
+ "rev": "d630d07d912e420f0880551f3ac94e933f9d3beb",
+ "title": "fix: removed all from commit",
+ "body": "",
+ },
+ {
+ "rev": "1792b8980c58787906dbe6836f93f31971b1ec2d",
+ "title": "feat(config): new set key, used to set version to cfg",
+ "body": "",
+ },
+ {
+ "rev": "52def1ea3555185ba4b936b463311949907e31ec",
+ "title": "feat: support for pyproject.toml",
+ "body": "",
+ },
+ {
+ "rev": "3127e05077288a5e2b62893345590bf1096141b7",
+ "title": "feat: first semantic version bump implementaiton",
+ "body": "",
+ },
+ {
+ "rev": "fd480ed90a80a6ffa540549408403d5b60d0e90c",
+ "title": "fix: fix config file not working",
+ "body": "",
+ },
+ {
+ "rev": "e4840a059731c0bf488381ffc77e989e85dd81ad",
+ "title": "refactor: added commands folder, better integration with decli",
+ "body": "",
+ },
+ {
+ "rev": "aa44a92d68014d0da98965c0c2cb8c07957d4362",
+ "title": "Bump version: 1.0.0b2 → 1.0.0",
+ "body": "",
+ },
+ {
+ "rev": "58bb709765380dbd46b74ce6e8978515764eb955",
+ "title": "docs(README): new badges",
+ "body": "",
+ },
+ {
+ "rev": "97afb0bb48e72b6feca793091a8a23c706693257",
+ "title": "Merge pull request #10 from Woile/feat/decli",
+ "body": "Feat/decli",
+ },
+ {
+ "rev": "9cecb9224aa7fa68d4afeac37eba2a25770ef251",
+ "title": "style: black to files",
+ "body": "",
+ },
+ {
+ "rev": "f5781d1a2954d71c14ade2a6a1a95b91310b2577",
+ "title": "ci: added travis",
+ "body": "",
+ },
+ {
+ "rev": "80105fb3c6d45369bc0cbf787bd329fba603864c",
+ "title": "refactor: removed delegator, added decli and many tests",
+ "body": "BREAKING CHANGE: API is stable",
+ },
+ {
+ "rev": "a96008496ffefb6b1dd9b251cb479eac6a0487f7",
+ "title": "docs: updated test command",
+ "body": "",
+ },
+ {
+ "rev": "aab33d13110f26604fb786878856ec0b9e5fc32b",
+ "title": "Bump version: 1.0.0b1 → 1.0.0b2",
+ "body": "",
+ },
+ {
+ "rev": "b73791563d2f218806786090fb49ef70faa51a3a",
+ "title": "docs(README): updated to reflect current state",
+ "body": "",
+ },
+ {
+ "rev": "7aa06a454fb717408b3657faa590731fb4ab3719",
+ "title": "Merge pull request #9 from Woile/dev",
+ "body": "feat: py3 only, tests and conventional commits 1.0",
+ },
+ {
+ "rev": "7c7e96b723c2aaa1aec3a52561f680adf0b60e97",
+ "title": "Bump version: 0.9.11 → 1.0.0b1",
+ "body": "",
+ },
+ {
+ "rev": "ed830019581c83ba633bfd734720e6758eca6061",
+ "title": "feat: py3 only, tests and conventional commits 1.0",
+ "body": "more tests\npyproject instead of Pipfile\nquestionary instead of whaaaaat (promptkit 2.0.0 support)",
+ },
+ {
+ "rev": "c52eca6f74f844ab3ffbde61d98ef96071e132b7",
+ "title": "Bump version: 0.9.10 → 0.9.11",
+ "body": "",
+ },
+ {
+ "rev": "0326652b2657083929507ee66d4d1a0899e861ba",
+ "title": "fix(config): load config reads in order without failing if there is no commitizen section",
+ "body": "Closes #8",
+ },
+ {
+ "rev": "b3f89892222340150e32631ae6b7aab65230036f",
+ "title": "Bump version: 0.9.9 → 0.9.10",
+ "body": "",
+ },
+ {
+ "rev": "5e837bf8ef0735193597372cd2d85e31a8f715b9",
+ "title": "fix: parse scope (this is my punishment for not having tests)",
+ "body": "",
+ },
+ {
+ "rev": "684e0259cc95c7c5e94854608cd3dcebbd53219e",
+ "title": "Bump version: 0.9.8 → 0.9.9",
+ "body": "",
+ },
+ {
+ "rev": "ca38eac6ff09870851b5c76a6ff0a2a8e5ecda15",
+ "title": "fix: parse scope empty",
+ "body": "",
+ },
+ {
+ "rev": "64168f18d4628718c49689ee16430549e96c5d4b",
+ "title": "Bump version: 0.9.7 → 0.9.8",
+ "body": "",
+ },
+ {
+ "rev": "9d4def716ef235a1fa5ae61614366423fbc8256f",
+ "title": "fix(scope): parse correctly again",
+ "body": "",
+ },
+ {
+ "rev": "33b0bf1a0a4dc60aac45ed47476d2e5473add09e",
+ "title": "Bump version: 0.9.6 → 0.9.7",
+ "body": "",
+ },
+ {
+ "rev": "696885e891ec35775daeb5fec3ba2ab92c2629e1",
+ "title": "fix(scope): parse correctly",
+ "body": "",
+ },
+ {
+ "rev": "bef4a86761a3bda309c962bae5d22ce9b57119e4",
+ "title": "Bump version: 0.9.5 → 0.9.6",
+ "body": "",
+ },
+ {
+ "rev": "72472efb80f08ee3fd844660afa012c8cb256e4b",
+ "title": "refactor(conventionalCommit): moved fitlers to questions instead of message",
+ "body": "",
+ },
+ {
+ "rev": "b5561ce0ab3b56bb87712c8f90bcf37cf2474f1b",
+ "title": "fix(manifest): inluded missing files",
+ "body": "",
+ },
+ {
+ "rev": "3e31714dc737029d96898f412e4ecd2be1bcd0ce",
+ "title": "Bump version: 0.9.4 → 0.9.5",
+ "body": "",
+ },
+ {
+ "rev": "9df721e06595fdd216884c36a28770438b4f4a39",
+ "title": "fix(config): home path for python versions between 3.0 and 3.5",
+ "body": "",
+ },
+ {
+ "rev": "0cf6ada372470c8d09e6c9e68ebf94bbd5a1656f",
+ "title": "Bump version: 0.9.3 → 0.9.4",
+ "body": "",
+ },
+ {
+ "rev": "973c6b3e100f6f69a3fe48bd8ee55c135b96c318",
+ "title": "feat(cli): added version",
+ "body": "",
+ },
+ {
+ "rev": "dacc86159b260ee98eb5f57941c99ba731a01399",
+ "title": "Bump version: 0.9.2 → 0.9.3",
+ "body": "",
+ },
+ {
+ "rev": "4368f3c3cbfd4a1ced339212230d854bc5bab496",
+ "title": "feat(commiter): conventional commit is a bit more intelligent now",
+ "body": "",
+ },
+ {
+ "rev": "da94133288727d35dae9b91866a25045038f2d38",
+ "title": "docs(README): motivation",
+ "body": "",
+ },
+ {
+ "rev": "1541f54503d2e1cf39bd777c0ca5ab5eb78772ba",
+ "title": "Bump version: 0.9.1 → 0.9.2",
+ "body": "",
+ },
+ {
+ "rev": "ddc855a637b7879108308b8dbd85a0fd27c7e0e7",
+ "title": "refactor: renamed conventional_changelog to conventional_commits, not backward compatible",
+ "body": "",
+ },
+ {
+ "rev": "46e9032e18a819e466618c7a014bcb0e9981af9e",
+ "title": "Bump version: 0.9.0 → 0.9.1",
+ "body": "",
+ },
+ {
+ "rev": "0fef73cd7dc77a25b82e197e7c1d3144a58c1350",
+ "title": "fix(setup.py): future is now required for every python version",
+ "body": "",
+ },
+]
+
+
+TAGS = [
+ ("v1.2.0", "141ee441c9c9da0809c554103a558eb17c30ed17", "2019-04-19"),
+ ("v1.1.1", "56c8a8da84e42b526bcbe130bd194306f7c7e813", "2019-04-18"),
+ ("v1.1.0", "17efb44d2cd16f6621413691a543e467c7d2dda6", "2019-04-14"),
+ ("v1.0.0", "aa44a92d68014d0da98965c0c2cb8c07957d4362", "2019-03-01"),
+ ("1.0.0b2", "aab33d13110f26604fb786878856ec0b9e5fc32b", "2019-01-18"),
+ ("v1.0.0b1", "7c7e96b723c2aaa1aec3a52561f680adf0b60e97", "2019-01-17"),
+ ("v0.9.11", "c52eca6f74f844ab3ffbde61d98ef96071e132b7", "2018-12-17"),
+ ("v0.9.10", "b3f89892222340150e32631ae6b7aab65230036f", "2018-09-22"),
+ ("v0.9.9", "684e0259cc95c7c5e94854608cd3dcebbd53219e", "2018-09-22"),
+ ("v0.9.8", "64168f18d4628718c49689ee16430549e96c5d4b", "2018-09-22"),
+ ("v0.9.7", "33b0bf1a0a4dc60aac45ed47476d2e5473add09e", "2018-09-22"),
+ ("v0.9.6", "bef4a86761a3bda309c962bae5d22ce9b57119e4", "2018-09-19"),
+ ("v0.9.5", "3e31714dc737029d96898f412e4ecd2be1bcd0ce", "2018-08-24"),
+ ("v0.9.4", "0cf6ada372470c8d09e6c9e68ebf94bbd5a1656f", "2018-08-02"),
+ ("v0.9.3", "dacc86159b260ee98eb5f57941c99ba731a01399", "2018-07-28"),
+ ("v0.9.2", "1541f54503d2e1cf39bd777c0ca5ab5eb78772ba", "2017-11-11"),
+ ("v0.9.1", "46e9032e18a819e466618c7a014bcb0e9981af9e", "2017-11-11"),
+]
+
+
+@pytest.fixture # type: ignore
+def gitcommits() -> list:
+ commits = [
+ git.GitCommit(commit["rev"], commit["title"], commit["body"])
+ for commit in COMMITS_DATA
+ ]
+ return commits
+
+
+@pytest.fixture # type: ignore
+def tags() -> list:
+ tags = [git.GitTag(*tag) for tag in TAGS]
+ return tags
+
+
+@pytest.fixture # type: ignore
+def changelog_content() -> str:
+ changelog_path = "tests/CHANGELOG_FOR_TEST.md"
+ with open(changelog_path, "r") as f:
+ return f.read()
+
+
+def test_get_commit_tag_is_a_version(gitcommits, tags):
+ commit = gitcommits[0]
+ tag = git.GitTag(*TAGS[0])
+ current_key = changelog.get_commit_tag(commit, tags)
+ assert current_key == tag
+
+
+def test_get_commit_tag_is_None(gitcommits, tags):
+ commit = gitcommits[1]
+ current_key = changelog.get_commit_tag(commit, tags)
+ assert current_key is None
+
+
+def test_generate_tree_from_commits(gitcommits, tags):
+ parser = defaults.commit_parser
+ changelog_pattern = defaults.bump_pattern
+ tree = changelog.generate_tree_from_commits(
+ gitcommits, tags, parser, changelog_pattern
+ )
+
+ assert tuple(tree) == (
+ {
+ "version": "v1.2.0",
+ "date": "2019-04-19",
+ "changes": {
+ "feat": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "custom cz plugins now support bumping version",
+ }
+ ]
+ },
+ },
+ {
+ "version": "v1.1.1",
+ "date": "2019-04-18",
+ "changes": {
+ "refactor": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "changed stdout statements",
+ },
+ {
+ "scope": "schema",
+ "breaking": None,
+ "message": "command logic removed from commitizen base",
+ },
+ {
+ "scope": "info",
+ "breaking": None,
+ "message": "command logic removed from commitizen base",
+ },
+ {
+ "scope": "example",
+ "breaking": None,
+ "message": "command logic removed from commitizen base",
+ },
+ {
+ "scope": "commit",
+ "breaking": None,
+ "message": "moved most of the commit logic to the commit command",
+ },
+ ],
+ "fix": [
+ {
+ "scope": "bump",
+ "breaking": None,
+ "message": "commit message now fits better with semver",
+ },
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "conventional commit 'breaking change' in body instead of title",
+ },
+ ],
+ },
+ },
+ {
+ "version": "v1.1.0",
+ "date": "2019-04-14",
+ "changes": {
+ "feat": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "new working bump command",
+ },
+ {"scope": None, "breaking": None, "message": "create version tag"},
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "update given files with new version",
+ },
+ {
+ "scope": "config",
+ "breaking": None,
+ "message": "new set key, used to set version to cfg",
+ },
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "support for pyproject.toml",
+ },
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "first semantic version bump implementaiton",
+ },
+ ],
+ "fix": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "removed all from commit",
+ },
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "fix config file not working",
+ },
+ ],
+ "refactor": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "added commands folder, better integration with decli",
+ }
+ ],
+ },
+ },
+ {
+ "version": "v1.0.0",
+ "date": "2019-03-01",
+ "changes": {
+ "refactor": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "removed delegator, added decli and many tests",
+ }
+ ],
+ "BREAKING CHANGE": [
+ {"scope": None, "breaking": None, "message": "API is stable"}
+ ],
+ },
+ },
+ {"version": "1.0.0b2", "date": "2019-01-18", "changes": {}},
+ {
+ "version": "v1.0.0b1",
+ "date": "2019-01-17",
+ "changes": {
+ "feat": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "py3 only, tests and conventional commits 1.0",
+ }
+ ]
+ },
+ },
+ {
+ "version": "v0.9.11",
+ "date": "2018-12-17",
+ "changes": {
+ "fix": [
+ {
+ "scope": "config",
+ "breaking": None,
+ "message": "load config reads in order without failing if there is no commitizen section",
+ }
+ ]
+ },
+ },
+ {
+ "version": "v0.9.10",
+ "date": "2018-09-22",
+ "changes": {
+ "fix": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "parse scope (this is my punishment for not having tests)",
+ }
+ ]
+ },
+ },
+ {
+ "version": "v0.9.9",
+ "date": "2018-09-22",
+ "changes": {
+ "fix": [
+ {"scope": None, "breaking": None, "message": "parse scope empty"}
+ ]
+ },
+ },
+ {
+ "version": "v0.9.8",
+ "date": "2018-09-22",
+ "changes": {
+ "fix": [
+ {
+ "scope": "scope",
+ "breaking": None,
+ "message": "parse correctly again",
+ }
+ ]
+ },
+ },
+ {
+ "version": "v0.9.7",
+ "date": "2018-09-22",
+ "changes": {
+ "fix": [
+ {"scope": "scope", "breaking": None, "message": "parse correctly"}
+ ]
+ },
+ },
+ {
+ "version": "v0.9.6",
+ "date": "2018-09-19",
+ "changes": {
+ "refactor": [
+ {
+ "scope": "conventionalCommit",
+ "breaking": None,
+ "message": "moved fitlers to questions instead of message",
+ }
+ ],
+ "fix": [
+ {
+ "scope": "manifest",
+ "breaking": None,
+ "message": "inluded missing files",
+ }
+ ],
+ },
+ },
+ {
+ "version": "v0.9.5",
+ "date": "2018-08-24",
+ "changes": {
+ "fix": [
+ {
+ "scope": "config",
+ "breaking": None,
+ "message": "home path for python versions between 3.0 and 3.5",
+ }
+ ]
+ },
+ },
+ {
+ "version": "v0.9.4",
+ "date": "2018-08-02",
+ "changes": {
+ "feat": [{"scope": "cli", "breaking": None, "message": "added version"}]
+ },
+ },
+ {
+ "version": "v0.9.3",
+ "date": "2018-07-28",
+ "changes": {
+ "feat": [
+ {
+ "scope": "commiter",
+ "breaking": None,
+ "message": "conventional commit is a bit more intelligent now",
+ }
+ ]
+ },
+ },
+ {
+ "version": "v0.9.2",
+ "date": "2017-11-11",
+ "changes": {
+ "refactor": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "renamed conventional_changelog to conventional_commits, not backward compatible",
+ }
+ ]
+ },
+ },
+ {
+ "version": "v0.9.1",
+ "date": "2017-11-11",
+ "changes": {
+ "fix": [
+ {
+ "scope": "setup.py",
+ "breaking": None,
+ "message": "future is now required for every python version",
+ }
+ ]
+ },
+ },
+ )
+
+
+def test_render_changelog(gitcommits, tags, changelog_content):
+ parser = defaults.commit_parser
+ changelog_pattern = defaults.bump_pattern
+ tree = changelog.generate_tree_from_commits(
+ gitcommits, tags, parser, changelog_pattern
+ )
+ result = changelog.render_changelog(tree)
+ assert result == changelog_content
+
+
+def test_render_changelog_unreleased(gitcommits):
+ some_commits = gitcommits[:7]
+ parser = defaults.commit_parser
+ changelog_pattern = defaults.bump_pattern
+ tree = changelog.generate_tree_from_commits(
+ some_commits, [], parser, changelog_pattern
+ )
+ result = changelog.render_changelog(tree)
+ assert "Unreleased" in result
+
+
+def test_render_changelog_tag_and_unreleased(gitcommits, tags):
+ some_commits = gitcommits[:7]
+ single_tag = [
+ tag for tag in tags if tag.rev == "56c8a8da84e42b526bcbe130bd194306f7c7e813"
+ ]
+
+ parser = defaults.commit_parser
+ changelog_pattern = defaults.bump_pattern
+ tree = changelog.generate_tree_from_commits(
+ some_commits, single_tag, parser, changelog_pattern
+ )
+ result = changelog.render_changelog(tree)
+
+ assert "Unreleased" in result
+ assert "## v1.1.1" in result
diff --git a/tests/test_changelog_meta.py b/tests/test_changelog_meta.py
new file mode 100644
index 0000000000..505daefb83
--- /dev/null
+++ b/tests/test_changelog_meta.py
@@ -0,0 +1,165 @@
+import os
+
+import pytest
+
+from commitizen import changelog
+
+CHANGELOG_A = """
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+- Start using "changelog" over "change log" since it's the common usage.
+
+## [1.0.0] - 2017-06-20
+### Added
+- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8).
+- Version navigation.
+""".strip()
+
+CHANGELOG_B = """
+## [Unreleased]
+- Start using "changelog" over "change log" since it's the common usage.
+
+## 1.2.0
+""".strip()
+
+CHANGELOG_C = """
+# Unreleased
+
+## v1.0.0
+"""
+
+CHANGELOG_D = """
+## Unreleased
+- Start using "changelog" over "change log" since it's the common usage.
+"""
+
+
+@pytest.fixture
+def changelog_a_file():
+ changelog_path = "tests/CHANGELOG.md"
+
+ with open(changelog_path, "w") as f:
+ f.write(CHANGELOG_A)
+
+ yield changelog_path
+
+ os.remove(changelog_path)
+
+
+@pytest.fixture
+def changelog_b_file():
+ changelog_path = "tests/CHANGELOG.md"
+
+ with open(changelog_path, "w") as f:
+ f.write(CHANGELOG_B)
+
+ yield changelog_path
+
+ os.remove(changelog_path)
+
+
+@pytest.fixture
+def changelog_c_file():
+ changelog_path = "tests/CHANGELOG.md"
+
+ with open(changelog_path, "w") as f:
+ f.write(CHANGELOG_C)
+
+ yield changelog_path
+
+ os.remove(changelog_path)
+
+
+@pytest.fixture
+def changelog_d_file():
+ changelog_path = "tests/CHANGELOG.md"
+
+ with open(changelog_path, "w") as f:
+ f.write(CHANGELOG_D)
+
+ yield changelog_path
+
+ os.remove(changelog_path)
+
+
+VERSIONS_EXAMPLES = [
+ ("## [1.0.0] - 2017-06-20", "1.0.0"),
+ (
+ "# [10.0.0-next.3](https://github.com/angular/angular/compare/10.0.0-next.2...10.0.0-next.3) (2020-04-22)",
+ "10.0.0-next.3",
+ ),
+ ("### 0.19.1 (Jan 7, 2020)", "0.19.1"),
+ ("## 1.0.0", "1.0.0"),
+ ("## v1.0.0", "1.0.0"),
+ ("## v1.0.0 - (2012-24-32)", "1.0.0"),
+ ("# version 2020.03.24", "2020.03.24"),
+ ("## [Unreleased]", None),
+ ("All notable changes to this project will be documented in this file.", None),
+ ("# Changelog", None),
+ ("### Bug Fixes", None),
+]
+
+
+@pytest.mark.parametrize("line_from_changelog,output_version", VERSIONS_EXAMPLES)
+def test_changelog_detect_version(line_from_changelog, output_version):
+ version = changelog.parse_version_from_markdown(line_from_changelog)
+ assert version == output_version
+
+
+TITLES_EXAMPLES = [
+ ("## [1.0.0] - 2017-06-20", "##"),
+ ("## [Unreleased]", "##"),
+ ("# Unreleased", "#"),
+]
+
+
+@pytest.mark.parametrize("line_from_changelog,output_title", TITLES_EXAMPLES)
+def test_parse_title_type_of_line(line_from_changelog, output_title):
+ title = changelog.parse_title_type_of_line(line_from_changelog)
+ assert title == output_title
+
+
+def test_get_metadata_from_a(changelog_a_file):
+ meta = changelog.get_metadata(changelog_a_file)
+ assert meta == {
+ "latest_version": "1.0.0",
+ "latest_version_position": 10,
+ "unreleased_end": 10,
+ "unreleased_start": 7,
+ }
+
+
+def test_get_metadata_from_b(changelog_b_file):
+ meta = changelog.get_metadata(changelog_b_file)
+ assert meta == {
+ "latest_version": "1.2.0",
+ "latest_version_position": 3,
+ "unreleased_end": 3,
+ "unreleased_start": 0,
+ }
+
+
+def test_get_metadata_from_c(changelog_c_file):
+ meta = changelog.get_metadata(changelog_c_file)
+ assert meta == {
+ "latest_version": "1.0.0",
+ "latest_version_position": 3,
+ "unreleased_end": 3,
+ "unreleased_start": 1,
+ }
+
+
+def test_get_metadata_from_d(changelog_d_file):
+ meta = changelog.get_metadata(changelog_d_file)
+ assert meta == {
+ "latest_version": None,
+ "latest_version_position": None,
+ "unreleased_end": 2,
+ "unreleased_start": 1,
+ }
diff --git a/tests/test_changelog_parser.py b/tests/test_changelog_parser.py
new file mode 100644
index 0000000000..f07d8c3f63
--- /dev/null
+++ b/tests/test_changelog_parser.py
@@ -0,0 +1,194 @@
+import os
+
+import pytest
+
+from commitizen import changelog_parser
+
+CHANGELOG_TEMPLATE = """
+## 1.0.0 (2019-07-12)
+
+### Fix
+
+- issue in poetry add preventing the installation in py36
+- **users**: lorem ipsum apap
+
+### Feat
+
+- it is possible to specify a pattern to be matched in configuration files bump.
+
+## 0.9 (2019-07-11)
+
+### Fix
+
+- holis
+
+"""
+
+
+@pytest.fixture # type: ignore
+def changelog_content() -> str:
+ changelog_path = "tests/CHANGELOG_FOR_TEST.md"
+ with open(changelog_path, "r") as f:
+ return f.read()
+
+
+@pytest.fixture
+def existing_changelog_file():
+ changelog_path = "tests/CHANGELOG.md"
+
+ with open(changelog_path, "w") as f:
+ f.write(CHANGELOG_TEMPLATE)
+
+ yield changelog_path
+
+ os.remove(changelog_path)
+
+
+def test_read_changelog_blocks(existing_changelog_file):
+ blocks = changelog_parser.find_version_blocks(existing_changelog_file)
+ blocks = list(blocks)
+ amount_of_blocks = len(blocks)
+ assert amount_of_blocks == 2
+
+
+VERSION_CASES: list = [
+ ("## 1.0.0 (2019-07-12)", {"version": "1.0.0", "date": "2019-07-12"}),
+ ("## 2.3.0a0", {"version": "2.3.0a0", "date": None}),
+ ("## 0.10.0a0", {"version": "0.10.0a0", "date": None}),
+ ("## 1.0.0rc0", {"version": "1.0.0rc0", "date": None}),
+ ("## 1beta", {"version": "1beta", "date": None}),
+ (
+ "## 1.0.0rc1+e20d7b57f3eb (2019-3-24)",
+ {"version": "1.0.0rc1+e20d7b57f3eb", "date": "2019-3-24"},
+ ),
+ ("### Bug fixes", {}),
+ ("- issue in poetry add preventing the installation in py36", {}),
+]
+
+CATEGORIES_CASES: list = [
+ ("## 1.0.0 (2019-07-12)", {}),
+ ("## 2.3.0a0", {}),
+ ("### Bug fixes", {"change_type": "Bug fixes"}),
+ ("### Features", {"change_type": "Features"}),
+ ("- issue in poetry add preventing the installation in py36", {}),
+]
+
+CATEGORIES_TRANSFORMATIONS: list = [
+ ("Bug fixes", "fix"),
+ ("Features", "feat"),
+ ("BREAKING CHANGES", "BREAKING CHANGES"),
+]
+
+MESSAGES_CASES: list = [
+ ("## 1.0.0 (2019-07-12)", {}),
+ ("## 2.3.0a0", {}),
+ ("### Fix", {}),
+ (
+ "- name no longer accept invalid chars",
+ {
+ "message": "name no longer accept invalid chars",
+ "scope": None,
+ "breaking": None,
+ },
+ ),
+ (
+ "- **users**: lorem ipsum apap",
+ {"message": "lorem ipsum apap", "scope": "users", "breaking": None},
+ ),
+]
+
+
+@pytest.mark.parametrize("test_input,expected", VERSION_CASES)
+def test_parse_md_version(test_input, expected):
+ assert changelog_parser.parse_md_version(test_input) == expected
+
+
+@pytest.mark.parametrize("test_input,expected", CATEGORIES_CASES)
+def test_parse_md_change_type(test_input, expected):
+ assert changelog_parser.parse_md_change_type(test_input) == expected
+
+
+@pytest.mark.parametrize("test_input,expected", CATEGORIES_TRANSFORMATIONS)
+def test_transform_change_type(test_input, expected):
+ assert changelog_parser.transform_change_type(test_input) == expected
+
+
+@pytest.mark.parametrize("test_input,expected", MESSAGES_CASES)
+def test_parse_md_message(test_input, expected):
+ assert changelog_parser.parse_md_message(test_input) == expected
+
+
+def test_transform_change_type_fail():
+ with pytest.raises(ValueError) as excinfo:
+ changelog_parser.transform_change_type("Bugs")
+ assert "Could not match a change_type" in str(excinfo.value)
+
+
+def test_generate_block_tree(existing_changelog_file):
+ blocks = changelog_parser.find_version_blocks(existing_changelog_file)
+ block = next(blocks)
+ tree = changelog_parser.generate_block_tree(block)
+ assert tree == {
+ "changes": {
+ "fix": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": "issue in poetry add preventing the installation in py36",
+ },
+ {"scope": "users", "breaking": None, "message": "lorem ipsum apap"},
+ ],
+ "feat": [
+ {
+ "scope": None,
+ "breaking": None,
+ "message": (
+ "it is possible to specify a pattern to be matched "
+ "in configuration files bump."
+ ),
+ }
+ ],
+ },
+ "version": "1.0.0",
+ "date": "2019-07-12",
+ }
+
+
+def test_generate_full_tree(existing_changelog_file):
+ blocks = changelog_parser.find_version_blocks(existing_changelog_file)
+ tree = list(changelog_parser.generate_full_tree(blocks))
+
+ assert tree == [
+ {
+ "changes": {
+ "fix": [
+ {
+ "scope": None,
+ "message": "issue in poetry add preventing the installation in py36",
+ "breaking": None,
+ },
+ {
+ "scope": "users",
+ "message": "lorem ipsum apap",
+ "breaking": None,
+ },
+ ],
+ "feat": [
+ {
+ "scope": None,
+ "message": "it is possible to specify a pattern to be matched in configuration files bump.",
+ "breaking": None,
+ },
+ ],
+ },
+ "version": "1.0.0",
+ "date": "2019-07-12",
+ },
+ {
+ "changes": {
+ "fix": [{"scope": None, "message": "holis", "breaking": None}],
+ },
+ "version": "0.9",
+ "date": "2019-07-11",
+ },
+ ]
diff --git a/tests/test_conf.py b/tests/test_conf.py
index 58580e52a6..00dc4d40ad 100644
--- a/tests/test_conf.py
+++ b/tests/test_conf.py
@@ -44,6 +44,7 @@
"bump_message": None,
"version_files": ["commitizen/__version__.py", "pyproject.toml"],
"style": [["pointer", "reverse"], ["question", "underline"]],
+ "changelog_file": "CHANGELOG.md",
}
_new_settings = {
@@ -53,6 +54,7 @@
"bump_message": None,
"version_files": ["commitizen/__version__.py", "pyproject.toml"],
"style": [["pointer", "reverse"], ["question", "underline"]],
+ "changelog_file": "CHANGELOG.md",
}
_read_settings = {
@@ -60,6 +62,7 @@
"version": "1.0.0",
"version_files": ["commitizen/__version__.py", "pyproject.toml"],
"style": [["pointer", "reverse"], ["question", "underline"]],
+ "changelog_file": "CHANGELOG.md",
}
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644
index 0000000000..64598b8df1
--- /dev/null
+++ b/tests/utils.py
@@ -0,0 +1,14 @@
+import uuid
+from pathlib import Path
+from typing import Optional
+
+from commitizen import cmd, git
+
+
+def create_file_and_commit(message: str, filename: Optional[str] = None):
+ if not filename:
+ filename = str(uuid.uuid4())
+
+ Path(f"./{filename}").touch()
+ cmd.run("git add .")
+ git.commit(message)