Skip to content
Open
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
6 changes: 3 additions & 3 deletions commitizen/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
if TYPE_CHECKING:
from collections.abc import Generator, Iterable

from commitizen.version_schemes import Increment, Version
from commitizen.version_schemes import Increment, VersionProtocol

VERSION_TYPES = [None, PATCH, MINOR, MAJOR]

Expand Down Expand Up @@ -131,8 +131,8 @@ def _resolve_files_and_regexes(


def create_commit_message(
current_version: Version | str,
new_version: Version | str,
current_version: VersionProtocol | str,
new_version: VersionProtocol | str,
message_template: str | None = None,
) -> str:
if message_template is None:
Expand Down
25 changes: 23 additions & 2 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
InvalidCommandArgumentError,
NoCommandFoundError,
)
from commitizen.version_increment import VersionIncrement

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -543,16 +544,36 @@ def __call__(
},
{
"name": ["--major"],
"help": "get just the major version. Need to be used with --project or --verbose.",
"help": "Output the major version only. Need to be used with MANUAL_VERSION, --project or --verbose.",
"action": "store_true",
"exclusive_group": "group2",
},
{
"name": ["--minor"],
"help": "get just the minor version. Need to be used with --project or --verbose.",
"help": "Output the minor version only. Need to be used with MANUAL_VERSION, --project or --verbose.",
"action": "store_true",
"exclusive_group": "group2",
},
{
"name": ["--patch"],
"help": "Output the patch version only. Need to be used with MANUAL_VERSION, --project or --verbose.",
"action": "store_true",
"exclusive_group": "group2",
},
{
"name": ["--next"],
"help": "Output the next version.",
"type": str,
"choices": [str(increment) for increment in VersionIncrement],
"exclusive_group": "group2",
},
{
"name": "manual_version",
"type": str,
"nargs": "?",
"help": "Use the version provided instead of the version from the project. Can be used to test the selected version scheme",
"metavar": "MANUAL_VERSION",
},
],
},
],
Expand Down
10 changes: 7 additions & 3 deletions commitizen/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
from commitizen.defaults import CONFIG_FILES, DEFAULT_SETTINGS
from commitizen.exceptions import InitFailedError, NoAnswersError
from commitizen.git import get_latest_tag_name, get_tag_names, smart_open
from commitizen.version_schemes import KNOWN_SCHEMES, Version, get_version_scheme
from commitizen.version_schemes import (
KNOWN_SCHEMES,
VersionProtocol,
get_version_scheme,
)

if TYPE_CHECKING:
from commitizen.config import (
Expand Down Expand Up @@ -238,7 +242,7 @@ def _ask_version_scheme(self) -> str:
).unsafe_ask()
return scheme

