Skip to content

feat: add build metadata #1173

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion commitizen/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,16 @@ def normalize_tag(

major, minor, patch = version.release
prerelease = version.prerelease or ""
postrelease = version.postrelease or ""

t = Template(tag_format)
return t.safe_substitute(
version=version, major=major, minor=minor, patch=patch, prerelease=prerelease
version=version,
major=major,
minor=minor,
patch=patch,
prerelease=prerelease,
postrelease=postrelease,
)


Expand Down
4 changes: 2 additions & 2 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ 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], prefix: str = "") -> list[GitTag]:
valid_tags: list[GitTag] = []
for tag in tags:
try:
scheme(tag.name)
scheme(tag.name.replace(prefix, ''))
except InvalidVersion:
out.warn(f"InvalidVersion {tag}")
else:
Expand Down
10 changes: 10 additions & 0 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ def __call__(
"action": "store_true",
"help": "bump only the local version portion",
},
{
"name": "--build-metadata",
"help": "set the build metadata portion of the version",
},
{
"name": ["--changelog", "-ch"],
"action": "store_true",
Expand Down Expand Up @@ -219,6 +223,12 @@ def __call__(
"help": "choose type of prerelease",
"choices": ["alpha", "beta", "rc"],
},
{
"name": ["--postrelease"],
"action": "store_true",
"default": False,
"help": "mark as a post release",
},
{
"name": ["--devrelease", "-d"],
"help": "specify non-negative integer for dev. release",
Expand Down
15 changes: 14 additions & 1 deletion commitizen/commands/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
for key in [
"tag_format",
"prerelease",
"postrelease",
"increment",
"bump_message",
"gpg_sign",
Expand Down Expand Up @@ -147,9 +148,11 @@ def __call__(self): # noqa: C901
is_yes: bool = self.arguments["yes"]
increment: str | None = self.arguments["increment"]
prerelease: str | None = self.arguments["prerelease"]
postrelease: bool = self.arguments["postrelease"]
devrelease: int | None = self.arguments["devrelease"]
is_files_only: bool | None = self.arguments["files_only"]
is_local_version: bool | None = self.arguments["local_version"]
build_metadata: str | None = self.arguments["build_metadata"]
manual_version = self.arguments["manual_version"]

if manual_version:
Expand All @@ -159,6 +162,9 @@ def __call__(self): # noqa: C901
if prerelease:
raise NotAllowed("--prerelease cannot be combined with MANUAL_VERSION")

if postrelease:
raise NotAllowed("--postrelease cannot be combined with MANUAL_VERSION")

if devrelease is not None:
raise NotAllowed("--devrelease cannot be combined with MANUAL_VERSION")

Expand All @@ -167,6 +173,11 @@ def __call__(self): # noqa: C901
"--local-version cannot be combined with MANUAL_VERSION"
)

if build_metadata:
raise NotAllowed(
"--build_metadata cannot be combined with MANUAL_VERSION"
)

if major_version_zero:
raise NotAllowed(
"--major-version-zero cannot be combined with MANUAL_VERSION"
Expand Down Expand Up @@ -228,15 +239,17 @@ def __call__(self): # noqa: C901

# Increment is removed when current and next version
# are expected to be prereleases.
if prerelease and current_version.is_prerelease:
if prerelease and current_version.is_prerelease or postrelease:
increment = None

new_version = current_version.bump(
increment,
prerelease=prerelease,
postrelease=postrelease,
prerelease_offset=prerelease_offset,
devrelease=devrelease,
is_local_version=is_local_version,
build_metadata=build_metadata,
)

new_tag_version = bump.normalize_tag(
Expand Down
5 changes: 4 additions & 1 deletion commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def __init__(self, config: BaseConfig, args):
self.tag_format: str = (
args.get("tag_format") or self.config.settings["tag_format"]
)
self.tag_prefix: str = args.get("tag_prefix") or self.config.settings.get(
"tag_prefix"
)
self.merge_prerelease = args.get(
"merge_prerelease"
) or self.config.settings.get("changelog_merge_prerelease")
Expand Down Expand Up @@ -158,7 +161,7 @@ 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_prefix), self.tag_prefix) or []

end_rev = ""
if self.incremental:
Expand Down
2 changes: 2 additions & 0 deletions commitizen/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Settings(TypedDict, total=False):
version_scheme: str | None
version_type: str | None
tag_format: str
tag_prefix: str | None
bump_message: str | None
allow_abort: bool
allowed_prefixes: list[str]
Expand Down Expand Up @@ -76,6 +77,7 @@ class Settings(TypedDict, total=False):
"version_provider": "commitizen",
"version_scheme": None,
"tag_format": "$version", # example v$version
"tag_prefix": "",
"bump_message": None, # bumped v$current_version to $new_version
"allow_abort": False,
"allowed_prefixes": [
Expand Down
5 changes: 4 additions & 1 deletion commitizen/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def get_filenames_in_commit(git_reference: str = ""):
raise GitCommandError(c.err)


def get_tags(dateformat: str = "%Y-%m-%d") -> list[GitTag]:
def get_tags(prefix: str, dateformat: str = "%Y-%m-%d") -> list[GitTag]:
inner_delimiter = "---inner_delimiter---"
formatter = (
f'"%(refname:lstrip=2){inner_delimiter}'
Expand All @@ -181,6 +181,9 @@ def get_tags(dateformat: str = "%Y-%m-%d") -> list[GitTag]:
for line in c.out.split("\n")[:-1]
]

if prefix:
git_tags = [tag for tag in git_tags if tag.name.startswith(prefix)]

return git_tags


Expand Down
2 changes: 2 additions & 0 deletions commitizen/providers/scm_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ScmProvider(VersionProvider):
"$minor": r"(?P<minor>\d+)",
"$patch": r"(?P<patch>\d+)",
"$prerelease": r"(?P<prerelease>\w+\d+)?",
"$postrelease": r"(?P<postrelease>\.post\d+)?",
"$devrelease": r"(?P<devrelease>\.dev\d+)?",
}

Expand Down Expand Up @@ -52,6 +53,7 @@ def matcher(tag: str) -> str | None:
f".{groups['minor']}" if groups.get("minor") else "",
f".{groups['patch']}" if groups.get("patch") else "",
groups["prerelease"] if groups.get("prerelease") else "",
groups["postrelease"] if groups.get("postrelease") else "",
groups["devrelease"] if groups.get("devrelease") else "",
)
)
Expand Down
58 changes: 53 additions & 5 deletions commitizen/version_schemes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import sys
import warnings
from itertools import zip_longest
from typing import TYPE_CHECKING, ClassVar, Protocol, Type, cast, runtime_checkable
from typing import (TYPE_CHECKING, ClassVar, Protocol, Type, cast,
runtime_checkable)

