Skip to content

Commit 781e8d8

Browse files
committed
Fix search and replace options for replace.
- The `--search` and `--replace` options now completely override any other search and replace logic. Fixes #34
1 parent 8ab8e5e commit 781e8d8

File tree

3 files changed

+87
-13
lines changed

3 files changed

+87
-13
lines changed

bumpversion/cli.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,6 @@ def replace(
408408
new_version=new_version,
409409
parse=parse,
410410
serialize=serialize or None,
411-
search=search,
412-
replace=replace,
413411
commit=False,
414412
tag=False,
415413
sign_tags=False,
@@ -441,6 +439,6 @@ def replace(
441439

442440
ctx = get_context(config, version, next_version)
443441

444-
configured_files = resolve_file_config(config.files, config.version_config)
442+
configured_files = resolve_file_config(config.files, config.version_config, search, replace)
445443

446444
modify_files(configured_files, version, next_version, ctx, dry_run)

bumpversion/files.py

+26-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import glob
33
import logging
44
from difflib import context_diff
5-
from typing import List, MutableMapping
5+
from typing import List, MutableMapping, Optional
66

77
from bumpversion.config import FileConfig
88
from bumpversion.exceptions import VersionNotFoundError
@@ -14,12 +14,18 @@
1414
class ConfiguredFile:
1515
"""A file to modify in a configured way."""
1616

17-
def __init__(self, file_cfg: FileConfig, version_config: VersionConfig) -> None:
17+
def __init__(
18+
self,
19+
file_cfg: FileConfig,
20+
version_config: VersionConfig,
21+
search: Optional[str] = None,
22+
replace: Optional[str] = None,
23+
) -> None:
1824
self.path = file_cfg.filename
1925
self.parse = file_cfg.parse or version_config.parse_regex.pattern
2026
self.serialize = file_cfg.serialize or version_config.serialize_formats
21-
self.search = file_cfg.search or version_config.search
22-
self.replace = file_cfg.replace or version_config.replace
27+
self.search = search or file_cfg.search or version_config.search
28+
self.replace = replace or file_cfg.replace or version_config.replace
2329
self.version_config = VersionConfig(
2430
self.parse, self.serialize, self.search, self.replace, version_config.part_configs
2531
)
@@ -97,8 +103,10 @@ def replace_version(
97103
file_content_before = f.read()
98104
file_new_lines = f.newlines[0] if isinstance(f.newlines, tuple) else f.newlines
99105

100-
context["current_version"] = self.version_config.serialize(current_version, context)
101-
context["new_version"] = self.version_config.serialize(new_version, context)
106+
if current_version:
107+
context["current_version"] = self.version_config.serialize(current_version, context)
108+
if new_version:
109+
context["new_version"] = self.version_config.serialize(new_version, context)
102110

103111
search_for = self.version_config.search.format(**context)
104112
replace_with = self.version_config.replace.format(**context)
@@ -138,13 +146,17 @@ def __repr__(self) -> str:
138146
return f"<bumpversion.ConfiguredFile:{self.path}>"
139147

140148

141-
def resolve_file_config(files: List[FileConfig], version_config: VersionConfig) -> List[ConfiguredFile]:
149+
def resolve_file_config(
150+
files: List[FileConfig], version_config: VersionConfig, search: Optional[str] = None, replace: Optional[str] = None
151+
) -> List[ConfiguredFile]:
142152
"""
143153
Resolve the files, searching and replacing values according to the FileConfig.
144154
145155
Args:
146156
files: A list of file configurations
147157
version_config: How the version should be changed
158+
search: The search pattern to use instead of any configured search pattern
159+
replace: The replace pattern to use instead of any configured replace pattern
148160
149161
Returns:
150162
A list of ConfiguredFiles
@@ -154,7 +166,7 @@ def resolve_file_config(files: List[FileConfig], version_config: VersionConfig)
154166
if file_cfg.glob:
155167
configured_files.extend(get_glob_files(file_cfg, version_config))
156168
else:
157-
configured_files.append(ConfiguredFile(file_cfg, version_config))
169+
configured_files.append(ConfiguredFile(file_cfg, version_config, search, replace))
158170

159171
return configured_files
160172

@@ -181,13 +193,17 @@ def modify_files(
181193
f.replace_version(current_version, new_version, context, dry_run)
182194

183195

184-
def get_glob_files(file_cfg: FileConfig, version_config: VersionConfig) -> List[ConfiguredFile]:
196+
def get_glob_files(
197+
file_cfg: FileConfig, version_config: VersionConfig, search: Optional[str] = None, replace: Optional[str] = None
198+
) -> List[ConfiguredFile]:
185199
"""
186200
Return a list of files that match the glob pattern.
187201
188202
Args:
189203
file_cfg: The file configuration containing the glob pattern
190204
version_config: The version configuration
205+
search: The search pattern to use instead of any configured search pattern
206+
replace: The replace pattern to use instead of any configured replace pattern
191207
192208
Returns:
193209
A list of resolved files according to the pattern.
@@ -196,7 +212,7 @@ def get_glob_files(file_cfg: FileConfig, version_config: VersionConfig) -> List[
196212
for filename_glob in glob.glob(file_cfg.glob, recursive=True):
197213
new_file_cfg = file_cfg.copy()
198214
new_file_cfg.filename = filename_glob
199-
files.append(ConfiguredFile(new_file_cfg, version_config))
215+
files.append(ConfiguredFile(new_file_cfg, version_config, search, replace))
200216
return files
201217

202218

tests/test_cli.py

+60
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Tests for `bumpversion` package."""
22
import shutil
33
import subprocess
4+
import traceback
45
from pathlib import Path
56

67
import pytest
@@ -429,3 +430,62 @@ def test_replace_specific_files(mocker, git_repo, fixtures_path):
429430
configured_files = call_args[0]
430431
assert len(configured_files) == 1
431432
assert configured_files[0].path == "VERSION"
433+
434+
435+
TEST_REPLACE_CONFIG = {
436+
"tool": {
437+
"bumpversion": {
438+
"allow_dirty": True,
439+
"commit": False,
440+
"current_version": "2.17.7",
441+
"files": [],
442+
"message": "Bump version: {current_version} → {new_version}",
443+
"parse": "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)",
444+
"parts": {
445+
"major": {},
446+
"minor": {},
447+
"patch": {},
448+
},
449+
"replace": "{new_version}",
450+
"search": "{current_version}",
451+
"serialize": ["{major}.{minor}.{patch}"],
452+
"sign_tags": False,
453+
"tag": False,
454+
"tag_message": "Bump version: {current_version} → {new_version}",
455+
"tag_name": "v{new_version}",
456+
}
457+
}
458+
}
459+
460+
461+
def test_replace_search_with_plain_string(tmp_path, fixtures_path):
462+
"""Replace should not worry if the search or replace values have version info."""
463+
from tomlkit import dumps
464+
465+
# Arrange
466+
config_path = tmp_path / "pyproject.toml"
467+
config_path.write_text(dumps(TEST_REPLACE_CONFIG))
468+
doc_path = tmp_path / "docs.yaml"
469+
doc_path.write_text("url: https://github.com/sampleuser/workflows/main/.github/update_mailmap.py")
470+
471+
runner: CliRunner = CliRunner()
472+
with inside_dir(tmp_path):
473+
result: Result = runner.invoke(
474+
cli.cli,
475+
[
476+
"replace",
477+
"--no-configured-files",
478+
"--search",
479+
"/workflows/main/",
480+
"--replace",
481+
"/workflows/v{current_version}/",
482+
"./docs.yaml",
483+
],
484+
)
485+
486+
if result.exit_code != 0:
487+
print("Here is the output:")
488+
print(result.output)
489+
print(traceback.print_exception(result.exc_info[1]))
490+
491+
assert result.exit_code == 0

0 commit comments

Comments
 (0)