Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 75b9d6a

Browse files
committed
feat(changelog): adds a tag parser to filter tags (--tag-parser; tag_parser)
The git tag parser is used to filter out undesired git tags from changelog. regex default is .* (all).
1 parent 7916511 commit 75b9d6a

File tree

8 files changed

+142
-6
lines changed

8 files changed

+142
-6
lines changed

commitizen/changelog.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import re
3030
from collections import OrderedDict, defaultdict
3131
from datetime import date
32-
from typing import Callable, Dict, Iterable, List, Optional, Tuple
32+
from typing import Callable, Dict, Iterable, List, Optional, Pattern, Tuple
3333

3434
from jinja2 import Environment, PackageLoader
3535

@@ -74,6 +74,7 @@ def generate_tree_from_commits(
7474
unreleased_version: Optional[str] = None,
7575
change_type_map: Optional[Dict[str, str]] = None,
7676
changelog_message_builder_hook: Optional[Callable] = None,
77+
tag_pattern: Pattern = re.compile(".*"),
7778
) -> Iterable[Dict]:
7879
pat = re.compile(changelog_pattern)
7980
map_pat = re.compile(commit_parser, re.MULTILINE)
@@ -87,24 +88,35 @@ def generate_tree_from_commits(
8788
current_tag_date: str = ""
8889
if unreleased_version is not None:
8990
current_tag_date = date.today().isoformat()
90-
if current_tag is not None and current_tag.name:
91+
if (
92+
current_tag is not None
93+
and current_tag.name
94+
and tag_pattern.fullmatch(current_tag.name)
95+
):
9196
current_tag_name = current_tag.name
9297
current_tag_date = current_tag.date
9398

9499
changes: Dict = defaultdict(list)
95100
used_tags: List = [current_tag]
96101
for commit in commits:
97-
commit_tag = get_commit_tag(commit, tags)
98102

99-
if commit_tag is not None and commit_tag not in used_tags:
103+
# determine if we found a new matching tag
104+
commit_tag = get_commit_tag(commit, tags)
105+
is_tag_match = False
106+
if commit_tag:
107+
matches = tag_pattern.fullmatch(commit_tag.name)
108+
if matches and (commit_tag not in used_tags):
109+
is_tag_match = True
110+
111+
# new node if we have a tag match
112+
if is_tag_match:
100113
used_tags.append(commit_tag)
101114
yield {
102115
"version": current_tag_name,
103116
"date": current_tag_date,
104117
"changes": changes,
105118
}
106-
# TODO: Check if tag matches the version pattern, otherwise skip it.
107-
# This in order to prevent tags that are not versions.
119+
assert commit_tag is not None # for mypy
108120
current_tag_name = commit_tag.name
109121
current_tag_date = commit_tag.date
110122
changes = defaultdict(list)

commitizen/cli.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@
220220
"If not set, it will generate changelog from the start"
221221
),
222222
},
223+
{
224+
"name": "--tag-parser",
225+
"help": (
226+
"regex match for tags represented "
227+
"within the changelog. default: '.*'"
228+
),
229+
},
223230
],
224231
},
225232
{

commitizen/commands/changelog.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os.path
2+
import re
23
from difflib import SequenceMatcher
34
from operator import itemgetter
45
from typing import Callable, Dict, List, Optional
@@ -51,6 +52,10 @@ def __init__(self, config: BaseConfig, args):
5152
self.tag_format = args.get("tag_format") or self.config.settings.get(
5253
"tag_format"
5354
)
55+
tag_parser = args.get("tag_parser")
56+
if tag_parser is None:
57+
tag_parser = self.config.settings.get("tag_parser", r".*")
58+
self.tag_pattern = re.compile(tag_parser)
5459

5560
def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
5661
"""Try to find the 'start_rev'.
@@ -154,6 +159,7 @@ def __call__(self):
154159
unreleased_version,
155160
change_type_map=change_type_map,
156161
changelog_message_builder_hook=changelog_message_builder_hook,
162+
tag_pattern=self.tag_pattern,
157163
)
158164
if self.change_type_order:
159165
tree = changelog.order_changelog_tree(tree, self.change_type_order)

commitizen/cz/customize/customize.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ def __init__(self, config: BaseConfig):
5050
if change_type_map:
5151
self.change_type_map = change_type_map
5252

53+
tag_parser = self.custom_settings.get("tag_pattern")
54+
if tag_parser:
55+
self.tag_parser = str(tag_parser)
56+
5357
def questions(self) -> Questions:
5458
return self.custom_settings.get("questions", [{}])
5559

docs/changelog.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@ cz changelog --start-rev="v0.2.0"
161161
changelog_start_rev = "v0.2.0"
162162
```
163163
164+
### `tag-parser`
165+
166+
This value can be set in the `toml` file with the key `tag_parser` under `tools.commitizen`
167+
168+
The default the changelog will capture all git tags (e.g. regex `.*`).
169+
The user may specify a regex pattern of their own display only
170+
specific tags within the changelog.
171+
172+
```bash
173+
cz changelog --tag-parser=".*"
174+
```
175+
176+
```toml
177+
[tools.commitizen]
178+
# ...
179+
tag_parser = "v[0-9]*\\.[0-9]*\\.[0-9]*"
180+
```
181+
164182
## Hooks
165183
166184
Supported hook methods:

docs/config.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ commitizen:
128128
| `version` | `str` | `None` | Current version. Example: "0.1.2" |
129129
| `version_files` | `list` | `[ ]` | Files were the version will be updated. A pattern to match a line, can also be specified, separated by `:` [See more][version_files] |
130130
| `tag_format` | `str` | `None` | Format for the git tag, useful for old projects, that use a convention like `"v1.2.1"`. [See more][tag_format] |
131+
| `tag_parser ` | `str` | `.*` | Generate changelog using only tags matching the regex pattern (e.g. `"v([0-9.])*"`). [See more][tag_parser] |
131132
| `update_changelog_on_bump` | `bool` | `false` | Create changelog when running `cz bump` |
132133
| `annotated_tag` | `bool` | `false` | Use annotated tags instead of lightweight tags. [See difference][annotated-tags-vs-lightweight] |
133134
| `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more][bump_message] |
@@ -142,6 +143,7 @@ commitizen:
142143
[version_files]: bump.md#version_files
143144
[tag_format]: bump.md#tag_format
144145
[bump_message]: bump.md#bump_message
146+
[tag_parser]: changelog.md#tag_parser
145147
[allow_abort]: check.md#allow-abort
146148
[additional-features]: https://github.com/tmbo/questionary#additional-features
147149
[customization]: customization.md

tests/commands/test_changelog_command.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,3 +872,50 @@ def test_changelog_from_rev_latest_version_dry_run(
872872
out, _ = capsys.readouterr()
873873

874874
file_regression.check(out, extension=".md")
875+
876+
877+
@pytest.mark.usefixtures("tmp_commitizen_project")
878+
@pytest.mark.freeze_time("2022-02-13")
879+
@pytest.mark.parametrize(
880+
"cli_args, line, filtered",
881+
[
882+
([], r'tag_parser = "v[0-9]*\\.[0-9]*\\.[0-9]*"', True), # version filter
883+
(["--tag-parser", r"v[0-9]*\.[0-9]*\.[0-9]*"], "", True), # cli arg filter
884+
([], "", False), # default tag_parser
885+
],
886+
)
887+
def test_changelog_tag_parser_config(
888+
mocker, config_path, changelog_path, cli_args, line, filtered
889+
):
890+
mocker.patch("commitizen.git.GitTag.date", "2022-02-13")
891+
892+
with open(config_path, "a") as f:
893+
# f.write('tag_format = "$version"\n')
894+
f.write(line)
895+
896+
# create a valid start tag
897+
create_file_and_commit("feat: initial")
898+
git.tag("v1.0.0")
899+
900+
# create a tag for this test
901+
create_file_and_commit("feat: add new")
902+
git.tag("v1.1.0-beta")
903+
904+
# create a valid end tag
905+
create_file_and_commit("feat: add more")
906+
git.tag("v1.1.0")
907+
908+
# call CLI
909+
command = ["cz", "changelog"]
910+
command.extend(cli_args)
911+
mocker.patch.object(sys, "argv", command)
912+
cli.main()
913+
914+
# open CLI output
915+
with open(changelog_path, "r") as f:
916+
out = f.read()
917+
918+
# test if cli is handling tag_format
919+
assert "v1.0.0" in out
920+
assert ("v1.1.0-beta" in out) is not filtered
921+
assert "v1.1.0" in out

tests/test_changelog.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import re
2+
from typing import Dict, Iterable, List, Pattern
3+
14
import pytest
25

36
from commitizen import changelog, defaults, git
@@ -790,6 +793,26 @@ def test_get_commit_tag_is_None(gitcommits, tags):
790793
)
791794

792795

796+
def _filter_tree(tag_pattern: Pattern, tree: Iterable[Dict]) -> List[Dict[str, str]]:
797+
"""filters the tree. commits with invalid tags are kept within the current node"""
798+
799+
current = None
800+
out = []
801+
for node in tree:
802+
if not current or tag_pattern.fullmatch(node["version"]) or not out:
803+
current = node.copy()
804+
out.append(current)
805+
else:
806+
changes = current["changes"]
807+
for key, value in node["changes"].items():
808+
if key in changes:
809+
changes[key].extend(value)
810+
else:
811+
changes[key] = value
812+
813+
return out
814+
815+
793816
def test_generate_tree_from_commits(gitcommits, tags):
794817
parser = defaults.commit_parser
795818
changelog_pattern = defaults.bump_pattern
@@ -800,6 +823,23 @@ def test_generate_tree_from_commits(gitcommits, tags):
800823
assert tuple(tree) == COMMITS_TREE
801824

802825

826+
def test_generate_tree_from_commits_release_filter(gitcommits, tags):
827+
parser = defaults.commit_parser
828+
changelog_pattern = defaults.bump_pattern
829+
830+
# match release tags only
831+
tag_parser = r"v([0-9]+)\.([0-9]+)\.([0-9]+)"
832+
tag_pattern = re.compile(tag_parser)
833+
834+
tree = changelog.generate_tree_from_commits(
835+
gitcommits, tags, parser, changelog_pattern, tag_pattern=tag_pattern
836+
)
837+
838+
expected_tree = _filter_tree(tag_pattern, COMMITS_TREE)
839+
840+
assert list(tree) == expected_tree
841+
842+
803843
@pytest.mark.parametrize(
804844
"change_type_order, expected_reordering",
805845
(

0 commit comments

Comments
 (0)