René's URL Explorer Experiment


Title: fix(tags): substitute ${devrelease} placeholder in tag_format by bearomorphism · Pull Request #1967 · commitizen-tools/commitizen · GitHub

Open Graph Title: fix(tags): substitute ${devrelease} placeholder in tag_format by bearomorphism · Pull Request #1967 · commitizen-tools/commitizen

X Title: fix(tags): substitute ${devrelease} placeholder in tag_format by bearomorphism · Pull Request #1967 · commitizen-tools/commitizen

Description: Description Closes #1615. Why TagRules.normalize_tag in commitizen/tags.py builds tag strings by substituting placeholders in tag_format using Python's string.Template.safe_substitute. Before this fix the substitution dictionary (lines 217–223) included version, major, minor, patch, and prerelease — but not devrelease. A tag_format that references ${devrelease} (for example ${major}.${minor}-${patch}${devrelease}) therefore left the placeholder unsubstituted, producing a literal ${devrelease} in the resulting tag name. Reported by @pydal on commitizen 4.9.1 / Python 3.9 / Linux (#1615): running cz bump --devrelease 1 --yes with tag_format = "${major}.${minor}-${patch}${devrelease}" produced tag to create: 0.0-2${devrelease} and then actually created a git tag with that verbatim string. All subsequent dev-release bumps failed with a duplicate-tag error. A triage note from the open-issues audit (2026-05-09) confirmed the bug still reproduces on master (v4.15.1) and pinpointed commitizen/tags.py:217–223 as the missing substitution. The fix adds a devrelease variable to the safe_substitute call in normalize_tag. The value is computed as f"dev{dev}" when the version carries a dev-release integer (version.dev is not None), and as the empty string otherwise — exactly mirroring the existing treatment of prerelease (version.prerelease or ""). What changed File Change commitizen/tags.py Compute devrelease from version.dev (after line 214) and add it to the safe_substitute call in normalize_tag tests/test_bump_normalize_tag.py Add three parametrised cases: dev release present (dev1), dev release at zero (dev0), and no dev release (empty string) How it works version.dev (from packaging.version.Version) is an int | None — it holds the dev-release number (e.g. 1 for 1.2.3.dev1) or None if the version is not a dev release. getattr(version, "dev", None) is used defensively so that version-scheme implementations that don't expose a dev attribute (custom schemes satisfying VersionProtocol) don't raise AttributeError. The rendered value is f"dev{dev}" (e.g. "dev1"), which matches how PEP 440 and the SemVer2 scheme stringify dev releases in version strings. For a tag_format of ${major}.${minor}-${patch}${devrelease} and version 0.0.2dev1, this produces the tag 0.0-2dev1. Why f"dev{dev}" rather than str(version.dev) or the full version string? Using the raw integer would produce 1 instead of dev1, which is not the conventional dev-release suffix. Using str(version) would embed the full version string where only the suffix is wanted. The f"dev{dev}" form gives the caller the smallest composable unit — it can be placed anywhere in tag_format without extra text leaking in. string.Template.safe_substitute (already used at commitizen/tags.py:217) leaves unrecognised placeholders unchanged rather than raising KeyError. Adding devrelease to the dict means ${devrelease} is now substituted; tag_format strings that don't reference it are completely unaffected. Dependency on #1972: the tag-parsing regex (commitizen/defaults.py::get_tag_regexes) currently requires a leading dot before dev (\.dev\d+). Tags created with this PR's substitution (e.g. 0.0-2dev1) don't round-trip through TagRules.is_version_tag / extract_version without the companion fix in #1972, which widens the regex to \.?dev\d+. Both PRs should be merged together to avoid a window in which created tags can't be parsed back. Backward compatibility tag_format strings that do not reference ${devrelease} are completely unaffected — safe_substitute ignores keys whose placeholders don't appear in the template. The prerelease substitution and all other existing template variables are unchanged. All pre-existing parametrised test cases in tests/test_bump_normalize_tag.py continue to pass. No change to CLI flags, exit codes, or any command path other than the tag-format rendering step. Checklist I have read the contributing guidelines Was generative AI tooling used to co-author this PR? Yes (please specify the tool below) Generated-by: Claude following the guidelines Code Changes Add test cases to all the changes you introduce Run uv run poe all locally to ensure this change passes linter check and tests Manually test the changes (see "Steps to Test" below) Update the documentation for the changes Expected Behavior Scenario Outcome tag_format = "${major}.${minor}-${patch}${devrelease}" and cz bump --devrelease 1 Tag created is 0.0-2dev1; literal ${devrelease} does not appear tag_format = "v$version" (no ${devrelease}) and cz bump --devrelease 1 Tag created is v0.0.2dev1 — existing behaviour unchanged tag_format = "$major.$minor.$patch$devrelease" and cz bump (no dev component) ${devrelease} renders as the empty string; tag is 0.0.2 Second cz bump --devrelease 2 after a successful first dev bump No duplicate-tag error; tag 0.0-2dev2 is created correctly Steps to Test This Pull Request git fetch fork fix/1615-tag-format-devrelease git checkout fork/fix/1615-tag-format-devrelease # 1. Targeted regression test. uv run pytest tests/test_bump_normalize_tag.py -v # 2. Reproduce the bug, then verify the fix. mkdir /tmp/cz-1615 && cd /tmp/cz-1615 git init git config user.name test && git config user.email test@example.com cat > cz.toml << 'EOF' [tool.commitizen] name = "cz_conventional_commits" tag_format = "${major}.${minor}-${patch}${devrelease}" version_scheme = "semver2" version = "0.0.1" EOF echo "# test" > README.md git add README.md cz.toml git commit -m "fix: initial" cz bump --devrelease 1 --yes git tag --list # Expected: 0.0-2dev1 # NOT: 0.0-2${devrelease} # Second dev bump must also succeed (previously failed with duplicate tag). echo "change" >> README.md git add README.md && git commit -m "fix: another change" cz bump --devrelease 2 --yes git tag --list # should contain 0.0-2dev1 and 0.0-2dev2 Additional Context This fix was identified during the open-issues audit tracked in #1964. A triage note (@bearomorphism, 2026-05-09) confirmed the bug reproduces on master (v4.15.1), pinpointed commitizen/tags.py:217–223 as the site of the missing substitution, and noted that #1614 is a companion issue where ${prerelease} has a similar (but distinct) malformed-tag problem in the same normalize_tag path. Merge dependency: this PR should land together with #1972 (fix(tags): widen prerelease and devrelease tag regexes for SemVer2). Without #1972, a tag like 0.0-2dev1 created by this fix cannot be parsed back by extract_version, breaking subsequent bumps. See PR comment for the full explanation.