import importlib_metadata as metadata
from packaging.version import InvalidVersion # noqa: F401: Rexpose the common exception
from packaging.version import \
InvalidVersion # noqa: F401: Rexpose the common exception
from packaging.version import Version as _BaseVersion

from commitizen.config.base_config import BaseConfig
Expand All @@ -28,7 +30,7 @@
from typing import Self


DEFAULT_VERSION_PARSER = r"v?(?P<version>([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?(\w+)?)"
DEFAULT_VERSION_PARSER = r"(?P<version>([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?(\w+)?)"


@runtime_checkable
Expand Down Expand Up @@ -68,6 +70,16 @@ def prerelease(self) -> str | None:
"""The prelease potion of the version is this is a prerelease."""
raise NotImplementedError("must be implemented")

@property
def is_postrelease(self) -> bool:
"""Whether this version is a post-release."""
raise NotImplementedError("must be implemented")

@property
def postrelease(self) -> str | None:
"""The postrelease potion of the version is this is a postrelease."""
raise NotImplementedError("must be implemented")

@property
def public(self) -> str:
"""The public portion of the version."""
Expand Down Expand Up @@ -98,8 +110,10 @@ def bump(
increment: str,
prerelease: str | None = None,
prerelease_offset: int = 0,
postrelease: bool = False,
devrelease: int | None = None,
is_local_version: bool = False,
build_metadata: str | None = None,
) -> Self:
"""
Based on the given increment, generate the next bumped version according to the version scheme
Expand Down Expand Up @@ -130,6 +144,13 @@ def prerelease(self) -> str | None:
return f"{self.pre[0]}{self.pre[1]}"
return None

@property
def postrelease(self) -> str | None:
# version.post is needed for mypy check
if self.is_postrelease and self.post:
return f"post{self.post}"
return None

def generate_prerelease(
self, prerelease: str | None = None, offset: int = 0
) -> str:
Expand All @@ -155,6 +176,19 @@ def generate_prerelease(
pre_version = f"{prerelease}{new_prerelease_number}"
return pre_version

def generate_postrelease(self, postrelease: bool = False) -> str:
"""Generate postrelease"""
if not postrelease:
return ""

# version.post is needed for mypy check
if self.is_postrelease and self.post is not None:
new_postrelease_number = self.post + 1
else:
new_postrelease_number = 0
post_version = f"post{new_postrelease_number}"
return post_version

def generate_devrelease(self, devrelease: int | None) -> str:
"""Generate devrelease

