Skip to content

Commit 62f2930

Browse files
authored
Merge pull request #326 from callowayproject/fix-moveable-tags
Bug Fixes
2 parents 65a9f4c + de4438b commit 62f2930

File tree

5 files changed

+119
-34
lines changed

5 files changed

+119
-34
lines changed

bumpversion/files.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pathlib import Path
88
from typing import Dict, List, MutableMapping, Optional
99

10+
from bumpversion.config import DEFAULTS as DEFAULT_CONFIG
1011
from bumpversion.config.models import FileChange
1112
from bumpversion.exceptions import VersionNotFoundError
1213
from bumpversion.ui import get_indented_logger
@@ -129,7 +130,7 @@ def write_file_contents(self, contents: str) -> None:
129130
f.write(contents)
130131

131132
def _contains_change_pattern(
132-
self, search_expression: re.Pattern, raw_search_expression: str, version: Version, context: MutableMapping
133+
self, search_expression: re.Pattern, raw_search_expression: str, version: Version
133134
) -> bool:
134135
"""
135136
Does the file contain the change pattern?
@@ -138,7 +139,6 @@ def _contains_change_pattern(
138139
search_expression: The compiled search expression
139140
raw_search_expression: The raw search expression
140141
version: The version to check, in case it's not the same as the original
141-
context: The context to use
142142
143143
Raises:
144144
VersionNotFoundError: if the version number isn't present in this file.
@@ -155,7 +155,7 @@ def _contains_change_pattern(
155155
# match instead. This is probably the case if environment variables are used.
156156

157157
# check whether `search` isn't customized
158-
search_pattern_is_default = self.file_change.search == self.version_config.search
158+
search_pattern_is_default = self.file_change.search == DEFAULT_CONFIG["search"]
159159

160160
if search_pattern_is_default and contains_pattern(re.compile(re.escape(version.original)), file_contents):
161161
# The original version is present, and we're not looking for something
@@ -195,7 +195,7 @@ def make_file_change(
195195
search_for, raw_search_pattern = self.file_change.get_search_pattern(context)
196196
replace_with = self.version_config.replace.format(**context)
197197

198-
if not self._contains_change_pattern(search_for, raw_search_pattern, current_version, context):
198+
if not self._contains_change_pattern(search_for, raw_search_pattern, current_version):
199199
logger.dedent()
200200
return
201201

bumpversion/scm/git.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def commit_and_tag(self, files: List[Pathlike], context: MutableMapping, dry_run
103103
tag(tag_name, sign=self.config.sign_tags, message=tag_message)
104104

105105
for m_tag_name in self.config.moveable_tags:
106-
moveable_tag(m_tag_name)
106+
moveable_tag(m_tag_name.format(**context))
107107

108108
def assert_nondirty(self) -> None:
109109
"""

tests/test_cli/test_replace.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,7 @@ def test_unintentional_valid_regex_still_found(self, tmp_path: Path, caplog, run
197197
)
198198
config_file = tmp_path / ".bumpversion.toml"
199199
config_file.write_text(
200-
"[tool.bumpversion]\n"
201-
'current_version = "0.0.1"\n'
202-
"allow_dirty = true\n\n"
203-
"[[tool.bumpversion.files]]\n"
204-
'filename = "VERSION"\n'
205-
"regex = false\n"
206-
f'search = "{search}"\n'
207-
f'replace = "{replace}"\n',
200+
"[tool.bumpversion]\n" 'current_version = "0.0.1"\n' "allow_dirty = true\n\n",
208201
encoding="utf-8",
209202
)
210203