Open Graph Description: Description Closes #1615. Why TagRules.normalize_tag in commitizen/tags.py builds tag strings by substituting placeholders in tag_format using Python's string.Template.safe_substitute. Before t...

X Description: Description Closes #1615. Why TagRules.normalize_tag in commitizen/tags.py builds tag strings by substituting placeholders in tag_format using Python&#39;s string.Template.safe_substitute. Befo...

Opengraph URL: https://github.com/commitizen-tools/commitizen/pull/1967

X: @github

direct link

Domain: github.com

route-pattern/:user_id/:repository/pull/:id/files(.:format)
route-controllerpull_requests
route-actionfiles
fetch-noncev2:88bb8f99-3676-49bd-8b8c-1f1511a171db
current-catalog-service-hashae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b
request-idCF18:1F1613:B519F0:103B208:6A4E47EE
html-safe-nonce7e28bb9f036bf2d7c346be890599d761fc79e06d0cef52fdc3e5d87674dd4f06
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDRjE4OjFGMTYxMzpCNTE5RjA6MTAzQjIwODo2QTRFNDdFRSIsInZpc2l0b3JfaWQiOiI2MzEzNzg3MTQxOTQ0OTU0NzAiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ==
visitor-hmac42da9167a6c6f2bb2878d246eeb62e88f490199022931bfd14e9465b5c523529
hovercard-subject-tagpull_request:3654642901
github-keyboard-shortcutsrepository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///pull_requests/show/files
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/commitizen-tools/commitizen/pull/1967/files
twitter:imagehttps://avatars.githubusercontent.com/u/26526132?s=400&v=4
twitter:cardsummary_large_image
og:imagehttps://avatars.githubusercontent.com/u/26526132?s=400&v=4
og:image:altDescription Closes #1615. Why TagRules.normalize_tag in commitizen/tags.py builds tag strings by substituting placeholders in tag_format using Python's string.Template.safe_substitute. Before t...
og:site_nameGitHub
og:typeobject
hostnamegithub.com
expected-hostnamegithub.com
None030096ee0db095447bfe77409d33bfac127ca7128299c58deef27c52eaa1b1f0
turbo-cache-controlno-preview
diff-viewunified
go-importgithub.com/commitizen-tools/commitizen git https://github.com/commitizen-tools/commitizen.git
octolytics-dimension-user_id62252524
octolytics-dimension-user_logincommitizen-tools
octolytics-dimension-repository_id106127589
octolytics-dimension-repository_nwocommitizen-tools/commitizen
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forkfalse
octolytics-dimension-repository_network_root_id106127589
octolytics-dimension-repository_network_root_nwocommitizen-tools/commitizen
turbo-body-classeslogged-out env-production page-responsive full-width
disable-turbotrue
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
releasee8506f6d0538364886e3f0153c154c410965e70d
ui-targetfull
theme-color#1e2327
color-schemelight dark

