Skip to content

Commit a5aa553

Browse files
committed
refactor(bump): cleanup related to update_version_file
1 parent cc981fc commit a5aa553

File tree

2 files changed

+207
-56
lines changed

2 files changed

+207
-56
lines changed

commitizen/bump.py

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
import os
44
import re
55
from collections import OrderedDict
6-
from collections.abc import Iterable
6+
from collections.abc import Generator, Iterable
77
from glob import iglob
88
from logging import getLogger
99
from string import Template
1010
from typing import cast
1111

12-
from commitizen.defaults import BUMP_MESSAGE, ENCODING, MAJOR, MINOR, PATCH
12+
from commitizen.defaults import BUMP_MESSAGE, MAJOR, MINOR, PATCH
1313
from commitizen.exceptions import CurrentVersionNotFoundError
1414
from commitizen.git import GitCommit, smart_open
1515
from commitizen.version_schemes import Increment, Version
@@ -64,8 +64,8 @@ def update_version_in_files(
6464
new_version: str,
6565
files: Iterable[str],
6666
*,
67-
check_consistency: bool = False,
68-
encoding: str = ENCODING,
67+
check_consistency: bool,
68+
encoding: str,
6969
) -> list[str]:
7070
"""Change old version to the new one in every file given.
7171
@@ -75,16 +75,22 @@ def update_version_in_files(
7575
7676
Returns the list of updated files.
7777
"""
78-
# TODO: separate check step and write step
79-
updated = []
80-
for path, regex in _files_and_regexes(files, current_version):
81-
current_version_found, version_file = _bump_with_regex(
82-
path,
83-
current_version,
84-
new_version,
85-
regex,
86-
encoding=encoding,
87-
)
78+
updated_files = []
79+
80+
for path, pattern in _resolve_files_and_regexes(files, current_version):
81+
current_version_found = False
82+
bumped_lines = []
83+
84+
with open(path, encoding=encoding) as version_file:
85+
for line in version_file:
86+
bumped_line = (
87+
line.replace(current_version, new_version)
88+
if pattern.search(line)
89+
else line
90+
)
91+
92+
current_version_found = current_version_found or bumped_line != line
93+
bumped_lines.append(bumped_line)
8894

