Skip to content

Commit 34fb773

Browse files
committed
feat(version): add MANUAL_VERSION, --next and --patch to version command, remove type alias
1 parent 2072f8e commit 34fb773

File tree

11 files changed

+220
-71
lines changed

11 files changed

+220
-71
lines changed

commitizen/bump.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
if TYPE_CHECKING:
1616
from collections.abc import Generator, Iterable
1717

18-
from commitizen.version_schemes import Increment, Version
18+
from commitizen.version_schemes import Increment, VersionProtocol
1919

2020
VERSION_TYPES = [None, PATCH, MINOR, MAJOR]
2121

@@ -131,8 +131,8 @@ def _resolve_files_and_regexes(
131131

132132

133133
def create_commit_message(
134-
current_version: Version | str,
135-
new_version: Version | str,
134+
current_version: VersionProtocol | str,
135+
new_version: VersionProtocol | str,
136136
message_template: str | None = None,
137137
) -> str:
138138
if message_template is None:

commitizen/cli.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
InvalidCommandArgumentError,
2121
NoCommandFoundError,
2222
)
23+
from commitizen.version_increment import VersionIncrement
2324

2425
logger = logging.getLogger(__name__)
2526

@@ -543,16 +544,36 @@ def __call__(
543544
},
544545
{
545546
"name": ["--major"],
546-
"help": "get just the major version. Need to be used with --project or --verbose.",
547+
"help": "Output the major version only. Need to be used with MANUAL_VERSION, --project or --verbose.",
547548
"action": "store_true",
548549
"exclusive_group": "group2",
549550
},
550551
{
551552
"name": ["--minor"],
552-
"help": "get just the minor version. Need to be used with --project or --verbose.",
553+
"help": "Output the minor version only. Need to be used with MANUAL_VERSION, --project or --verbose.",
553554
"action": "store_true",
554555
"exclusive_group": "group2",
555556
},
557+
{
558+
"name": ["--patch"],
559+
"help": "Output the patch version only. Need to be used with MANUAL_VERSION, --project or --verbose.",
560+
"action": "store_true",
561+
"exclusive_group": "group2",
562+
},
563+
{
564+
"name": ["--next"],
565+
"help": "Output the next version.",
566+
"type": str,
567+
"choices": [str(increment) for increment in VersionIncrement],
568+
"exclusive_group": "group2",
569+
},
570+
{
571+
"name": "manual_version",
572+
"type": str,
573+
"nargs": "?",
574+
"help": "Use the version provided instead of the version from the project. Can be used to test the selected version scheme",
575+
"metavar": "MANUAL_VERSION",
576+
},
556577
],
557578
},
558579
],

commitizen/commands/init.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
from commitizen.defaults import CONFIG_FILES, DEFAULT_SETTINGS
1414
from commitizen.exceptions import InitFailedError, NoAnswersError
1515
from commitizen.git import get_latest_tag_name, get_tag_names, smart_open
16-
from commitizen.version_schemes import KNOWN_SCHEMES, Version, get_version_scheme
16+
from commitizen.version_schemes import (
17+
KNOWN_SCHEMES,
18+
VersionProtocol,
19+
get_version_scheme,
20+
)
1721

1822
if TYPE_CHECKING:
1923
from commitizen.config import (
@@ -238,7 +242,7 @@ def _ask_version_scheme(self) -> str:
238242
).unsafe_ask()
239243
return scheme
240244

