Skip to content

feat(bump): version_files now support glob patterns (fix #1067) #1070

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

Merged
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
39 changes: 28 additions & 11 deletions commitizen/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import re
from collections import OrderedDict
from glob import iglob
from string import Template
from typing import cast

Expand Down Expand Up @@ -53,23 +54,20 @@ def update_version_in_files(
*,
check_consistency: bool = False,
encoding: str = encoding,
) -> None:
) -> list[str]:
"""Change old version to the new one in every file given.

Note that this version is not the tag formatted one.
So for example, your tag could look like `v1.0.0` while your version in
the package like `1.0.0`.

Returns the list of updated files.
"""
# TODO: separate check step and write step
for location in files:
drive, tail = os.path.splitdrive(location)
path, _, regex = tail.partition(":")
filepath = drive + path
if not regex:
regex = _version_to_regex(current_version)

updated = []
for path, regex in files_and_regexs(files, current_version):
current_version_found, version_file = _bump_with_regex(
filepath,
path,
current_version,
new_version,
regex,
Expand All @@ -78,14 +76,33 @@ def update_version_in_files(

if check_consistency and not current_version_found:
raise CurrentVersionNotFoundError(
f"Current version {current_version} is not found in {location}.\n"
f"Current version {current_version} is not found in {path}.\n"
"The version defined in commitizen configuration and the ones in "
"version_files are possibly inconsistent."
)

# Write the file out again
with smart_open(filepath, "w", encoding=encoding) as file:
with smart_open(path, "w", encoding=encoding) as file:
file.write(version_file)
updated.append(path)
return updated


def files_and_regexs(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 = []
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)

for path in iglob(filepath):
out.append((path, regex))
return sorted(list(set(out)))


def _bump_with_regex(
Expand Down
65 changes: 24 additions & 41 deletions commitizen/commands/bump.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from __future__ import annotations

import os
import warnings
from logging import getLogger

import questionary

from commitizen import bump, cmd, factory, git, hooks, out
from commitizen import bump, factory, git, hooks, out
from commitizen.commands.changelog import Changelog
from commitizen.config import BaseConfig
from commitizen.exceptions import (
Expand Down Expand Up @@ -286,56 +285,39 @@ def __call__(self) -> None: # noqa: C901
"The commits found are not eligible to be bumped"
)

files: list[str] = []
if self.changelog:
args = {
"unreleased_version": new_tag_version,
"template": self.template,
"extras": self.extras,
"incremental": True,
"dry_run": dry_run,
}
if self.changelog_to_stdout:
changelog_cmd = Changelog(
self.config,
{
"unreleased_version": new_tag_version,
"template": self.template,
"extras": self.extras,
"incremental": True,
"dry_run": True,
},
)
changelog_cmd = Changelog(self.config, {**args, "dry_run": True})
try:
changelog_cmd()
except DryRunExit:
pass
changelog_cmd = Changelog(
self.config,
{
"unreleased_version": new_tag_version,
"incremental": True,
"dry_run": dry_run,
"template": self.template,
"extras": self.extras,
"file_name": self.file_name,
},
)

args["file_name"] = self.file_name
changelog_cmd = Changelog(self.config, args)
changelog_cmd()
file_names = []
for file_name in version_files:
drive, tail = os.path.splitdrive(file_name)
path, _, regex = tail.partition(":")
path = drive + path if path != "" else drive + regex
file_names.append(path)
git_add_changelog_and_version_files_command = (
f"git add {changelog_cmd.file_name} "
f"{' '.join(name for name in file_names)}"
)
c = cmd.run(git_add_changelog_and_version_files_command)
files.append(changelog_cmd.file_name)

# Do not perform operations over files or git.
if dry_run:
raise DryRunExit()

bump.update_version_in_files(
str(current_version),
str(new_version),
version_files,
check_consistency=self.check_consistency,
encoding=self.encoding,
files.extend(
bump.update_version_in_files(
str(current_version),
str(new_version),
version_files,
check_consistency=self.check_consistency,
encoding=self.encoding,
)
)

provider.set_version(str(new_version))
Expand All @@ -358,12 +340,13 @@ def __call__(self) -> None: # noqa: C901
raise ExpectedExit()

# FIXME: check if any changes have been staged
git.add(*files)
c = git.commit(message, args=self._get_commit_args())
if self.retry and c.return_code != 0 and self.changelog:
# Maybe pre-commit reformatted some files? Retry once
logger.debug("1st git.commit error: %s", c.err)
logger.info("1st commit attempt failed; retrying once")
cmd.run(git_add_changelog_and_version_files_command)
git.add(*files)
c = git.commit(message, args=self._get_commit_args())
if c.return_code != 0:
err = c.err.strip() or c.out
Expand Down
6 changes: 3 additions & 3 deletions commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from difflib import SequenceMatcher
from operator import itemgetter
from pathlib import Path
from typing import Callable
from typing import Callable, cast

from commitizen import bump, changelog, defaults, factory, git, out

Expand Down Expand Up @@ -38,8 +38,8 @@ def __init__(self, config: BaseConfig, args):
self.start_rev = args.get("start_rev") or self.config.settings.get(
"changelog_start_rev"
)
self.file_name = args.get("file_name") or self.config.settings.get(
"changelog_file"
self.file_name = args.get("file_name") or cast(
str, self.config.settings.get("changelog_file")
)
if not isinstance(self.file_name, str):
raise NotAllowed(
Expand Down
4 changes: 2 additions & 2 deletions commitizen/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ def tag(
return c


def add(args: str = "") -> cmd.Command:
c = cmd.run(f"git add {args}")
def add(*args: str) -> cmd.Command:
c = cmd.run(f"git add {' '.join(args)}")
return c


Expand Down
4 changes: 4 additions & 0 deletions docs/bump.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,10 @@ In the example above, we can see the reference `"setup.py:version"`.
This means that it will find a file `setup.py` and will only make a change
in a line containing the `version` substring.

!!! note
Files can be specified using relative (to the execution) paths, absolute paths
or glob patterns.

---

### `bump_message`
Expand Down
10 changes: 10 additions & 0 deletions tests/commands/test_bump_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,16 @@ def test_bump_with_git_to_stdout_arg(mocker: MockFixture, capsys, changelog_path
""",
id="version in __init__.py with regex",
),
pytest.param(
"pyproject.toml",
"*.toml:^version",
"""
[tool.poetry]
name = "my_package"
version = "0.1.0"
""",
id="version in pyproject.toml with glob and regex",
),
],
)
@pytest.mark.parametrize(
Expand Down
18 changes: 18 additions & 0 deletions tests/test_bump_update_version_in_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,21 @@ def test_multiplt_versions_to_bump(
bump.update_version_in_files(old_version, new_version, [location], encoding="utf-8")
with open(multiple_versions_to_update_poetry_lock, encoding="utf-8") as f:
file_regression.check(f.read(), extension=".toml")


def test_update_version_in_globbed_files(commitizen_config_file, file_regression):
old_version = "1.2.3"
new_version = "2.0.0"
other = commitizen_config_file.dirpath("other.toml")
print(commitizen_config_file, other)
copyfile(commitizen_config_file, other)

# Prepend full ppath as test assume absolute paths or cwd-relative
version_files = [commitizen_config_file.dirpath("*.toml")]

bump.update_version_in_files(
old_version, new_version, version_files, encoding="utf-8"
)

for file in commitizen_config_file, other:
file_regression.check(file.read_text("utf-8"), extension=".toml")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.poetry]
name = "commitizen"
version = "2.0.0"