Skip to content

Commit

Permalink
fix(changelog): handle custom tag_format in changelog generation
Browse files Browse the repository at this point in the history
When the tag_format does not follow the allowed schemas patterns then changlog generation fails.
  • Loading branch information
grahamhar authored and woile committed Sep 26, 2024
1 parent 88f1e21 commit db09538
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 14 deletions.
33 changes: 25 additions & 8 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,34 @@ def tag_included_in_changelog(
return True


def get_version_tags(scheme: type[BaseVersion], tags: list[GitTag]) -> list[GitTag]:
def get_version_tags(
scheme: type[BaseVersion], tags: list[GitTag], tag_format: str
) -> list[GitTag]:
valid_tags: list[GitTag] = []
TAG_FORMAT_REGEXS = {
"$version": str(scheme.parser.pattern),
"$major": r"(?P<major>\d+)",
"$minor": r"(?P<minor>\d+)",
"$patch": r"(?P<patch>\d+)",
"$prerelease": r"(?P<prerelease>\w+\d+)?",
"$devrelease": r"(?P<devrelease>\.dev\d+)?",
"${version}": str(scheme.parser.pattern),
"${major}": r"(?P<major>\d+)",
"${minor}": r"(?P<minor>\d+)",
"${patch}": r"(?P<patch>\d+)",
"${prerelease}": r"(?P<prerelease>\w+\d+)?",
"${devrelease}": r"(?P<devrelease>\.dev\d+)?",
}
tag_format_regex = tag_format
for pattern, regex in TAG_FORMAT_REGEXS.items():
tag_format_regex = tag_format_regex.replace(pattern, regex)
for tag in tags:
try:
scheme(tag.name)
except InvalidVersion:
out.warn(f"InvalidVersion {tag}")
else:
if re.match(tag_format_regex, tag.name):
valid_tags.append(tag)

else:
out.warn(
f"InvalidVersion {tag.name} doesn't match configured tag format {tag_format}"
)
return valid_tags


Expand Down Expand Up @@ -351,7 +369,6 @@ def get_oldest_and_newest_rev(
oldest, newest = version.split("..")
except ValueError:
newest = version

newest_tag = normalize_tag(newest, tag_format=tag_format, scheme=scheme)

oldest_tag = None
Expand Down
22 changes: 21 additions & 1 deletion commitizen/changelog_formats/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,30 @@ def __init__(self, config: BaseConfig):
# See: https://bugs.python.org/issue44807
self.config = config
self.encoding = self.config.settings["encoding"]
self.tag_format = self.config.settings.get("tag_format")

@property
def version_parser(self) -> Pattern:
return get_version_scheme(self.config).parser
version_regex = get_version_scheme(self.config).parser.pattern
if self.tag_format != "$version":
TAG_FORMAT_REGEXS = {
"$version": version_regex,
"$major": "(?P<major>\d+)",
"$minor": "(?P<minor>\d+)",
"$patch": "(?P<patch>\d+)",
"$prerelease": "(?P<prerelease>\w+\d+)?",
"$devrelease": "(?P<devrelease>\.dev\d+)?",
"${version}": version_regex,
"${major}": "(?P<major>\d+)",
"${minor}": "(?P<minor>\d+)",
"${patch}": "(?P<patch>\d+)",
"${prerelease}": "(?P<prerelease>\w+\d+)?",
"${devrelease}": "(?P<devrelease>\.dev\d+)?",
}
version_regex = self.tag_format
for pattern, regex in TAG_FORMAT_REGEXS.items():
version_regex = version_regex.replace(pattern, regex)
return rf"{version_regex}"

def get_metadata(self, filepath: str) -> Metadata:
if not os.path.isfile(filepath):
Expand Down
9 changes: 4 additions & 5 deletions commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,10 @@ def __call__(self):
# Don't continue if no `file_name` specified.
assert self.file_name

tags = changelog.get_version_tags(self.scheme, git.get_tags()) or []

tags = (
changelog.get_version_tags(self.scheme, git.get_tags(), self.tag_format)
or []
)
end_rev = ""
if self.incremental:
changelog_meta = self.changelog_format.get_metadata(self.file_name)
Expand All @@ -182,21 +184,18 @@ def __call__(self):
start_rev = self._find_incremental_rev(
strip_local_version(latest_tag_version), tags
)

if self.rev_range:
start_rev, end_rev = changelog.get_oldest_and_newest_rev(
tags,
version=self.rev_range,
tag_format=self.tag_format,
scheme=self.scheme,
)

commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order")
if not commits and (
self.current_version is None or not self.current_version.is_prerelease
):
raise NoCommitsFoundError("No commits found")

tree = changelog.generate_tree_from_commits(
commits,
tags,
Expand Down
6 changes: 6 additions & 0 deletions commitizen/providers/scm_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class ScmProvider(VersionProvider):
"$patch": r"(?P<patch>\d+)",
"$prerelease": r"(?P<prerelease>\w+\d+)?",
"$devrelease": r"(?P<devrelease>\.dev\d+)?",
"${version}": r"(?P<version>.+)",
"${major}": r"(?P<major>\d+)",
"${minor}": r"(?P<minor>\d+)",
"${patch}": r"(?P<patch>\d+)",
"${prerelease}": r"(?P<prerelease>\w+\d+)?",
"${devrelease}": r"(?P<devrelease>\.dev\d+)?",
}

def _tag_format_matcher(self) -> Callable[[str], VersionProtocol | None]:
Expand Down
40 changes: 40 additions & 0 deletions docs/tutorials/monorepo_guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Configuring commitizen in a monorepo

This tutorial assumes the monorepo layout is designed with multiple components that can be released independently of each other,
some suggested layouts:

```
library-a
.cz.toml
library-b
.cz.toml
```

```
src
library-b
.cz.toml
library-z
.cz.toml
```

Each component will have its own changelog, commits will need to use scopes so only relevant commits are included in the
appropriate change log for a given component. Example config and commit for `library-b`

```toml
[tool.commitizen]
name = "cz_customize"
version = "0.0.0"
tag_format = "${version}-library-b" # the component name can be a prefix or suffix with or without a separator
update_changelog_on_bump = true

[tool.commitizen.customize]
changelog_pattern = "^(feat|fix)\\(library-b\\)(!)?:" #the pattern on types can be a wild card or any types you wish to include
```

example commit message for the above

`fix:(library-b) Some awesome message`

If the above is followed and the `cz bump --changelog` is run in the directory containing the component the changelog
should be generated in the same directory with only commits scoped to the component.

0 comments on commit db09538

Please sign in to comment.