Skip to content

Commit 008c616

Browse files
committed
fix: accept uppercase V prefix in --tag
Fold a leading uppercase `V` (a common paste) to the canonical lowercase `v` before validating `--tag`. The remainder of the tag stays case-sensitive on purpose: the validated value is used verbatim as a git ref, which is case-sensitive on GitHub, so rewriting label/build-metadata casing could point at a tag that does not exist. Adds a normalization test.
1 parent 05d00ea commit 008c616

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

src/specify_cli/_version.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,20 @@ def _validate_tag(tag: str) -> str:
305305
306306
Accepts vX.Y.Z plus an optional dev or alpha/beta/rc suffix and/or an
307307
optional build-metadata suffix, which may combine (for example:
308-
v1.0.0-rc1, v0.8.0.dev0, v0.8.0+build.42, v1.0.0-rc1+build.42). Rejects
309-
everything else, including bare 'latest', hash refs, branch names, and
310-
numeric versions without the 'v' prefix.
308+
v1.0.0-rc1, v0.8.0.dev0, v0.8.0+build.42, v1.0.0-rc1+build.42). An
309+
uppercase ``V`` prefix is accepted and folded to the canonical lowercase
310+
``v``. Rejects everything else, including bare 'latest', hash refs, branch
311+
names, and numeric versions without the 'v' prefix.
311312
"""
312313
tag = tag.strip()
313314
if not tag:
314315
raise typer.BadParameter(_INVALID_TAG_MESSAGE)
316+
# Fold a leading uppercase `V` (a common paste) to the canonical lowercase
317+
# `v`. The remainder stays case-sensitive on purpose: the validated tag is
318+
# used verbatim as a git ref, which is case-sensitive on GitHub, so we must
319+
# not rewrite label/build-metadata casing into a ref that may not exist.
320+
if tag[:1] == "V":
321+
tag = "v" + tag[1:]
315322
if not _TAG_REGEX.match(tag):
316323
raise typer.BadParameter(_INVALID_TAG_MESSAGE)
317324
try:

tests/test_self_upgrade_verification.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,21 @@ def test_valid_build_metadata_tag(self, uv_tool_argv0, clean_environ):
421421
assert result.exit_code == 0
422422
assert "Target version: v0.8.0+build.42" in strip_ansi(result.output)
423423

424+
def test_uppercase_v_prefix_is_folded_to_lowercase(
425+
self, uv_tool_argv0, clean_environ
426+
):
427+
# A pasted uppercase `V` prefix is accepted and normalized to `v` so
428+
# the git ref matches the canonical lowercase release tag.
429+
with patch("specify_cli._version.shutil.which", return_value="uv"), patch(
430+
"specify_cli._version._get_installed_version", return_value="0.7.5"
431+
):
432+
result = runner.invoke(
433+
app,
434+
["self", "upgrade", "--dry-run", "--tag", "V0.7.6"],
435+
)
436+
assert result.exit_code == 0
437+
assert "Target version: v0.7.6" in strip_ansi(result.output)
438+
424439
def test_valid_prerelease_with_build_metadata_tag(
425440
self, uv_tool_argv0, clean_environ
426441
):

0 commit comments

Comments
 (0)