def _ask_major_version_zero(self, version: Version) -> bool:
def _ask_major_version_zero(self, version: VersionProtocol) -> bool:
"""Ask for setting: major_version_zero"""
if version.major > 0:
return False
Expand Down Expand Up @@ -295,7 +299,7 @@ def _write_config_to_file(
cz_name: str,
version_provider: str,
version_scheme: str,
version: Version,
version: VersionProtocol,
tag_format: str,
update_changelog_on_bump: bool,
major_version_zero: bool,
Expand Down
68 changes: 52 additions & 16 deletions commitizen/commands/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,31 @@
import sys
from typing import TypedDict

from packaging.version import InvalidVersion

from commitizen import out
from commitizen.__version__ import __version__
from commitizen.config import BaseConfig
from commitizen.exceptions import NoVersionSpecifiedError, VersionSchemeUnknown
from commitizen.providers import get_provider
from commitizen.version_increment import VersionIncrement
from commitizen.version_schemes import get_version_scheme


class VersionArgs(TypedDict, total=False):
manual_version: str | None
next: str | None

# Exclusive groups 1
commitizen: bool
report: bool
project: bool
verbose: bool

# Exclusive groups 2
major: bool
minor: bool
patch: bool


class Version:
Expand All @@ -41,24 +51,49 @@ def __call__(self) -> None:
if self.arguments.get("verbose"):
out.write(f"Installed Commitizen Version: {__version__}")

if not self.arguments.get("commitizen") and (
self.arguments.get("project") or self.arguments.get("verbose")
if self.arguments.get("commitizen"):
out.write(__version__)
return

if (
self.arguments.get("project")
or self.arguments.get("verbose")
or self.arguments.get("next")
or self.arguments.get("manual_version")
):
version_str = self.arguments.get("manual_version")
if version_str is None:
try:
version_str = get_provider(self.config).get_version()
except NoVersionSpecifiedError:
out.error("No project information in this project.")
return
try:
version = get_provider(self.config).get_version()
except NoVersionSpecifiedError:
out.error("No project information in this project.")
return
try:
version_scheme = get_version_scheme(self.config.settings)(version)
version_scheme = get_version_scheme(self.config.settings)
except VersionSchemeUnknown:
out.error("Unknown version scheme.")
return

try:
version = version_scheme(version_str)
except InvalidVersion:
out.error("Invalid version.")
return

if next_str := self.arguments.get("next"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would happen here if I run cz version --project --next? I would expect that to work

next_increment = VersionIncrement.safe_cast(next_str)
# TODO: modify the interface of bump to accept VersionIncrement
version = version.bump(increment=str(next_increment)) # type: ignore[arg-type]

if self.arguments.get("major"):
version = f"{version_scheme.major}"
elif self.arguments.get("minor"):
version = f"{version_scheme.minor}"
out.write(version.major)
return
if self.arguments.get("minor"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize now that this creates problems with the (not)monotonic kind of versions (and possible non-semver). I'm not sure what to do about it.

I think for now it's fine that if you diverge too much from semver in your custom version scheme, then you won't get the full range of features.

out.write(version.minor)
return
if self.arguments.get("patch"):
out.write(version.micro)
return

out.write(
f"Project Version: {version}"
Expand All @@ -67,11 +102,12 @@ def __call__(self) -> None:
)
return

if self.arguments.get("major") or self.arguments.get("minor"):
out.error(
"Major or minor version can only be used with --project or --verbose."
)
return
for argument in ("major", "minor", "patch"):
if self.arguments.get(argument):
out.error(
f"{argument} can only be used with MANUAL_VERSION, --project or --verbose."
)
return

# If no arguments are provided, just show the installed commitizen version
out.write(__version__)
22 changes: 11 additions & 11 deletions commitizen/out.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,35 @@
sys.stdout.reconfigure(encoding="utf-8")


def write(value: str, *args: object) -> None:
def write(value: object, *args: object) -> None:
"""Intended to be used when value is multiline."""
print(value, *args)


def line(value: str, *args: object, **kwargs: Any) -> None:
def line(value: object, *args: object, **kwargs: Any) -> None:
"""Wrapper in case I want to do something different later."""
print(value, *args, **kwargs)


def error(value: str) -> None:
message = colored(value, "red")
def error(value: object) -> None:
message = colored(str(value), "red")
line(message, file=sys.stderr)


def success(value: str) -> None:
message = colored(value, "green")
def success(value: object) -> None:
message = colored(str(value), "green")
line(message)


def info(value: str) -> None:
message = colored(value, "blue")
def info(value: object) -> None:
message = colored(str(value), "blue")
line(message)


def diagnostic(value: str) -> None:
def diagnostic(value: object) -> None:
line(value, file=sys.stderr)


def warn(value: str) -> None:
message = colored(value, "magenta")
def warn(value: object) -> None:
message = colored(str(value), "magenta")
line(message, file=sys.stderr)
15 changes: 6 additions & 9 deletions commitizen/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,14 @@
from commitizen.version_schemes import (
DEFAULT_SCHEME,
InvalidVersion,
Version,
VersionScheme,
VersionProtocol,
get_version_scheme,
)

if TYPE_CHECKING:
import sys
from collections.abc import Iterable, Sequence

from commitizen.version_schemes import VersionScheme

# Self is Python 3.11+ but backported in typing-extensions
if sys.version_info < (3, 11):
from typing_extensions import Self
Expand Down Expand Up @@ -75,15 +72,15 @@ class TagRules:
assert not rules.is_version_tag("warn1.0.0", warn=True) # Does warn

assert rules.search_version("# My v1.0.0 version").version == "1.0.0"
assert rules.extract_version("v1.0.0") == Version("1.0.0")
assert rules.extract_version("v1.0.0") == VersionProtocol("1.0.0")
try:
assert rules.extract_version("not-a-v1.0.0")
except InvalidVersion:
print("Does not match a tag format")
```
"""

scheme: VersionScheme = DEFAULT_SCHEME
scheme: type[VersionProtocol] = DEFAULT_SCHEME
tag_format: str = DEFAULT_SETTINGS["tag_format"]
legacy_tag_formats: Sequence[str] = field(default_factory=list)
ignored_tag_formats: Sequence[str] = field(default_factory=list)
Expand Down Expand Up @@ -145,7 +142,7 @@ def get_version_tags(
"""Filter in version tags and warn on unexpected tags"""
return [tag for tag in tags if self.is_version_tag(tag, warn)]

def extract_version(self, tag: GitTag) -> Version:
def extract_version(self, tag: GitTag) -> VersionProtocol:
"""
Extract a version from the tag as defined in tag formats.

Expand Down Expand Up @@ -211,7 +208,7 @@ def search_version(self, text: str, last: bool = False) -> VersionTag | None:
return VersionTag(version, match.group(0))

def normalize_tag(
self, version: Version | str, tag_format: str | None = None
self, version: VersionProtocol | str, tag_format: str | None = None
) -> str:
"""
The tag and the software version might be different.
Expand Down Expand Up @@ -241,7 +238,7 @@ def normalize_tag(
)

def find_tag_for(
self, tags: Iterable[GitTag], version: Version | str
self, tags: Iterable[GitTag], version: VersionProtocol | str
) -> GitTag | None:
"""Find the first matching tag for a given version."""
version = self.scheme(version) if isinstance(version, str) else version
Expand Down
28 changes: 28 additions & 0 deletions commitizen/version_increment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from enum import IntEnum


class VersionIncrement(IntEnum):
"""An enumeration representing semantic versioning increments.
This class defines the four types of version increments according to semantic versioning:
- NONE: For commits that don't require a version bump (docs, style, etc.)
- PATCH: For backwards-compatible bug fixes
- MINOR: For backwards-compatible functionality additions
- MAJOR: For incompatible API changes
"""

NONE = 0
PATCH = 1
MINOR = 2
MAJOR = 3

def __str__(self) -> str:
return self.name

@classmethod
def safe_cast(cls, value: object) -> "VersionIncrement":
if not isinstance(value, str):
return VersionIncrement.NONE
try:
return cls[value]
except KeyError:
return VersionIncrement.NONE
Loading
Loading