Links:

Skip to contenthttps://github.com/commitizen-tools/commitizen/pull/1967/files#start-of-content
https://github.com/
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fcommitizen-tools%2Fcommitizen%2Fpull%2F1967%2Ffiles
GitHub CopilotWrite better code with AIhttps://github.com/features/copilot
GitHub Copilot appDirect agents from issue to mergehttps://github.com/features/ai/github-app
MCP RegistryNewIntegrate external toolshttps://github.com/mcp
ActionsAutomate any workflowhttps://github.com/features/actions
CodespacesInstant dev environmentshttps://github.com/features/codespaces
IssuesPlan and track workhttps://github.com/features/issues
Code ReviewManage code changeshttps://github.com/features/code-review
GitHub Advanced SecurityFind and fix vulnerabilitieshttps://github.com/security/advanced-security
Code securitySecure your code as you buildhttps://github.com/security/advanced-security/code-security
Secret protectionStop leaks before they starthttps://github.com/security/advanced-security/secret-protection
Why GitHubhttps://github.com/why-github
Documentationhttps://docs.github.com
Bloghttps://github.blog
Changeloghttps://github.blog/changelog
Marketplacehttps://github.com/marketplace
View all featureshttps://github.com/features
Enterpriseshttps://github.com/enterprise
Small and medium teamshttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
App Modernizationhttps://github.com/solutions/use-case/app-modernization
DevSecOpshttps://github.com/solutions/use-case/devsecops
DevOpshttps://github.com/solutions/use-case/devops
CI/CDhttps://github.com/solutions/use-case/ci-cd
View all use caseshttps://github.com/solutions/use-case
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
View all industrieshttps://github.com/solutions/industry
View all solutionshttps://github.com/solutions
AIhttps://github.com/resources/articles?topic=ai
Software Developmenthttps://github.com/resources/articles?topic=software-development
DevOpshttps://github.com/resources/articles?topic=devops
Securityhttps://github.com/resources/articles?topic=security
View all topicshttps://github.com/resources/articles
Customer storieshttps://github.com/customer-stories
Events & webinarshttps://github.com/resources/events
Ebooks & reportshttps://github.com/resources/whitepapers
Business insightshttps://github.com/solutions/executive-insights
GitHub Skillshttps://skills.github.com
Documentationhttps://docs.github.com
Customer supporthttps://support.github.com
Community forumhttps://github.com/orgs/community/discussions
Trust centerhttps://github.com/trust-center
Partnershttps://github.com/partners
View all resourceshttps://github.com/resources
GitHub SponsorsFund open source developershttps://github.com/sponsors
Security Labhttps://securitylab.github.com
Maintainer Communityhttps://maintainers.github.com
Acceleratorhttps://github.com/accelerator
GitHub Starshttps://stars.github.com
Archive Programhttps://archiveprogram.github.com
Topicshttps://github.com/topics
Trendinghttps://github.com/trending
Collectionshttps://github.com/collections
Enterprise platformAI-powered developer platformhttps://github.com/enterprise
GitHub Advanced SecurityEnterprise-grade security featureshttps://github.com/security/advanced-security
Copilot for BusinessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/premium-support
Pricinghttps://github.com/pricing
Search syntax tipshttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
documentationhttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fcommitizen-tools%2Fcommitizen%2Fpull%2F1967%2Ffiles
Sign up https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fpull_requests%2Fshow%2Ffiles&source=header-repo&source_repo=commitizen-tools%2Fcommitizen
Reloadhttps://github.com/commitizen-tools/commitizen/pull/1967/files
Reloadhttps://github.com/commitizen-tools/commitizen/pull/1967/files
Reloadhttps://github.com/commitizen-tools/commitizen/pull/1967/files
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1967/files
commitizen-tools https://github.com/commitizen-tools
commitizenhttps://github.com/commitizen-tools/commitizen
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1967/files
Notifications https://github.com/login?return_to=%2Fcommitizen-tools%2Fcommitizen
Fork 344 https://github.com/login?return_to=%2Fcommitizen-tools%2Fcommitizen
Star 3.5k https://github.com/login?return_to=%2Fcommitizen-tools%2Fcommitizen
Code https://github.com/commitizen-tools/commitizen
Issues 116 https://github.com/commitizen-tools/commitizen/issues
Pull requests 45 https://github.com/commitizen-tools/commitizen/pulls
Discussions https://github.com/commitizen-tools/commitizen/discussions
Actions https://github.com/commitizen-tools/commitizen/actions
Projects https://github.com/commitizen-tools/commitizen/projects
Security and quality 0 https://github.com/commitizen-tools/commitizen/security
Insights https://github.com/commitizen-tools/commitizen/pulse
Code https://github.com/commitizen-tools/commitizen
Issues https://github.com/commitizen-tools/commitizen/issues
Pull requests https://github.com/commitizen-tools/commitizen/pulls
Discussions https://github.com/commitizen-tools/commitizen/discussions
Actions https://github.com/commitizen-tools/commitizen/actions
Projects https://github.com/commitizen-tools/commitizen/projects
Security and quality https://github.com/commitizen-tools/commitizen/security
Insights https://github.com/commitizen-tools/commitizen/pulse
Sign up for GitHub https://github.com/signup?return_to=%2Fcommitizen-tools%2Fcommitizen%2Fissues%2Fnew%2Fchoose
terms of servicehttps://docs.github.com/terms
privacy statementhttps://docs.github.com/privacy
Sign inhttps://github.com/login?return_to=%2Fcommitizen-tools%2Fcommitizen%2Fissues%2Fnew%2Fchoose
bearomorphismhttps://github.com/bearomorphism
commitizen-tools:masterhttps://github.com/commitizen-tools/commitizen/tree/master
bearomorphism:fix/1615-tag-format-devreleasehttps://github.com/bearomorphism/commitizen/tree/fix/1615-tag-format-devrelease
Conversation 5 https://github.com/commitizen-tools/commitizen/pull/1967
Commits 1 https://github.com/commitizen-tools/commitizen/pull/1967/commits
Checks 20 https://github.com/commitizen-tools/commitizen/pull/1967/checks
Files changed https://github.com/commitizen-tools/commitizen/pull/1967/files
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1967/files
fix(tags): substitute ${devrelease} placeholder in tag_format https://github.com/commitizen-tools/commitizen/pull/1967/files#top
Show all changes 1 commit https://github.com/commitizen-tools/commitizen/pull/1967/files
5269c23 fix(tags): substitute ${devrelease} placeholder in tag_format bearomorphism May 9, 2026 https://github.com/commitizen-tools/commitizen/pull/1967/commits/5269c2305051d4d37c60c2b0e4bdfad669f50a32
Clear filters https://github.com/commitizen-tools/commitizen/pull/1967/files
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1967/files
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1967/files
tags.py https://github.com/commitizen-tools/commitizen/pull/1967/files#diff-a5edd827ef040910855f0d0ab2e9bd2303e0d329b5d432b9c0045d55f1dd420d
test_bump_normalize_tag.py https://github.com/commitizen-tools/commitizen/pull/1967/files#diff-18e73e02dff29701abee8c281d4ab534ad29e2ada84c5368bddff72af8d71d78
commitizen/tags.pyhttps://github.com/commitizen-tools/commitizen/pull/1967/files#diff-a5edd827ef040910855f0d0ab2e9bd2303e0d329b5d432b9c0045d55f1dd420d
View file https://github.com/commitizen-tools/commitizen/blob/5269c2305051d4d37c60c2b0e4bdfad669f50a32/commitizen/tags.py
Open in desktop https://desktop.github.com
https://github.co/hiddenchars
https://github.com/commitizen-tools/commitizen/pull/1967/{{ revealButtonHref }}
https://github.com/commitizen-tools/commitizen/pull/1967/files#diff-a5edd827ef040910855f0d0ab2e9bd2303e0d329b5d432b9c0045d55f1dd420d
bearomorphismhttps://github.com/bearomorphism
May 9, 2026https://github.com/commitizen-tools/commitizen/pull/1967/files#r3213267772
Learn morehttps://docs.github.com/articles/managing-disruptive-comments/#hiding-a-comment
#1972https://github.com/commitizen-tools/commitizen/pull/1972
#1972https://github.com/commitizen-tools/commitizen/pull/1972
#1972https://github.com/commitizen-tools/commitizen/pull/1972
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1967/files
https://github.com/commitizen-tools/commitizen/pull/1967/files#diff-a5edd827ef040910855f0d0ab2e9bd2303e0d329b5d432b9c0045d55f1dd420d
https://github.com/commitizen-tools/commitizen/pull/1967/files#diff-a5edd827ef040910855f0d0ab2e9bd2303e0d329b5d432b9c0045d55f1dd420d
tests/test_bump_normalize_tag.pyhttps://github.com/commitizen-tools/commitizen/pull/1967/files#diff-18e73e02dff29701abee8c281d4ab534ad29e2ada84c5368bddff72af8d71d78
View file https://github.com/commitizen-tools/commitizen/blob/5269c2305051d4d37c60c2b0e4bdfad669f50a32/tests/test_bump_normalize_tag.py
Open in desktop https://desktop.github.com
https://github.co/hiddenchars
https://github.com/commitizen-tools/commitizen/pull/1967/{{ revealButtonHref }}
https://github.com/commitizen-tools/commitizen/pull/1967/files#diff-18e73e02dff29701abee8c281d4ab534ad29e2ada84c5368bddff72af8d71d78
https://github.com/commitizen-tools/commitizen/pull/1967/files#diff-18e73e02dff29701abee8c281d4ab534ad29e2ada84c5368bddff72af8d71d78
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1967/files
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.