Skip to content

Commit

Permalink
Merge branch 'master' into issue-945
Browse files Browse the repository at this point in the history
  • Loading branch information
xeor authored Feb 15, 2024
2 parents 1da13bd + 3e51e88 commit 46ec85b
Show file tree
Hide file tree
Showing 27 changed files with 682 additions and 143 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
shell: bash
- name: Upload coverage to Codecov
if: runner.os == 'Linux'
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{secrets.CODECOV_TOKEN}}
file: ./coverage.xml
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ repos:
exclude: "poetry.lock"

- repo: https://github.com/commitizen-tools/commitizen
rev: v3.13.0 # automatically updated by Commitizen
rev: v3.14.1 # automatically updated by Commitizen
hooks:
- id: commitizen
- id: commitizen-branch
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@

## v3.14.1 (2024-02-04)

### Fix

- **bump**: remove unused method
- **scm**: only search tags that are reachable by the current commit

## v3.14.0 (2024-02-01)

### Feat

- properly bump versions between prereleases (#799)

## v3.13.0 (2023-12-03)

### Feat
Expand Down
2 changes: 1 addition & 1 deletion commitizen/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.13.0"
__version__ = "3.14.1"
1 change: 1 addition & 0 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- [x] hook after changelog is generated (api calls)
- [x] add support for change_type maps
"""

from __future__ import annotations

import re
Expand Down
10 changes: 4 additions & 6 deletions commitizen/commands/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
)
from commitizen.changelog_formats import get_changelog_format
from commitizen.providers import get_provider
from commitizen.version_schemes import InvalidVersion, get_version_scheme
from commitizen.version_schemes import (
get_version_scheme,
InvalidVersion,
)

logger = getLogger("commitizen")

Expand Down Expand Up @@ -238,11 +241,6 @@ def __call__(self): # noqa: C901
"To avoid this error, manually specify the type of increment with `--increment`"
)

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

new_version = current_version.bump(
increment,
prerelease=prerelease,
Expand Down
6 changes: 3 additions & 3 deletions commitizen/cz/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ class BaseCommitizen(metaclass=ABCMeta):
change_type_order: list[str] | None = None

# Executed per message parsed by the commitizen
changelog_message_builder_hook: None | (
Callable[[dict, git.GitCommit], dict]
) = None
changelog_message_builder_hook: None | (Callable[[dict, git.GitCommit], dict]) = (
None
)

# Executed only at the end of the changelog generation
changelog_hook: Callable[[str, str | None], str] | None = None
Expand Down
6 changes: 2 additions & 4 deletions commitizen/cz/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
class CzException(Exception):
...
class CzException(Exception): ...


class AnswerRequiredError(CzException):
...
class AnswerRequiredError(CzException): ...
10 changes: 8 additions & 2 deletions commitizen/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,22 @@ 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(
dateformat: str = "%Y-%m-%d", reachable_only: bool = False
) -> list[GitTag]:
inner_delimiter = "---inner_delimiter---"
formatter = (
f'"%(refname:lstrip=2){inner_delimiter}'
f"%(objectname){inner_delimiter}"
f"%(creatordate:format:{dateformat}){inner_delimiter}"
f'%(object)"'
)
c = cmd.run(f"git tag --format={formatter} --sort=-creatordate")
extra = "--merged" if reachable_only else ""
c = cmd.run(f"git tag --format={formatter} --sort=-creatordate {extra}")
if c.return_code != 0:
if reachable_only and c.err == "fatal: malformed object name HEAD\n":
# this can happen if there are no commits in the repo yet
return []
raise GitCommandError(c.err)

if c.err:
Expand Down
36 changes: 26 additions & 10 deletions commitizen/providers/scm_provider.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from __future__ import annotations

import re
from typing import Callable, cast
from typing import Callable


from commitizen.git import get_tags
from commitizen.version_schemes import get_version_scheme
from commitizen.version_schemes import (
get_version_scheme,
InvalidVersion,
Version,
VersionProtocol,
)

from commitizen.providers.base_provider import VersionProvider

Expand All @@ -28,7 +33,7 @@ class ScmProvider(VersionProvider):
"$devrelease": r"(?P<devrelease>\.dev\d+)?",
}

def _tag_format_matcher(self) -> Callable[[str], str | None]:
def _tag_format_matcher(self) -> Callable[[str], VersionProtocol | None]:
version_scheme = get_version_scheme(self.config)
pattern = self.config.settings["tag_format"]
if pattern == "$version":
Expand All @@ -38,15 +43,15 @@ def _tag_format_matcher(self) -> Callable[[str], str | None]:

regex = re.compile(f"^{pattern}$", re.VERBOSE)

def matcher(tag: str) -> str | None:
def matcher(tag: str) -> Version | None:
match = regex.match(tag)
if not match:
return None
groups = match.groupdict()
if "version" in groups:
return groups["version"]
ver = groups["version"]
elif "major" in groups:
return "".join(
ver = "".join(
(
groups["major"],
f".{groups['minor']}" if groups.get("minor") else "",
Expand All @@ -56,16 +61,27 @@ def matcher(tag: str) -> str | None:
)
)
elif pattern == version_scheme.parser.pattern:
return str(version_scheme(tag))
return None
ver = tag
else:
return None

try:
return version_scheme(ver)
except InvalidVersion:
return None

return matcher

def get_version(self) -> str:
matcher = self._tag_format_matcher()
return next(
(cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0"
matches = sorted(
version
for t in get_tags(reachable_only=True)
if (version := matcher(t.name))
)
if not matches:
return "0.0.0"
return str(matches[-1])

def set_version(self, version: str):
# Not necessary
Expand Down
80 changes: 63 additions & 17 deletions commitizen/version_schemes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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, Any, ClassVar, Protocol, Type, cast, runtime_checkable

import importlib_metadata as metadata
from packaging.version import InvalidVersion # noqa: F401: Rexpose the common exception
Expand Down Expand Up @@ -93,13 +93,32 @@ def micro(self) -> int:
"""The third item of :attr:`release` or ``0`` if unavailable."""
raise NotImplementedError("must be implemented")

def __lt__(self, other: Any) -> bool:
raise NotImplementedError("must be implemented")

def __le__(self, other: Any) -> bool:
raise NotImplementedError("must be implemented")

def __eq__(self, other: object) -> bool:
raise NotImplementedError("must be implemented")

def __ge__(self, other: Any) -> bool:
raise NotImplementedError("must be implemented")

def __gt__(self, other: Any) -> bool:
raise NotImplementedError("must be implemented")

def __ne__(self, other: object) -> bool:
raise NotImplementedError("must be implemented")

def bump(
self,
increment: str,
prerelease: str | None = None,
prerelease_offset: int = 0,
devrelease: int | None = None,
is_local_version: bool = False,
force_bump: bool = False,
) -> Self:
"""
Based on the given increment, generate the next bumped version according to the version scheme
Expand Down Expand Up @@ -146,6 +165,12 @@ def generate_prerelease(
if not prerelease:
return ""

# prevent down-bumping the pre-release phase, e.g. from 'b1' to 'a2'
# https://packaging.python.org/en/latest/specifications/version-specifiers/#pre-releases
# https://semver.org/#spec-item-11
if self.is_prerelease and self.pre:
prerelease = max(prerelease, self.pre[0])

# version.pre is needed for mypy check
if self.is_prerelease and self.pre and prerelease.startswith(self.pre[0]):
prev_prerelease: int = self.pre[1]
Expand Down Expand Up @@ -182,20 +207,15 @@ def increment_base(self, increment: str | None = None) -> str:
increments = [MAJOR, MINOR, PATCH]
base = dict(zip_longest(increments, prev_release, fillvalue=0))

# This flag means that current version
# must remove its prerelease tag,
# so it doesn't matter the increment.
# Example: 1.0.0a0 with PATCH/MINOR -> 1.0.0
if not self.is_prerelease:
if increment == MAJOR:
base[MAJOR] += 1
base[MINOR] = 0
base[PATCH] = 0
elif increment == MINOR:
base[MINOR] += 1
base[PATCH] = 0
elif increment == PATCH:
base[PATCH] += 1
if increment == MAJOR:
base[MAJOR] += 1
base[MINOR] = 0
base[PATCH] = 0
elif increment == MINOR:
base[MINOR] += 1
base[PATCH] = 0
elif increment == PATCH:
base[PATCH] += 1

return f"{base[MAJOR]}.{base[MINOR]}.{base[PATCH]}"

Expand All @@ -207,6 +227,7 @@ def bump(
devrelease: int | None = None,
is_local_version: bool = False,
build_metadata: str | None = None,
force_bump: bool = False,
) -> Self:
"""Based on the given increment a proper semver will be generated.
Expand All @@ -224,10 +245,35 @@ def bump(
local_version = self.scheme(self.local).bump(increment)
return self.scheme(f"{self.public}+{local_version}") # type: ignore
else:
base = self.increment_base(increment)
if not self.is_prerelease:
base = self.increment_base(increment)
elif force_bump:
base = self.increment_base(increment)
else:
base = f"{self.major}.{self.minor}.{self.micro}"
if increment == PATCH:
pass
elif increment == MINOR:
if self.micro != 0:
base = self.increment_base(increment)
elif increment == MAJOR:
if self.minor != 0 or self.micro != 0:
base = self.increment_base(increment)
dev_version = self.generate_devrelease(devrelease)
pre_version = self.generate_prerelease(prerelease, offset=prerelease_offset)
build_metadata = self.generate_build_metadata(build_metadata)
release = list(self.release)
if len(release) < 3:
release += [0] * (3 - len(release))
current_base = ".".join(str(part) for part in release)
if base == current_base:
pre_version = self.generate_prerelease(
prerelease, offset=prerelease_offset
)
else:
base_version = cast(BaseVersion, self.scheme(base))
pre_version = base_version.generate_prerelease(
prerelease, offset=prerelease_offset
)
# TODO: post version
return self.scheme(f"{base}{pre_version}{dev_version}{build_metadata}") # type: ignore

Expand Down
30 changes: 29 additions & 1 deletion docs/bump.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,34 @@ Generate a **changelog** along with the new version and tag when bumping.
cz bump --changelog
```
### `--prerelease`
The bump is a pre-release bump, meaning that in addition to a possible version bump the new version receives a
pre-release segment compatible with the bump’s version scheme, where the segment consist of a _phase_ and a
non-negative number. Supported options for `--prerelease` are the following phase names `alpha`, `beta`, or
`rc` (release candidate). For more details, refer to the
[Python Packaging User Guide](https://packaging.python.org/en/latest/specifications/version-specifiers/#pre-releases).
Note that as per [semantic versioning spec](https://semver.org/#spec-item-9)
> Pre-release versions have a lower precedence than the associated normal version. A pre-release version
> indicates that the version is unstable and might not satisfy the intended compatibility requirements
> as denoted by its associated normal version.
For example, the following versions (using the [PEP 440](https://peps.python.org/pep-0440/) scheme) are ordered
by their precedence and showcase how a release might flow through a development cycle:
- `1.0.0` is the current published version
- `1.0.1a0` after committing a `fix:` for pre-release
- `1.1.0a1` after committing an additional `feat:` for pre-release
- `1.1.0b0` after bumping a beta release
- `1.1.0rc0` after bumping the release candidate
- `1.1.0` next feature release
Also note that bumping pre-releases _maintains linearity_: bumping of a pre-release with lower precedence than
the current pre-release phase maintains the current phase of higher precedence. For example, if the current
version is `1.0.0b1` then bumping with `--prerelease alpha` will continue to bump the “beta” phase.
### `--check-consistency`
Check whether the versions defined in `version_files` and the version in commitizen
Expand Down Expand Up @@ -570,7 +598,7 @@ Choose version scheme
| devrelease | `0.1.1.dev1` | `0.1.1-dev1` |
| dev and pre | `1.0.0a3.dev1` | `1.0.0-a3-dev1` |
Options: `sever`, `pep440`
Options: `semver`, `pep440`
Defaults to: `pep440`
Expand Down
18 changes: 18 additions & 0 deletions docs/third-party-commitizen.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ pip install cz-emoji
cz --name cz_emoji commit
```

### [cz-conventional-gitmoji](https://github.com/ljnsn/cz-conventional-gitmoji)

*conventional commit*s, but with [gitmojis](https://gitmoji.dev).

Includes a pre-commit hook that automatically adds the correct gitmoji to the commit message based on the conventional type.

### Installation

```bash
pip install cz-conventional-gitmoji
```

### Usage

```bash
cz --name cz_gitmoji commit
```


### [Commitizen emoji](https://pypi.org/project/commitizen-emoji/) (Unmaintained)

Expand Down
Loading

0 comments on commit 46ec85b

Please sign in to comment.