Skip to content

4.8.3 candidate #1457

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

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
Draft
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
24 changes: 11 additions & 13 deletions commitizen/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def update_version_in_files(
"""
# TODO: separate check step and write step
updated = []
for path, regex in files_and_regexs(files, current_version):
for path, regex in _files_and_regexes(files, current_version):
current_version_found, version_file = _bump_with_regex(
path,
current_version,
Expand All @@ -99,17 +99,17 @@ def update_version_in_files(
return updated


def files_and_regexs(patterns: list[str], version: str) -> list[tuple[str, str]]:
def _files_and_regexes(patterns: list[str], version: str) -> list[tuple[str, str]]:
"""
Resolve all distinct files with their regexp from a list of glob patterns with optional regexp
"""
out = []
out: list[tuple[str, str]] = []
for pattern in patterns:
drive, tail = os.path.splitdrive(pattern)
path, _, regex = tail.partition(":")
filepath = drive + path
if not regex:
regex = _version_to_regex(version)
regex = re.escape(version)

for path in iglob(filepath):
out.append((path, regex))
Expand All @@ -128,18 +128,16 @@ def _bump_with_regex(
pattern = re.compile(regex)
with open(version_filepath, encoding=encoding) as f:
for line in f:
if pattern.search(line):
bumped_line = line.replace(current_version, new_version)
if bumped_line != line:
current_version_found = True
lines.append(bumped_line)
else:
if not pattern.search(line):
lines.append(line)
return current_version_found, "".join(lines)
continue

bumped_line = line.replace(current_version, new_version)
if bumped_line != line:
current_version_found = True
lines.append(bumped_line)

def _version_to_regex(version: str) -> str:
return version.replace(".", r"\.").replace("+", r"\+")
return current_version_found, "".join(lines)


def create_commit_message(
Expand Down
2 changes: 1 addition & 1 deletion commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def process_commit_message(
def order_changelog_tree(tree: Iterable, change_type_order: list[str]) -> Iterable:
if len(set(change_type_order)) != len(change_type_order):
raise InvalidConfigurationError(
f"Change types contain duplicates types ({change_type_order})"
f"Change types contain duplicated types ({change_type_order})"
)

sorted_tree = []
Expand Down
8 changes: 2 additions & 6 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,14 +637,10 @@ def main():
extra_args = " ".join(unknown_args[1:])
arguments["extra_cli_args"] = extra_args

if args.config:
conf = config.read_cfg(args.config)
else:
conf = config.read_cfg()

conf = config.read_cfg(args.config)
if args.name:
conf.update({"name": args.name})
elif not args.name and not conf.path:
elif not conf.path:
conf.update({"name": "cz_conventional_commits"})

if args.debug:
Expand Down
34 changes: 15 additions & 19 deletions commitizen/commands/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
"template",
"file_name",
]
if arguments[key] is not None
if arguments.get(key) is not None
},
}
self.cz = factory.committer_factory(self.config)
Expand Down Expand Up @@ -105,19 +105,18 @@ def is_initial_tag(
self, current_tag: git.GitTag | None, is_yes: bool = False
) -> bool:
"""Check if reading the whole git tree up to HEAD is needed."""
is_initial = False
if not current_tag:
if is_yes:
is_initial = True
else:
out.info("No tag matching configuration could not be found.")
out.info(
"Possible causes:\n"
"- version in configuration is not the current version\n"
"- tag_format or legacy_tag_formats is missing, check them using 'git tag --list'\n"
)
is_initial = questionary.confirm("Is this the first tag created?").ask()
return is_initial
if current_tag:
return False
if is_yes:
return True

out.info("No tag matching configuration could be found.")
out.info(
"Possible causes:\n"
"- version in configuration is not the current version\n"
"- tag_format or legacy_tag_formats is missing, check them using 'git tag --list'\n"
)
return bool(questionary.confirm("Is this the first tag created?").ask())

def find_increment(self, commits: list[git.GitCommit]) -> Increment | None:
# Update the bump map to ensure major version doesn't increment.
Expand All @@ -134,10 +133,7 @@ def find_increment(self, commits: list[git.GitCommit]) -> Increment | None:
raise NoPatternMapError(
f"'{self.config.settings['name']}' rule does not support bump"
)
increment = bump.find_increment(
commits, regex=bump_pattern, increments_map=bump_map
)
return increment
return bump.find_increment(commits, regex=bump_pattern, increments_map=bump_map)

def __call__(self) -> None: # noqa: C901
"""Steps executed to bump."""
Expand All @@ -148,7 +144,7 @@ def __call__(self) -> None: # noqa: C901
except TypeError:
raise NoVersionSpecifiedError()

bump_commit_message: str = self.bump_settings["bump_message"]
bump_commit_message: str | None = self.bump_settings["bump_message"]
version_files: list[str] = self.bump_settings["version_files"]
major_version_zero: bool = self.bump_settings["major_version_zero"]
prerelease_offset: int = self.bump_settings["prerelease_offset"]
Expand Down
45 changes: 22 additions & 23 deletions commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import os
import os.path
from collections.abc import Generator
from difflib import SequenceMatcher
from operator import itemgetter
from pathlib import Path
from typing import Callable, cast

from commitizen import changelog, defaults, factory, git, out
from commitizen.changelog_formats import get_changelog_format
Expand All @@ -32,9 +32,10 @@ def __init__(self, config: BaseConfig, args):
if not git.is_git_project():
raise NotAGitProjectError()

self.config: BaseConfig = config
changelog_file_name = args.get("file_name") or cast(
str, self.config.settings.get("changelog_file")
self.config = config

changelog_file_name = args.get("file_name") or self.config.settings.get(
"changelog_file"
)
if not isinstance(changelog_file_name, str):
raise NotAllowed(
Expand Down Expand Up @@ -114,28 +115,28 @@ def _find_incremental_rev(self, latest_version: str, tags: list[GitTag]) -> str:
on our experience.
"""
SIMILARITY_THRESHOLD = 0.89
tag_ratio = map(
lambda tag: (
SequenceMatcher(
scores_and_tag_names: Generator[tuple[float, str]] = (
(
score,
tag.name,
)
for tag in tags
if (
score := SequenceMatcher(
None, latest_version, strip_local_version(tag.name)
).ratio(),
tag,
),
tags,
).ratio()
)
>= SIMILARITY_THRESHOLD
)
try:
score, tag = max(tag_ratio, key=itemgetter(0))
_, start_rev = max(scores_and_tag_names, key=itemgetter(0))
except ValueError:
raise NoRevisionError()
if score < SIMILARITY_THRESHOLD:
raise NoRevisionError()
start_rev = tag.name
return start_rev

def write_changelog(
self, changelog_out: str, lines: list[str], changelog_meta: changelog.Metadata
):
changelog_hook: Callable | None = self.cz.changelog_hook
with smart_open(self.file_name, "w", encoding=self.encoding) as changelog_file:
partial_changelog: str | None = None
if self.incremental:
Expand All @@ -145,8 +146,8 @@ def write_changelog(
changelog_out = "".join(new_lines)
partial_changelog = changelog_out

if changelog_hook:
changelog_out = changelog_hook(changelog_out, partial_changelog)
if self.cz.changelog_hook:
changelog_out = self.cz.changelog_hook(changelog_out, partial_changelog)

changelog_file.write(changelog_out)

Expand Down Expand Up @@ -221,14 +222,12 @@ def __call__(self):
extras.update(self.extras)
changelog_out = changelog.render_changelog(
tree, loader=self.cz.template_loader, template=self.template, **extras
)
changelog_out = changelog_out.lstrip("\n")
).lstrip("\n")

# Dry_run is executed here to avoid checking and reading the files
if self.dry_run:
changelog_hook: Callable | None = self.cz.changelog_hook
if changelog_hook:
changelog_out = changelog_hook(changelog_out, "")
if self.cz.changelog_hook:
changelog_out = self.cz.changelog_hook(changelog_out, "")
out.write(changelog_out)
raise DryRunExit()

Expand Down
64 changes: 28 additions & 36 deletions commitizen/commands/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,41 +92,36 @@ def manual_edit(self, message: str) -> str:
os.unlink(file.name)
return message

def __call__(self):
extra_args: str = self.arguments.get("extra_cli_args", "")
def _get_message(self) -> str:
if self.arguments.get("retry"):
m = self.read_backup_message()
if m is None:
raise NoCommitBackupError()
return m

allow_empty: bool = "--allow-empty" in extra_args
if self.config.settings.get("retry_after_failure") and not self.arguments.get(
"no_retry"
):
return self.read_backup_message() or self.prompt_commit_questions()
return self.prompt_commit_questions()

def __call__(self):
extra_args: str = self.arguments.get("extra_cli_args", "")
dry_run: bool = self.arguments.get("dry_run")
write_message_to_file: bool = self.arguments.get("write_message_to_file")
manual_edit: bool = self.arguments.get("edit")
signoff: bool = self.arguments.get("signoff")

is_all: bool = self.arguments.get("all")
if is_all:
c = git.add("-u")
if self.arguments.get("all"):
git.add("-u")

if git.is_staging_clean() and not (dry_run or allow_empty):
if git.is_staging_clean() and not (dry_run or "--allow-empty" in extra_args):
raise NothingToCommitError("No files added to staging!")

if write_message_to_file is not None and write_message_to_file.is_dir():
raise NotAllowed(f"{write_message_to_file} is a directory")

retry: bool = self.arguments.get("retry")
no_retry: bool = self.arguments.get("no_retry")
retry_after_failure: bool = self.config.settings.get("retry_after_failure")

if retry:
m = self.read_backup_message()
if m is None:
raise NoCommitBackupError()
elif retry_after_failure and not no_retry:
m = self.read_backup_message()
if m is None:
m = self.prompt_commit_questions()
else:
m = self.prompt_commit_questions()

if manual_edit:
m = self._get_message()
if self.arguments.get("edit"):
m = self.manual_edit(m)

out.info(f"\n{m}\n")
Expand All @@ -138,19 +133,15 @@ def __call__(self):
if dry_run:
raise DryRunExit()

always_signoff: bool = self.config.settings["always_signoff"]
signoff: bool = self.arguments.get("signoff")

if signoff:
out.warn(
"signoff mechanic is deprecated, please use `cz commit -- -s` instead."
)

if always_signoff or signoff:
if self.config.settings["always_signoff"] or signoff:
extra_args = f"{extra_args} -s".strip()

c = git.commit(m, args=extra_args)

if c.return_code != 0:
out.error(c.err)

Expand All @@ -160,11 +151,12 @@ def __call__(self):

raise CommitError()

if "nothing added" in c.out or "no changes added to commit" in c.out:
if any(s in c.out for s in ("nothing added", "no changes added to commit")):
out.error(c.out)
else:
with contextlib.suppress(FileNotFoundError):
os.remove(self.temp_file)
out.write(c.err)
out.write(c.out)
out.success("Commit successful!")
return

with contextlib.suppress(FileNotFoundError):
os.remove(self.temp_file)
out.write(c.err)
out.write(c.out)
out.success("Commit successful!")
12 changes: 9 additions & 3 deletions commitizen/config/base_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ def settings(self) -> Settings:
def path(self) -> Path | None:
return self._path

@path.setter
def path(self, path: str | Path) -> None:
"""
mypy does not like this until 1.16
See https://github.com/python/mypy/pull/18510
TODO: remove "type: ignore" from the call sites when 1.16 is available
"""
self._path = Path(path)

def set_key(self, key, value):
"""Set or update a key in the conf.

Expand All @@ -30,8 +39,5 @@ def set_key(self, key, value):
def update(self, data: Settings) -> None:
self._settings.update(data)

def add_path(self, path: str | Path) -> None:
self._path = Path(path)

def _parse_setting(self, data: bytes | str) -> None:
raise NotImplementedError()
2 changes: 1 addition & 1 deletion commitizen/config/json_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class JsonConfig(BaseConfig):
def __init__(self, *, data: bytes | str, path: Path | str):
super().__init__()
self.is_empty_config = False
self.add_path(path)
self.path = path # type: ignore
self._parse_setting(data)

def init_empty_config_content(self):
Expand Down
2 changes: 1 addition & 1 deletion commitizen/config/toml_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class TomlConfig(BaseConfig):
def __init__(self, *, data: bytes | str, path: Path | str):
super().__init__()
self.is_empty_config = False
self.add_path(path)
self.path = path # type: ignore
self._parse_setting(data)

def init_empty_config_content(self):
Expand Down
2 changes: 1 addition & 1 deletion commitizen/config/yaml_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class YAMLConfig(BaseConfig):
def __init__(self, *, data: bytes | str, path: Path | str):
super().__init__()
self.is_empty_config = False
self.add_path(path)
self.path = path # type: ignore
self._parse_setting(data)

def init_empty_config_content(self):
Expand Down
Loading