Expand All @@ -166,6 +200,17 @@ def generate_devrelease(self, devrelease: int | None) -> str:

return f"dev{devrelease}"

def generate_build_metadata(self, build_metadata: str | None) -> str:
"""Generate build metadata

The build metadata should be passed directly and is not
inferred based on previous versions.
"""
if build_metadata is None:
return ""

return f"+{build_metadata}"

def increment_base(self, increment: str | None = None) -> str:
prev_release = list(self.release)
increments = [MAJOR, MINOR, PATCH]
Expand Down Expand Up @@ -193,8 +238,10 @@ def bump(
increment: str,
prerelease: str | None = None,
prerelease_offset: int = 0,
postrelease: bool = False,
devrelease: int | None = None,
is_local_version: bool = False,
build_metadata: str | None = None,
) -> Self:
"""Based on the given increment a proper semver will be generated.

Expand All @@ -215,8 +262,9 @@ def bump(
base = self.increment_base(increment)
dev_version = self.generate_devrelease(devrelease)
pre_version = self.generate_prerelease(prerelease, offset=prerelease_offset)
# TODO: post version
return self.scheme(f"{base}{pre_version}{dev_version}") # type: ignore
post_version = self.generate_postrelease(postrelease)
build_metadata = self.generate_build_metadata(build_metadata)
return self.scheme(f"{base}{pre_version}{post_version}{dev_version}{build_metadata}") # type: ignore


class Pep440(BaseVersion):
Expand Down
4 changes: 2 additions & 2 deletions docs/bump.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ Some examples of pep440:

```bash
$ cz bump --help
usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--changelog]
[--no-verify] [--yes] [--tag-format TAG_FORMAT]
usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--build-metadata BUILD_METADATA]
[--changelog] [--no-verify] [--yes] [--tag-format TAG_FORMAT]
[--bump-message BUMP_MESSAGE] [--prerelease {alpha,beta,rc}]
[--devrelease DEVRELEASE] [--increment {MAJOR,MINOR,PATCH}]
[--check-consistency] [--annotated-tag] [--gpg-sign]
Expand Down
22 changes: 22 additions & 0 deletions tests/commands/test_bump_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,28 @@ def test_bump_local_version(mocker: MockFixture, tmp_commitizen_project):
assert "4.5.1+0.2.0" in f.read()


def test_bump_build_metadata_version(mocker: MockFixture, tmp_commitizen_project):
tmp_version_file = tmp_commitizen_project.join("__version__.py")
tmp_version_file.write("4.5.1")
tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml")
tmp_version_file_string = str(tmp_version_file).replace("\\", "/")
tmp_commitizen_cfg_file.write(
f"[tool.commitizen]\n"
'version="4.5.1"\n'
f'version_files = ["{tmp_version_file_string}"]'
)

create_file_and_commit("feat: new user interface")
testargs = ["cz", "bump", "--yes", "--build-metadata", "metadata"]
mocker.patch.object(sys, "argv", testargs)
cli.main()
tag_exists = git.tag_exist("4.6.0+metadata")
assert tag_exists is True

with open(tmp_version_file, encoding="utf-8") as f:
assert "4.6.0+metadata" in f.read()


@pytest.mark.usefixtures("tmp_commitizen_project")
def test_bump_dry_run(mocker: MockFixture, capsys):
create_file_and_commit("feat: new file")
Expand Down