Skip to content

Commit c025650

Browse files
committed
Fixes issue of duplicate tags
- Now it checks if the tag exists and reports a warning
1 parent 19f13b7 commit c025650

File tree

3 files changed

+49
-3
lines changed

3 files changed

+49
-3
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ commits and tags:
4646
- https://github.com/c4urself/bump2version/issues/246 Release inconsistency
4747
- https://github.com/c4urself/bump2version/issues/233 How are relative configured file paths resolved?
4848
- https://github.com/c4urself/bump2version/issues/225 Properly resolve configuration file through parent directories when in a git or mercurial repo
49-
- https://github.com/c4urself/bump2version/issues/224 Verify tag doesn't exist
49+
- Fixed: https://github.com/c4urself/bump2version/issues/224 Verify tag doesn't exist
5050

5151
**Documentation opportunities**
5252

bumpversion/scm.py

+18
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class SourceCodeManager:
3232

3333
_TEST_USABLE_COMMAND: List[str] = []
3434
_COMMIT_COMMAND: List[str] = []
35+
_ALL_TAGS_COMMAND: List[str] = []
3536

3637
@classmethod
3738
def commit(cls, message: str, current_version: str, new_version: str, extra_args: Optional[list] = None) -> None:
@@ -84,6 +85,15 @@ def tag(cls, name: str, sign: bool = False, message: Optional[str] = None) -> No
8485
"""Create a tag of the new_version in VCS."""
8586
raise NotImplementedError
8687

88+
@classmethod
89+
def get_all_tags(cls) -> List[str]:
90+
"""Return all tags in VCS."""
91+
try:
92+
result = subprocess.run(cls._ALL_TAGS_COMMAND, text=True, check=True, capture_output=True)
93+
return result.stdout.splitlines()
94+
except (FileNotFoundError, PermissionError, NotADirectoryError, subprocess.CalledProcessError):
95+
return []
96+
8797
@classmethod
8898
def commit_to_scm(
8999
cls,
@@ -135,7 +145,13 @@ def tag_in_scm(cls, config: "Config", context: MutableMapping, dry_run: bool = F
135145
sign_tags = config.sign_tags
136146
tag_name = config.tag_name.format(**context)
137147
tag_message = config.tag_message.format(**context)
148+
existing_tags = cls.get_all_tags()
138149
do_tag = config.tag and not dry_run
150+
151+
if tag_name in existing_tags and config.tag:
152+
logger.warning("Tag '%s' already exists. Will not tag.", tag_name)
153+
return
154+
139155
logger.info(
140156
"%s '%s' %s in %s and %s",
141157
"Tagging" if do_tag else "Would tag",
@@ -153,6 +169,7 @@ class Git(SourceCodeManager):
153169

154170
_TEST_USABLE_COMMAND = ["git", "rev-parse", "--git-dir"]
155171
_COMMIT_COMMAND = ["git", "commit", "-F"]
172+
_ALL_TAGS_COMMAND = ["git", "tag", "--list"]
156173

157174
@classmethod
158175
def assert_nondirty(cls) -> None:
@@ -241,6 +258,7 @@ class Mercurial(SourceCodeManager):
241258

242259
_TEST_USABLE_COMMAND = ["hg", "root"]
243260
_COMMIT_COMMAND = ["hg", "commit", "--logfile"]
261+
_ALL_TAGS_COMMAND = ["hg", "log", '--rev="tag()"', '--template="{tags}\n"']
244262

245263
@classmethod
246264
def latest_tag_info(cls, tag_pattern: str) -> SCMInfo:

tests/test_scm.py

+30-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
from pathlib import Path
44

55
import pytest
6-
from pytest import param
6+
from pytest import param, LogCaptureFixture
77

88
from bumpversion import scm
99
from bumpversion.exceptions import DirtyWorkingDirectoryError
10+
from bumpversion.logging import setup_logging
1011
from tests.conftest import get_config_data, inside_dir
1112

1213

@@ -42,7 +43,7 @@ def test_git_latest_tag_info(git_repo: Path) -> None:
4243
"""Should return information about the latest tag."""
4344
readme = git_repo.joinpath("readme.md")
4445
readme.touch()
45-
# with pytest.raises(DirtyWorkingDirectoryError):
46+
4647
with inside_dir(git_repo):
4748
assert scm.Git.latest_tag_info("v*") == scm.SCMInfo(tool=scm.Git)
4849

@@ -65,6 +66,33 @@ def test_git_latest_tag_info(git_repo: Path) -> None:
6566
assert tag_info.dirty is True
6667

6768

69+
def test_git_detects_existing_tag(git_repo: Path, caplog: LogCaptureFixture) -> None:
70+
"""Attempting to tag when a tag exists will do nothing."""
71+
# Arrange
72+
git_repo.joinpath("readme.md").touch()
73+
git_repo.joinpath("something.md").touch()
74+
overrides = {"current_version": "0.1.0", "commit": True, "tag": True}
75+
context = {
76+
"current_version": "0.1.0",
77+
"new_version": "0.2.0",
78+
}
79+
setup_logging(2)
80+
with inside_dir(git_repo):
81+
conf, version_config, current_version = get_config_data(overrides)
82+
subprocess.run(["git", "add", "readme.md"])
83+
subprocess.run(["git", "commit", "-m", "first"])
84+
subprocess.run(["git", "tag", "v0.1.0"])
85+
subprocess.run(["git", "add", "something.md"])
86+
subprocess.run(["git", "commit", "-m", "second"])
87+
subprocess.run(["git", "tag", "v0.2.0"])
88+
89+
# Act
90+
scm.Git.tag_in_scm(config=conf, context=context)
91+
92+
# Assert
93+
assert "Will not tag" in caplog.text
94+
95+
6896
def test_hg_is_not_usable(tmp_path: Path) -> None:
6997
"""Should return false if it is not a mercurial repo."""
7098
with inside_dir(tmp_path):

0 commit comments

Comments
 (0)