241-
def _ask_major_version_zero(self, version: Version) -> bool:
245+
def _ask_major_version_zero(self, version: VersionProtocol) -> bool:
242246
"""Ask for setting: major_version_zero"""
243247
if version.major > 0:
244248
return False
@@ -295,7 +299,7 @@ def _write_config_to_file(
295299
cz_name: str,
296300
version_provider: str,
297301
version_scheme: str,
298-
version: Version,
302+
version: VersionProtocol,
299303
tag_format: str,
300304
update_changelog_on_bump: bool,
301305
major_version_zero: bool,

commitizen/commands/version.py

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,31 @@
22
import sys
33
from typing import TypedDict
44

5+
from packaging.version import InvalidVersion
6+
57
from commitizen import out
68
from commitizen.__version__ import __version__
79
from commitizen.config import BaseConfig
810
from commitizen.exceptions import NoVersionSpecifiedError, VersionSchemeUnknown
911
from commitizen.providers import get_provider
12+
from commitizen.version_increment import VersionIncrement
1013
from commitizen.version_schemes import get_version_scheme
1114

1215

1316
class VersionArgs(TypedDict, total=False):
17+
manual_version: str | None
18+
next: str | None
19+
20+
# Exclusive groups 1
1421
commitizen: bool
1522
report: bool
1623
project: bool
1724
verbose: bool
25+
26+
# Exclusive groups 2
1827
major: bool
1928
minor: bool
29+
patch: bool
2030

2131

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

44-
if not self.arguments.get("commitizen") and (
45-
self.arguments.get("project") or self.arguments.get("verbose")
54+
if self.arguments.get("commitizen"):
55+
out.write(__version__)
56+
return
57+
58+
if (
59+
self.arguments.get("project")
60+
or self.arguments.get("verbose")
61+
or self.arguments.get("next")
62+
or self.arguments.get("manual_version")
4663
):
64+
version_str = self.arguments.get("manual_version")
65+
if version_str is None:
66+
try:
67+
version_str = get_provider(self.config).get_version()
68+
except NoVersionSpecifiedError:
69+
out.error("No project information in this project.")
70+
return
4771
try:
48-
version = get_provider(self.config).get_version()
49-
except NoVersionSpecifiedError:
50-
out.error("No project information in this project.")
51-
return
52-
try:
53-
version_scheme = get_version_scheme(self.config.settings)(version)
72+
version_scheme = get_version_scheme(self.config.settings)
5473
except VersionSchemeUnknown:
5574
out.error("Unknown version scheme.")
5675
return
5776

77+
try:
78+
version = version_scheme(version_str)
79+
except InvalidVersion:
80+
out.error("Invalid version.")
81+
return
82+
83+
if next_increment_str := self.arguments.get("next"):
84+
next_increment = VersionIncrement.safe_cast(next_increment_str)
85+
# TODO: modify the interface of bump to accept VersionIncrement
86+
version = version.bump(increment=str(next_increment)) # type: ignore[arg-type]
87+
5888
if self.arguments.get("major"):
59-
version = f"{version_scheme.major}"
60-
elif self.arguments.get("minor"):
61-
version = f"{version_scheme.minor}"
89+
out.write(version.major)
90+
return
91+
if self.arguments.get("minor"):
92+
out.write(version.minor)
93+
return
94+
if self.arguments.get("patch"):
95+
out.write(version.micro)
96+
return
6297

6398
out.write(
6499
f"Project Version: {version}"
@@ -67,11 +102,12 @@ def __call__(self) -> None:
67102
)
68103
return
69104

70-
if self.arguments.get("major") or self.arguments.get("minor"):
71-
out.error(
72-
"Major or minor version can only be used with --project or --verbose."
73-
)
74-
return
105+
for argument in ("major", "minor", "patch"):
106+
if self.arguments.get(argument):
107+
out.error(
108+
f"{argument} can only be used with MANUAL_VERSION, --project or --verbose."
109+
)
110+
return
75111

76112
# If no arguments are provided, just show the installed commitizen version
77113
out.write(__version__)

commitizen/out.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,35 @@
99
sys.stdout.reconfigure(encoding="utf-8")
1010

1111

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

1616

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

2121

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

2626

27-
def success(value: str) -> None:
28-
message = colored(value, "green")
27+
def success(value: object) -> None:
28+
message = colored(str(value), "green")
2929
line(message)
3030

3131

32-
def info(value: str) -> None:
33-
message = colored(value, "blue")
32+
def info(value: object) -> None:
33+
message = colored(str(value), "blue")
3434
line(message)
3535

3636

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

4040

41-
def warn(value: str) -> None:
42-
message = colored(value, "magenta")
41+
def warn(value: object) -> None:
42+
message = colored(str(value), "magenta")
4343
line(message, file=sys.stderr)

commitizen/tags.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,14 @@
1414
from commitizen.version_schemes import (
1515
DEFAULT_SCHEME,
1616
InvalidVersion,
17-
Version,
18-
VersionScheme,
17+
VersionProtocol,
1918
get_version_scheme,
2019
)
2120

2221
if TYPE_CHECKING:
2322
import sys
2423
from collections.abc import Iterable, Sequence
2524

26-
from commitizen.version_schemes import VersionScheme
27-
2825
# Self is Python 3.11+ but backported in typing-extensions
2926
if sys.version_info < (3, 11):
3027
from typing_extensions import Self
@@ -75,15 +72,15 @@ class TagRules:
7572
assert not rules.is_version_tag("warn1.0.0", warn=True) # Does warn
7673
7774
assert rules.search_version("# My v1.0.0 version").version == "1.0.0"
78-
assert rules.extract_version("v1.0.0") == Version("1.0.0")
75+
assert rules.extract_version("v1.0.0") == VersionProtocol("1.0.0")
7976
try:
8077
assert rules.extract_version("not-a-v1.0.0")
8178
except InvalidVersion:
8279
print("Does not match a tag format")
8380
```
8481
"""
8582

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

148-
def extract_version(self, tag: GitTag) -> Version:
145+
def extract_version(self, tag: GitTag) -> VersionProtocol:
149146
"""
150147
Extract a version from the tag as defined in tag formats.
151148
@@ -211,7 +208,7 @@ def search_version(self, text: str, last: bool = False) -> VersionTag | None:
211208
return VersionTag(version, match.group(0))
212209

213210
def normalize_tag(
214-
self, version: Version | str, tag_format: str | None = None
211+
self, version: VersionProtocol | str, tag_format: str | None = None
215212
) -> str:
216213
"""
217214
The tag and the software version might be different.
@@ -241,7 +238,7 @@ def normalize_tag(
241238
)
242239

243240
def find_tag_for(
244-
self, tags: Iterable[GitTag], version: Version | str
241+
self, tags: Iterable[GitTag], version: VersionProtocol | str
245242
) -> GitTag | None:
246243
"""Find the first matching tag for a given version."""
247244
version = self.scheme(version) if isinstance(version, str) else version

commitizen/version_increment.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from enum import IntEnum
2+
3+
4+
class VersionIncrement(IntEnum):
5+
"""An enumeration representing semantic versioning increments.
6+
This class defines the four types of version increments according to semantic versioning:
7+
- NONE: For commits that don't require a version bump (docs, style, etc.)
8+
- PATCH: For backwards-compatible bug fixes
9+
- MINOR: For backwards-compatible functionality additions
10+
- MAJOR: For incompatible API changes
11+
"""
12+
13+
NONE = 0
14+
PATCH = 1
15+
MINOR = 2
16+
MAJOR = 3
17+
18+
def __str__(self) -> str:
19+
return self.name
20+
21+
@classmethod
22+
def safe_cast(cls, value: object) -> "VersionIncrement":
23+
if not isinstance(value, str):
24+
return VersionIncrement.NONE
25+
try:
26+
return cls[value]
27+
except KeyError:
28+
return VersionIncrement.NONE

0 commit comments

Comments
 (0)