@@ -214,7 +207,7 @@ def test_unintentional_valid_regex_still_found(self, tmp_path: Path, caplog, run
214207
cli.cli,
215208
[
216209
"replace",
217-
"--verbose",
210+
"-vv",
218211
"--no-regex",
219212
"--no-configured-files",
220213
"--search",
@@ -229,7 +222,10 @@ def test_unintentional_valid_regex_still_found(self, tmp_path: Path, caplog, run
229222
if result.exit_code != 0:
230223
print("Here is the output:")
231224
print(result.output)
232-
print(traceback.print_exception(result.exc_info[1]))
225+
# print(traceback.print_exception(result.exc_info[1]))
226+
print(caplog.text)
227+
print("ouch")
228+
print(version_path.read_text())
233229

234230
assert result.exit_code == 0
235231
assert (

tests/test_files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_non_matching_search_does_not_modify_file(self, tmp_path: Path):
6969
"""
7070
# Unreleased
7171
72-
* bullet point A
72+
* bullet point A prep for 1.0.3
7373
7474
# Release v'older' (2019-09-17)
7575

tests/test_scm/test_git.py

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,53 +14,56 @@
1414
from tests.conftest import inside_dir
1515

1616

17+
@pytest.fixture
18+
def git_instance(scm_config: SCMConfig) -> Git:
19+
"""Return a Git instance."""
20+
return Git(config=scm_config)
21+
22+
1723
class TestGit:
1824
"""Tests related to Git."""
1925

2026
class TestIsAvailable:
2127
"""Tests related to Git.is_available."""
2228

23-
def test_recognizes_a_git_repo(self, git_repo: Path, scm_config: SCMConfig) -> None:
29+
def test_recognizes_a_git_repo(self, git_repo: Path, git_instance: Git) -> None:
2430
"""Should return true if git is available, and it is a git repo."""
2531
with inside_dir(git_repo):
26-
git_tool = Git(scm_config)
27-
assert git_tool.is_available()
32+
assert git_instance.is_available()
2833

29-
def test_recognizes_not_a_git_repo(self, tmp_path: Path, scm_config: SCMConfig) -> None:
34+
def test_recognizes_not_a_git_repo(self, tmp_path: Path, git_instance: Git) -> None:
3035
"""Should return false if it is not a git repo."""
3136
with inside_dir(tmp_path):
32-
git_tool = Git(scm_config)
33-
assert not git_tool.is_available()
37+
assert not git_instance.is_available()
3438

3539
class TestLatestTagInfo:
3640
"""Test for the Git.latest_tag_info() function."""
3741

38-
def test_returns_default_if_not_available(self, tmp_path: Path, scm_config: SCMConfig) -> None:
42+
def test_returns_default_if_not_available(self, tmp_path: Path, git_instance: Git) -> None:
3943
"""If git is not available, it returns the default LatestTagInfo object, with all None values."""
4044
# Arrange
4145
expected = LatestTagInfo()
42-
tool = Git(scm_config)
4346

4447
# Act
4548
with inside_dir(tmp_path):
46-
info = tool.latest_tag_info()
49+
info = git_instance.latest_tag_info()
4750

4851
# Assert
4952
assert info == expected
5053

51-
def test_returns_correct_commit_and_tag_info(self, git_repo: Path, scm_config: SCMConfig) -> None:
54+
def test_returns_correct_commit_and_tag_info(self, git_repo: Path, git_instance: Git) -> None:
5255
"""Should return information that it is a git repo."""
5356
# Arrange
5457
tag_prefix = "app/"
5558
readme = git_repo.joinpath("readme.md")
5659
readme.touch()
57-
scm_config.tag_name = f"{tag_prefix}{{new_version}}"
60+
git_instance.config.tag_name = f"{tag_prefix}{{new_version}}"
5861

5962
# Act
6063
with inside_dir(git_repo):
6164
commit_readme("first")
6265
tag(f"{tag_prefix}0.1.0", sign=False, message="bumpversion")
63-
tag_info = Git(scm_config).latest_tag_info()
66+
tag_info = git_instance.latest_tag_info()
6467

6568
# Assert
6669
assert tag_info.commit_sha is not None
@@ -79,7 +82,7 @@ class TestGitAddPath:
7982
@patch("bumpversion.scm.git.Git.latest_tag_info")
8083
@patch("bumpversion.scm.git.is_subpath")
8184
def test_valid_subpath_is_added(
82-
self, mock_is_subpath, mock_latest_tag_info, mock_run_command, scm_config: SCMConfig, tmp_path: Path
85+
self, mock_is_subpath, mock_latest_tag_info, mock_run_command, git_instance: Git, tmp_path: Path
8386
):
8487
"""A path that is a subpath of the repository root should be added."""
8588
# Arrange
@@ -89,7 +92,6 @@ def test_valid_subpath_is_added(
8992
mock_latest_tag_info.return_value.repository_root = repository_root
9093
mock_is_subpath.return_value = True
9194
mock_run_command.return_value = None
92-
git_instance = Git(scm_config)
9395

9496
# Act
9597
with inside_dir(repository_root):
@@ -102,7 +104,7 @@ def test_valid_subpath_is_added(
102104
@patch("bumpversion.scm.git.Git.latest_tag_info")
103105
@patch("bumpversion.scm.git.is_subpath")
104106
def test_invalid_subpath_is_not_added(
105-
self, mock_is_subpath, mock_latest_tag_info, mock_run_command, scm_config, tmp_path: Path
107+
self, mock_is_subpath, mock_latest_tag_info, mock_run_command, git_instance: Git, tmp_path: Path
106108
):
107109
"""A path that is not a subpath of the repository root should not be added."""
108110
# Arrange
@@ -111,7 +113,6 @@ def test_invalid_subpath_is_not_added(
111113
path_to_add = repository_root / "file.txt"
112114
mock_latest_tag_info.return_value.repository_root = repository_root
113115
mock_is_subpath.return_value = False
114-
git_instance = Git(scm_config)
115116

116117
# Act
117118
git_instance.add_path(path_to_add)
@@ -123,7 +124,7 @@ def test_invalid_subpath_is_not_added(
123124
@patch("bumpversion.scm.git.Git.latest_tag_info")
124125
@patch("bumpversion.scm.git.is_subpath")
125126
def test_raises_error_on_command_failure(
126-
self, mock_is_subpath, mock_latest_tag_info, mock_run_command, scm_config: SCMConfig, tmp_path: Path
127+
self, mock_is_subpath, mock_latest_tag_info, mock_run_command, git_instance: Git, tmp_path: Path
127128
):
128129
"""If the git command fails, a BumpVersionError should be raised."""
129130
# Arrange
@@ -133,13 +134,101 @@ def test_raises_error_on_command_failure(
133134
mock_latest_tag_info.return_value.repository_root = repository_root
134135
mock_is_subpath.return_value = True
135136
mock_run_command.side_effect = subprocess.CalledProcessError(returncode=1, cmd="git add")
136-
git_instance = Git(scm_config)
137137

138138
# Act / Assert
139139
with inside_dir(repository_root):
140140
with pytest.raises(BumpVersionError):
141141
git_instance.add_path(path_to_add)
142142

143+
class TestGitCommitAndTag:
144+
"""Tests for the commit_and_tag() method."""
145+
146+
def test_dry_run_skips_method(self, git_instance: Git, mocker):
147+
"""If dry_run is True, the method is short-circuited."""
148+
# Arrange
149+
files = ["file1.txt", "file2.txt"]
150+
context = {"new_version": "1.0.0", "new_major": "1", "current_version": "0.1.0"}
151+
mock_add_path = mocker.patch.object(git_instance, "add_path")
152+
mock_commit = mocker.patch.object(git_instance, "commit")
153+
mock_tag = mocker.patch("bumpversion.scm.git.tag")
154+
mock_moveable_tag = mocker.patch("bumpversion.scm.git.moveable_tag")
155+
156+
# Act
157+
git_instance.commit_and_tag(files, context, dry_run=True)
158+
159+
# Assert
160+
mock_add_path.assert_not_called()
161+
mock_commit.assert_not_called()
162+
mock_tag.assert_not_called()
163+
mock_moveable_tag.assert_not_called()
164+
165+
def test_commits_and_tags_when_configured(self, git_instance: Git, mocker):
166+
"""Does both commit and tag functions when they are configured."""
167+
# Arrange
168+
files = ["file1.txt", "file2.txt"]
169+
context = {"new_version": "1.0.0", "new_major": "1", "current_version": "0.1.0"}
170+
git_instance.config.moveable_tags = ["v{new_major}"]
171+
git_instance.config.tag = True
172+
git_instance.config.commit = True
173+
mock_add_path = mocker.patch.object(git_instance, "add_path")
174+
mock_commit = mocker.patch.object(git_instance, "commit")
175+
mock_tag = mocker.patch("bumpversion.scm.git.tag")
176+
mock_moveable_tag = mocker.patch("bumpversion.scm.git.moveable_tag")
177+
178+
# Act
179+
git_instance.commit_and_tag(files, context, dry_run=False)
180+
181+
# Assert
182+
assert mock_add_path.call_count == len(files)
183+
mock_add_path.assert_any_call("file1.txt")
184+
mock_add_path.assert_any_call("file2.txt")
185+
mock_commit.assert_called_once_with(context)
186+
mock_tag.assert_called_once_with("v1.0.0", sign=False, message="Bump version: 0.1.0 → 1.0.0")
187+
mock_moveable_tag.assert_called_once_with("v1")
188+
189+
def test_tags_when_commit_is_false(self, git_instance: Git, mocker):
190+
"""The method only tags when commit is False."""
191+
# Arrange
192+
files = ["file1.txt"]
193+
context = {"new_version": "1.0.0", "new_major": "1", "current_version": "0.1.0"}
194+
git_instance.config.moveable_tags = ["v{new_major}"]
195+
git_instance.config.commit = False
196+
git_instance.config.tag = True
197+
mock_add_path = mocker.patch.object(git_instance, "add_path")
198+
mock_commit = mocker.patch.object(git_instance, "commit")
199+
mock_tag = mocker.patch("bumpversion.scm.git.tag")
200+
mock_moveable_tag = mocker.patch("bumpversion.scm.git.moveable_tag")
201+
202+
# Act
203+
git_instance.commit_and_tag(files, context, dry_run=False)
204+
205+
# Assert
206+
mock_add_path.assert_not_called()
207+
mock_commit.assert_not_called()
208+
mock_tag.assert_called_once_with("v1.0.0", sign=False, message="Bump version: 0.1.0 → 1.0.0")
209+
mock_moveable_tag.assert_called_once_with("v1")
210+
211+
def test_commits_when_tag_is_false(self, git_instance: Git, mocker):
212+
"""The method only commits when tag is False."""
213+
# Arrange
214+
files = ["file1.txt"]
215+
context = {"new_version": "1.0.0", "new_major": "1", "current_version": "0.1.0"}
216+
git_instance.config.moveable_tags = ["v{new_major}"]
217+
git_instance.config.tag = False
218+
git_instance.config.commit = True
219+
mock_add_path = mocker.patch.object(git_instance, "add_path")
220+
mock_commit = mocker.patch.object(git_instance, "commit")
221+
mock_tag = mocker.patch("bumpversion.scm.git.tag")
222+
mock_moveable_tag = mocker.patch("bumpversion.scm.git.moveable_tag")
223+
224+
# Act
225+
git_instance.commit_and_tag(files, context, dry_run=False)
226+
227+
mock_add_path.assert_called_with("file1.txt")
228+
mock_commit.assert_called_once_with(context)
229+
mock_tag.assert_not_called()
230+
mock_moveable_tag.assert_not_called()
231+
143232

144233
class TestRevisionInfo:
145234
"""Tests for the revision_info function."""

0 commit comments

Comments
 (0)