Title: fix(providers/uv): treat uv.lock as optional by bearomorphism · Pull Request #1979 · commitizen-tools/commitizen · GitHub
Open Graph Title: fix(providers/uv): treat uv.lock as optional by bearomorphism · Pull Request #1979 · commitizen-tools/commitizen
X Title: fix(providers/uv): treat uv.lock as optional by bearomorphism · Pull Request #1979 · commitizen-tools/commitizen
Description: Description Closes #1383. Why UvProvider.set_lock_version() unconditionally read uv.lock via self.lock_file.read_text(), so cz bump crashed with FileNotFoundError: [Errno 2] No such file or directory: 'uv.lock' whenever the lock had not been written yet. Two real-world reproducers: A freshly initialised uv project that has never had uv sync run (the original reporter's reproduction, posted in the issue thread). A uv workspace member: the lock lives at the workspace root, not next to the package's own pyproject.toml. A user reported this in a follow-up comment using uv 0.7.9 / cz 4.8.0. The maintainer (@Lee-W) confirmed in the issue thread: "hmmm... I thought we have it long ago. yep, we'll definitely need it." What changed File Change commitizen/providers/uv_provider.py set_version() now wraps the set_lock_version(version) call in if self.lock_file.is_file():, mirroring the convention used by cargo_provider.py:42 and npm_provider.py:53,62. set_lock_version() itself is unchanged so external callers that already verified the lock exists keep working. tests/providers/test_uv_provider.py New test_uv_provider_without_lock_file — creates a pyproject.toml without a sibling uv.lock, runs set_version("100.100.100"), asserts pyproject.toml was updated and no uv.lock was created. How it works The guard uses Path.is_file(), not Path.exists(). exists() is also true for directories and symlinks-to-non-files, both of which would still trip read_text(). The cargo and npm providers already make this distinction; the uv provider was the outlier. The guard is hoisted to set_version() rather than placed at the top of set_lock_version(). This matches cargo_provider.py's structure and means the lock-rewrite path can keep its precondition: "if you call set_lock_version, the lock must exist." For uv workspace members the lock is at the workspace root, not next to the package. The lock_file property here resolves to the cwd-relative uv.lock, which won't exist for a workspace member running from its own subdirectory; the is_file() guard skips the rewrite cleanly. A future PR could add proper workspace-root resolution, but that's a feature, not part of this fix — uv reconciles the lock on the next uv sync either way. TOCTOU race (lock created between is_file() and read_text()) is benign — the subsequent read_text() would simply succeed with the new content. Backward compatibility Existing path (lock present, single-package uv project) is exercised by the unchanged test_uv_provider[hyphenated|underscore] and produces byte-identical output. No public API change; set_lock_version() keeps the same signature. 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 (1 new regression test) Run uv run poe all locally to ensure this change passes linter check and tests (poe lint clean; 3/3 uv_provider tests pass — 2 existing + 1 new) Manually test the changes (see "Steps to Test" below for the issue reporter's exact reproduction script) Update the documentation for the changes (no doc change required — the lock-handling behaviour is internal) Expected Behavior Scenario Before this PR After this PR Fresh uv project, no uv.lock yet cz bump crashes with FileNotFoundError: 'uv.lock' pyproject.toml is updated, no lock written, exit 0. uv workspace member running from subdirectory Same FileNotFoundError Same — package's pyproject.toml updated, lock-rewrite skipped (workspace root will be reconciled by next uv sync). Single-package project with a populated uv.lock Both files updated Both files updated (unchanged). uv.lock is a directory or symlink to a non-file Crashes with the same error Skipped via is_file() (matches cargo/npm convention). Steps to Test This Pull Request git fetch fork fix/1383-uv-lock-optional git checkout fork/fix/1383-uv-lock-optional # 1. Targeted regression test (3 cases). uv run pytest tests/providers/test_uv_provider.py -v # expected: 3 passed # 2. Reproduce the original bug, then verify the fix (matches the issue reporter's script). mkdir -p /tmp/cz-1383 && cd /tmp/cz-1383 git init -q uv init --quiet uv run cz init --no-prompt # or follow the prompts; pick `uv` as the version provider sed -i.bak 's/pep621/uv/' pyproject.toml git add . && git -c user.email=a@b -c user.name=t commit -qm "feat: a" # On master this would now crash. On this PR it should succeed: uv run cz bump --yes test -f uv.lock && echo "lock exists" || echo "no lock — expected" grep '^version' pyproject.toml # should show the bumped version Additional Context Surfaced while triaging open issues in #1976 (round 3). Switched from Path.exists() to Path.is_file() in fixup commit f204942e after GitHub Copilot's review noted that the existing convention in cargo_provider.py:42 and npm_provider.py:53,62 is is_file() (and that exists() would still trip if the lock path happened to be a directory). The issue had been open for 11 months and was already labelled good first issue + wait-for-implementation; the previously assigned contributor confirmed they were happy for someone else to take it over.
Open Graph Description: Description Closes #1383. Why UvProvider.set_lock_version() unconditionally read uv.lock via self.lock_file.read_text(), so cz bump crashed with FileNotFoundError: [Errno 2] No such file or directo...
X Description: Description Closes #1383. Why UvProvider.set_lock_version() unconditionally read uv.lock via self.lock_file.read_text(), so cz bump crashed with FileNotFoundError: [Errno 2] No such file or directo...
Opengraph URL: https://github.com/commitizen-tools/commitizen/pull/1979
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:ffd95db7-9620-1e4e-7d27-d8ce786450a4 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | EA94:171DD4:1BE6C6:256DC3:6A525557 |
| html-safe-nonce | c7753d3a60416c0aee4df6cda596061fd6112724b475edd65b4c8a58bc4ee66a |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFQTk0OjE3MURENDoxQkU2QzY6MjU2REMzOjZBNTI1NTU3IiwidmlzaXRvcl9pZCI6IjY0MzI1NDg3NDgzNTg5OTczMzUiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | c1dfb1fe9c0ed97485bd2d925fd43edd013ace98a37bc34f725e963aac06876d |
| hovercard-subject-tag | pull_request:3654776687 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/commitizen-tools/commitizen/pull/1979/files |
| twitter:image | https://avatars.githubusercontent.com/u/26526132?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/26526132?s=400&v=4 |
| og:image:alt | Description Closes #1383. Why UvProvider.set_lock_version() unconditionally read uv.lock via self.lock_file.read_text(), so cz bump crashed with FileNotFoundError: [Errno 2] No such file or directo... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/commitizen-tools/commitizen git https://github.com/commitizen-tools/commitizen.git |
| octolytics-dimension-user_id | 62252524 |
| octolytics-dimension-user_login | commitizen-tools |
| octolytics-dimension-repository_id | 106127589 |
| octolytics-dimension-repository_nwo | commitizen-tools/commitizen |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 106127589 |
| octolytics-dimension-repository_network_root_nwo | commitizen-tools/commitizen |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7aed05249554b889eb33d002851a973eebcc7e91 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width