Skip to content

Commit 6ede88e

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

File tree

2 files changed

+227
-56
lines changed

2 files changed

+227
-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: 194 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,178 @@ 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+
def test_update_version_in_files_with_latin1_encoding(tmp_path):
297+
"""Test update_version_in_files with latin-1 encoding."""
298+
# Create a test file with latin-1 encoding
299+
test_file = tmp_path / "test_latin1.txt"
300+
content = 'version = "1.2.3"\n# This is a test file with latin-1 encoding\n'
301+
test_file.write_text(content, encoding="latin-1")
302+
303+
old_version = "1.2.3"
304+
new_version = "2.0.0"
305+
306+
updated_files = bump.update_version_in_files(
307+
old_version,
308+
new_version,
309+
[str(test_file)],
310+
check_consistency=True,
311+
encoding="latin-1",
312+
)
313+
314+
# Verify the file was updated
315+
assert len(updated_files) == 1
316+
assert str(test_file) in updated_files
317+
318+
# Verify the content was updated correctly
319+
updated_content = test_file.read_text(encoding="latin-1")
320+
assert f'version = "{new_version}"' in updated_content
321+
assert f'version = "{old_version}"' not in updated_content
322+
323+
324+
def test_update_version_in_files_with_utf16_encoding(tmp_path):
325+
"""Test update_version_in_files with utf-16 encoding."""
326+
# Create a test file with utf-16 encoding
327+
test_file = tmp_path / "test_utf16.txt"
328+
content = 'version = "1.2.3"\n# This is a test file with utf-16 encoding\n'
329+
test_file.write_text(content, encoding="utf-16")
330+
331+
old_version = "1.2.3"
332+
new_version = "2.0.0"
333+
334+
updated_files = bump.update_version_in_files(
335+
old_version,
336+
new_version,
337+
[str(test_file)],
338+
check_consistency=True,
339+
encoding="utf-16",
340+
)
341+
342+
# Verify the file was updated
343+
assert len(updated_files) == 1
344+
assert str(test_file) in updated_files
345+
346+
# Verify the content was updated correctly
347+
updated_content = test_file.read_text(encoding="utf-16")
348+
assert f'version = "{new_version}"' in updated_content
349+
assert f'version = "{old_version}"' not in updated_content
350+
351+
352+
def test_update_version_in_files_return_value(version_files):
353+
"""Test that update_version_in_files returns the correct list of updated files."""
354+
old_version = "1.2.3"
355+
new_version = "2.0.0"
356+
357+
updated_files = bump.update_version_in_files(
358+
old_version,
359+
new_version,
360+
version_files,
361+
check_consistency=False,
362+
encoding="utf-8",
363+
)
364+
365+
# Verify return value is a list
366+
assert isinstance(updated_files, list)
367+
368+
# Verify all files in the input are in the returned list
369+
assert len(updated_files) == len(version_files)
370+
for file_path in version_files:
371+
assert file_path in updated_files
372+
373+
# Verify the returned paths are strings
374+
for file_path in updated_files:
375+
assert isinstance(file_path, str)
376+
377+
378+
def test_update_version_in_files_return_value_partial_update(tmp_path):
379+
"""Test return value when only some files are updated."""
380+
# Create two test files
381+
file1 = tmp_path / "file1.txt"
382+
file2 = tmp_path / "file2.txt"
383+
384+
# File1 contains the version to update
385+
file1.write_text('version = "1.2.3"\n')
386+
387+
# File2 doesn't contain the version
388+
file2.write_text("some other content\n")
389+
390+
old_version = "1.2.3"
391+
new_version = "2.0.0"
392+
393+
updated_files = bump.update_version_in_files(
394+
old_version,
395+
new_version,
396+
[str(file1), str(file2)],
397+
check_consistency=False,
398+
encoding="utf-8",
399+
)
400+
401+
# Verify return value
402+
assert isinstance(updated_files, list)
403+
assert len(updated_files) == 2 # Both files should be in the list
404+
assert str(file1) in updated_files
405+
assert str(file2) in updated_files
406+
407+
# Verify file1 was actually updated
408+
content1 = file1.read_text(encoding="utf-8")
409+
assert f'version = "{new_version}"' in content1
410+
411+
# Verify file2 was not changed
412+
content2 = file2.read_text(encoding="utf-8")
413+
assert content2 == "some other content\n"

0 commit comments

Comments
 (0)