8995
if check_consistency and not current_version_found:
9096
raise CurrentVersionNotFoundError(
@@ -93,53 +99,32 @@ def update_version_in_files(
9399
"version_files are possibly inconsistent."
94100
)
95101

102+
bumped_version_file_content = "".join(bumped_lines)
103+
96104
# Write the file out again
97105
with smart_open(path, "w", encoding=encoding) as file:
98-
file.write(version_file)
99-
updated.append(path)
100-
return updated
106+
file.write(bumped_version_file_content)
107+
updated_files.append(path)
108+
109+
return updated_files
101110

102111

103-
def _files_and_regexes(patterns: Iterable[str], version: str) -> list[tuple[str, str]]:
112+
def _resolve_files_and_regexes(
113+
patterns: Iterable[str], version: str
114+
) -> Generator[tuple[str, re.Pattern], None, None]:
104115
"""
105116
Resolve all distinct files with their regexp from a list of glob patterns with optional regexp
106117
"""
107-
out: set[tuple[str, str]] = set()
118+
filepath_set: set[tuple[str, str]] = set()
108119
for pattern in patterns:
109120
drive, tail = os.path.splitdrive(pattern)
110121
path, _, regex = tail.partition(":")
111122
filepath = drive + path
112-
if not regex:
113-
regex = re.escape(version)
123+
regex = regex or re.escape(version)
114124

115-
for file in iglob(filepath):
116-
out.add((file, regex))
125+
filepath_set.update((path, regex) for path in iglob(filepath))
117126

118-
return sorted(out)
119-
120-
121-
def _bump_with_regex(
122-
version_filepath: str,
123-
current_version: str,
124-
new_version: str,
125-
regex: str,
126-
encoding: str = ENCODING,
127-
) -> tuple[bool, str]:
128-
current_version_found = False
129-
lines = []
130-
pattern = re.compile(regex)
131-
with open(version_filepath, encoding=encoding) as f:
132-
for line in f:
133-
if not pattern.search(line):
134-
lines.append(line)
135-
continue
136-
137-
bumped_line = line.replace(current_version, new_version)
138-
if bumped_line != line:
139-
current_version_found = True
140-
lines.append(bumped_line)
141-
142-
return current_version_found, "".join(lines)
127+
return ((path, re.compile(regex)) for path, regex in sorted(filepath_set))
143128

144129

145130
def create_commit_message(

tests/test_bump_update_version_in_files.py

Lines changed: 174 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,11 @@ def test_update_version_in_files(version_files, file_regression):
103103
old_version = "1.2.3"
104104
new_version = "2.0.0"
105105
bump.update_version_in_files(
106-
old_version, new_version, version_files, encoding="utf-8"
106+
old_version,
107+
new_version,
108+
version_files,
109+
check_consistency=False,
110+
encoding="utf-8",
107111
)
108112

109113
file_contents = ""
@@ -119,7 +123,9 @@ def test_partial_update_of_file(version_repeated_file, file_regression):
119123
regex = "version"
120124
location = f"{version_repeated_file}:{regex}"
121125

122-
bump.update_version_in_files(old_version, new_version, [location], encoding="utf-8")
126+
bump.update_version_in_files(
127+
old_version, new_version, [location], check_consistency=False, encoding="utf-8"
128+
)
123129
with open(version_repeated_file, encoding="utf-8") as f:
124130
file_regression.check(f.read(), extension=".json")
125131

@@ -129,7 +135,9 @@ def test_random_location(random_location_version_file, file_regression):
129135
new_version = "2.0.0"
130136
location = f"{random_location_version_file}:version.+Commitizen"
131137

132-
bump.update_version_in_files(old_version, new_version, [location], encoding="utf-8")
138+
bump.update_version_in_files(
139+
old_version, new_version, [location], check_consistency=False, encoding="utf-8"
140+
)
133141
with open(random_location_version_file, encoding="utf-8") as f:
134142
file_regression.check(f.read(), extension=".lock")
135143

@@ -141,7 +149,9 @@ def test_duplicates_are_change_with_no_regex(
141149
new_version = "2.0.0"
142150
location = f"{random_location_version_file}:version"
143151

144-
bump.update_version_in_files(old_version, new_version, [location], encoding="utf-8")
152+
bump.update_version_in_files(
153+
old_version, new_version, [location], check_consistency=False, encoding="utf-8"
154+
)
145155
with open(random_location_version_file, encoding="utf-8") as f:
146156
file_regression.check(f.read(), extension=".lock")
147157

@@ -153,7 +163,9 @@ def test_version_bump_increase_string_length(
153163
new_version = "1.2.10"
154164
location = f"{multiple_versions_increase_string}:version"
155165

156-
bump.update_version_in_files(old_version, new_version, [location], encoding="utf-8")
166+
bump.update_version_in_files(
167+
old_version, new_version, [location], check_consistency=False, encoding="utf-8"
168+
)
157169
with open(multiple_versions_increase_string, encoding="utf-8") as f:
158170
file_regression.check(f.read(), extension=".txt")
159171

@@ -165,7 +177,9 @@ def test_version_bump_reduce_string_length(
165177
new_version = "2.0.0"
166178
location = f"{multiple_versions_reduce_string}:version"
167179

168-
bump.update_version_in_files(old_version, new_version, [location], encoding="utf-8")
180+
bump.update_version_in_files(
181+
old_version, new_version, [location], check_consistency=False, encoding="utf-8"
182+
)
169183
with open(multiple_versions_reduce_string, encoding="utf-8") as f:
170184
file_regression.check(f.read(), extension=".txt")
171185

@@ -204,7 +218,9 @@ def test_multiple_versions_to_bump(
204218
new_version = "1.2.10"
205219
location = f"{multiple_versions_to_update_poetry_lock}:version"
206220

207-
bump.update_version_in_files(old_version, new_version, [location], encoding="utf-8")
221+
bump.update_version_in_files(
222+
old_version, new_version, [location], check_consistency=False, encoding="utf-8"
223+
)
208224
with open(multiple_versions_to_update_poetry_lock, encoding="utf-8") as f:
209225
file_regression.check(f.read(), extension=".toml")
210226

@@ -220,8 +236,158 @@ def test_update_version_in_globbed_files(commitizen_config_file, file_regression
220236
version_files = [commitizen_config_file.dirpath("*.toml")]
221237

222238
bump.update_version_in_files(
223-
old_version, new_version, version_files, encoding="utf-8"
239+
old_version,
240+
new_version,
241+
version_files,
242+
check_consistency=False,
243+
encoding="utf-8",
224244
)
225245

226246
for file in commitizen_config_file, other:
227247
file_regression.check(file.read_text("utf-8"), extension=".toml")
248+
249+
250+
def test_update_version_in_files_with_check_consistency_true(version_files):
251+
"""Test update_version_in_files with check_consistency=True (success case)."""
252+
old_version = "1.2.3"
253+
new_version = "2.0.0"
254+
255+
# This should succeed because all files contain the current version
256+
updated_files = bump.update_version_in_files(
257+
old_version,
258+
new_version,
259+
version_files,
260+
check_consistency=True,
261+
encoding="utf-8",
262+
)
263+
264+
# Verify that all files were updated
265+
assert len(updated_files) == len(version_files)
266+
for file_path in updated_files:
267+
assert file_path in version_files
268+
269+
270+
def test_update_version_in_files_with_check_consistency_true_failure(
271+
commitizen_config_file, inconsistent_python_version_file
272+
):
273+
"""Test update_version_in_files with check_consistency=True (failure case)."""
274+
old_version = "1.2.3"
275+
new_version = "2.0.0"
276+
version_files = [commitizen_config_file, inconsistent_python_version_file]
277+
278+
# This should fail because inconsistent_python_version_file doesn't contain the current version
279+
with pytest.raises(CurrentVersionNotFoundError) as excinfo:
280+
bump.update_version_in_files(
281+
old_version,
282+
new_version,
283+
version_files,
284+
check_consistency=True,
285+
encoding="utf-8",
286+
)
287+
288+
expected_msg = (
289+
f"Current version {old_version} is not found in {inconsistent_python_version_file}.\n"
290+
"The version defined in commitizen configuration and the ones in "
291+
"version_files are possibly inconsistent."
292+
)
293+
assert expected_msg in str(excinfo.value)
294+
295+
296+
@pytest.mark.parametrize(
297+
"encoding,filename",
298+
[
299+
("latin-1", "test_latin1.txt"),
300+
("utf-16", "test_utf16.txt"),
301+
],
302+
ids=["latin-1", "utf-16"],
303+
)
304+
def test_update_version_in_files_with_different_encodings(tmp_path, encoding, filename):
305+
"""Test update_version_in_files with different encodings."""
306+
# Create a test file with the specified encoding
307+
test_file = tmp_path / filename
308+
content = f'version = "1.2.3"\n# This is a test file with {encoding} encoding\n'
309+
test_file.write_text(content, encoding=encoding)
310+
311+
old_version = "1.2.3"
312+
new_version = "2.0.0"
313+
314+
updated_files = bump.update_version_in_files(
315+
old_version,
316+
new_version,
317+
[str(test_file)],
318+
check_consistency=True,
319+
encoding=encoding,
320+
)
321+
322+
# Verify the file was updated
323+
assert len(updated_files) == 1
324+
assert str(test_file) in updated_files
325+
326+
# Verify the content was updated correctly
327+
updated_content = test_file.read_text(encoding=encoding)
328+
assert f'version = "{new_version}"' in updated_content
329+
assert f'version = "{old_version}"' not in updated_content
330+
331+
332+
def test_update_version_in_files_return_value(version_files):
333+
"""Test that update_version_in_files returns the correct list of updated files."""
334+
old_version = "1.2.3"
335+
new_version = "2.0.0"
336+
337+
updated_files = bump.update_version_in_files(
338+
old_version,
339+
new_version,
340+
version_files,
341+
check_consistency=False,
342+
encoding="utf-8",
343+
)
344+
345+
# Verify return value is a list
346+
assert isinstance(updated_files, list)
347+
348+
# Verify all files in the input are in the returned list
349+
assert len(updated_files) == len(version_files)
350+
for file_path in version_files:
351+
assert file_path in updated_files
352+
353+
# Verify the returned paths are strings
354+
for file_path in updated_files:
355+
assert isinstance(file_path, str)
356+
357+
358+
def test_update_version_in_files_return_value_partial_update(tmp_path):
359+
"""Test return value when only some files are updated."""
360+
# Create two test files
361+
file1 = tmp_path / "file1.txt"
362+
file2 = tmp_path / "file2.txt"
363+
364+
# File1 contains the version to update
365+
file1.write_text('version = "1.2.3"\n')
366+
367+
# File2 doesn't contain the version
368+
file2.write_text("some other content\n")
369+
370+
old_version = "1.2.3"
371+
new_version = "2.0.0"
372+
373+
updated_files = bump.update_version_in_files(
374+
old_version,
375+
new_version,
376+
[str(file1), str(file2)],
377+
check_consistency=False,
378+
encoding="utf-8",
379+
)
380+
381+
# Verify return value
382+
assert isinstance(updated_files, list)
383+
assert len(updated_files) == 2 # Both files should be in the list
384+
assert str(file1) in updated_files
385+
assert str(file2) in updated_files
386+
387+
# Verify file1 was actually updated
388+
content1 = file1.read_text(encoding="utf-8")
389+
assert f'version = "{new_version}"' in content1
390+
391+
# Verify file2 was not changed
392+
content2 = file2.read_text(encoding="utf-8")
393+
assert content2 == "some other content\n"

0 commit comments

Comments
 (0)