Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-poetry for openSUSE:Factory checked in at 2026-03-31 15:23:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-poetry (Old) and /work/SRC/openSUSE:Factory/.python-poetry.new.1999 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-poetry" Tue Mar 31 15:23:11 2026 rev:41 rq:1343788 version:2.3.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-poetry/python-poetry.changes 2026-02-12 17:29:44.825347730 +0100 +++ /work/SRC/openSUSE:Factory/.python-poetry.new.1999/python-poetry.changes 2026-03-31 15:24:24.517617841 +0200 @@ -1,0 +2,33 @@ +Mon Mar 30 20:35:42 UTC 2026 - Dirk Müller <[email protected]> + +- update to 2.3.3: + * **Fix a path traversal vulnerability in the wheel installer + that could allow malicious wheel files to write files outside + the intended installation directory** (#10792). + * Fix an issue where `git` dependencies from annotated tags + could not be updated (#10719). + * Fix an issue where empty `VIRTUAL_ENV` or `CONDA_PREFIX` + environment variables (e.g., after `conda deactivate`) would + cause Poetry to incorrectly detect an active virtualenv + (#10784). + * Fix an issue where an incomprehensible error message was + printed when `.venv` was a file instead of a directory + (#10777). + * Fix an issue where HTTP Basic Authentication credentials + could be corrupted during request preparation, causing + authentication failures with long tokens (#10748). + * Fix an issue where `poetry publish --no-interaction --build` + requested user interaction (#10769). + * Fix an issue where `poetry init` and `poetry new` created a + deprecated `project.license` format (#10787). + * Clarify the differences between `poetry install` and `poetry + update` (#10713). + * Clarify the section of fields in the `pyproject.toml` + examples (#10753). + * Add a note about the different installation location when + Python from the Microsoft Store is used (#10759). + * Fix the system requirements for Poetry (#10739). + * Fix the `poetry cache clear` example (#10749). + * Fix the link to `pipx` installation instructions (#10783). + +------------------------------------------------------------------- Old: ---- poetry-2.3.2.tar.gz New: ---- poetry-2.3.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-poetry.spec ++++++ --- /var/tmp/diff_new_pack.FeR4M6/_old 2026-03-31 15:24:25.109642504 +0200 +++ /var/tmp/diff_new_pack.FeR4M6/_new 2026-03-31 15:24:25.113642671 +0200 @@ -27,7 +27,7 @@ %{?sle15_python_module_pythons} Name: python-poetry%{psuffix} -Version: 2.3.2 +Version: 2.3.3 Release: 0 Summary: Python dependency management and packaging License: MIT @@ -39,14 +39,14 @@ BuildRequires: %{python_module poetry-core >= 2} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-poetry-core = 2.3.1 +Requires: python-poetry-core = 2.3.2 Requires: (python-build >= 1.2.1 with python-build < 2.0.0) Requires: (python-cachecontrol >= 0.14.0 with python-cachecontrol < 0.15.0) # from cachecontrol[filecache] Requires: python-filelock >= 3.8.0 # /cachecontrol[filecache] Requires: (python-cleo >= 2.1.0 with python-cleo < 3.0.0) -Requires: python-packaging >= 24 +Requires: python-packaging >= 24.2 Requires: python-pbs-installer >= 2025.6.10 Requires: python-trove-classifiers >= 2022.5.19 Requires: python-virtualenv >= 20.26.6 ++++++ poetry-2.3.2.tar.gz -> poetry-2.3.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/CHANGELOG.md new/poetry-2.3.3/CHANGELOG.md --- old/poetry-2.3.2/CHANGELOG.md 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/CHANGELOG.md 2026-03-29 14:13:27.000000000 +0200 @@ -1,5 +1,40 @@ # Change Log +## [2.3.3] - 2026-03-29 + +### Fixed + +- **Fix a path traversal vulnerability in the wheel installer that could allow malicious wheel files to write files outside the intended installation directory** ([#10792](https://github.com/python-poetry/poetry/pull/10792)). +- Fix an issue where `git` dependencies from annotated tags could not be updated ([#10719](https://github.com/python-poetry/poetry/pull/10719)). +- Fix an issue where empty `VIRTUAL_ENV` or `CONDA_PREFIX` environment variables (e.g., after `conda deactivate`) would cause Poetry to incorrectly detect an active virtualenv ([#10784](https://github.com/python-poetry/poetry/pull/10784)). +- Fix an issue where an incomprehensible error message was printed when `.venv` was a file instead of a directory ([#10777](https://github.com/python-poetry/poetry/pull/10777)). +- Fix an issue where HTTP Basic Authentication credentials could be corrupted during request preparation, causing authentication failures with long tokens ([#10748](https://github.com/python-poetry/poetry/pull/10748)). +- Fix an issue where `poetry publish --no-interaction --build` requested user interaction ([#10769](https://github.com/python-poetry/poetry/pull/10769)). +- Fix an issue where `poetry init` and `poetry new` created a deprecated `project.license` format ([#10787](https://github.com/python-poetry/poetry/pull/10787)). + +### Docs + +- Clarify the differences between `poetry install` and `poetry update` ([#10713](https://github.com/python-poetry/poetry/pull/10713)). +- Clarify the section of fields in the `pyproject.toml` examples ([#10753](https://github.com/python-poetry/poetry/pull/10753)). +- Add a note about the different installation location when Python from the Microsoft Store is used ([#10759](https://github.com/python-poetry/poetry/pull/10759)). +- Fix the system requirements for Poetry ([#10739](https://github.com/python-poetry/poetry/pull/10739)). +- Fix the `poetry cache clear` example ([#10749](https://github.com/python-poetry/poetry/pull/10749)). +- Fix the link to `pipx` installation instructions ([#10783](https://github.com/python-poetry/poetry/pull/10783)). + +### poetry-core ([`2.3.2`](https://github.com/python-poetry/poetry-core/releases/tag/2.3.2)) + +- Fix an issue where `platform_release` could not be parsed on Debian Trixie ([#930](https://github.com/python-poetry/poetry-core/pull/930)). +- Fix an issue where using `project.readme.text` in the `pyproject.toml` file resulted in broken metadata ([#914](https://github.com/python-poetry/poetry-core/pull/914)). +- Fix an issue where dependency groups were considered equal when their resolved dependencies were equal, even if the groups themselves were not ([#919](https://github.com/python-poetry/poetry-core/pull/919)). +- Fix an issue where removing a dependency from a group that included another group resulted in other dependencies being added to the included group ([#922](https://github.com/python-poetry/poetry-core/pull/922)). +- Fix an issue where PEP 735 `include-group` entries were lost when `[tool.poetry.group]` also defined `include-groups` for the same group ([#924](https://github.com/python-poetry/poetry-core/pull/924)). +- Fix an issue where the union of `<value> not in <marker>` constraints was wrongly treated as always satisfied ([#925](https://github.com/python-poetry/poetry-core/pull/925)). +- Fix an issue where a post release with a local version identifier was wrongly allowed by a `>` version constraint ([#921](https://github.com/python-poetry/poetry-core/pull/921)). +- Fix an issue where a version with the local version identifier `0` was treated as equal to the corresponding public version ([#920](https://github.com/python-poetry/poetry-core/pull/920)). +- Fix an issue where a `!= <version>` constraint wrongly disallowed pre releases and post releases of the specified version ([#929](https://github.com/python-poetry/poetry-core/pull/929)). +- Fix an issue where `in` and `not in` constraints were wrongly not allowed by specific compound constraints ([#927](https://github.com/python-poetry/poetry-core/pull/927)). + + ## [2.3.2] - 2026-02-01 ### Changed @@ -2658,7 +2693,8 @@ -[Unreleased]: https://github.com/python-poetry/poetry/compare/2.3.2...main +[Unreleased]: https://github.com/python-poetry/poetry/compare/2.3.3...main +[2.3.3]: https://github.com/python-poetry/poetry/releases/tag/2.3.3 [2.3.2]: https://github.com/python-poetry/poetry/releases/tag/2.3.2 [2.3.1]: https://github.com/python-poetry/poetry/releases/tag/2.3.1 [2.3.0]: https://github.com/python-poetry/poetry/releases/tag/2.3.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/docs/_index.md new/poetry-2.3.3/docs/_index.md --- old/poetry-2.3.2/docs/_index.md 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/docs/_index.md 2026-03-29 14:13:27.000000000 +0200 @@ -18,7 +18,7 @@ ## System requirements -Poetry requires **Python 3.9+**. It is multi-platform and the goal is to make it work equally well +Poetry requires **Python 3.10+**. It is multi-platform and the goal is to make it work equally well on Linux, macOS and Windows. ## Installation @@ -40,7 +40,7 @@ **Install pipx** If `pipx` is not already installed, you can follow any of the options in the -[official pipx installation instructions](https://pipx.pypa.io/stable/installation/). +[official pipx installation instructions](https://pipx.pypa.io/stable/how-to/install-pipx/). Any non-ancient version of `pipx` will do. {{< /step >}} @@ -190,6 +190,18 @@ - `%APPDATA%\Python\Scripts` on Windows. - `$POETRY_HOME/bin` if `$POETRY_HOME` is set. +{{% note %}} +If you have installed Python through the Microsoft Store, the `poetry` binary +will be installed to a different location, for example: + +``` +%LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0 +\LocalCache\Roaming\Python\Scripts +``` + +Replace `3.12` with your installed Python version and `qbz5n2kfra8p0` with your suffix. +{{% /note %}} + If this directory is not present in your `$PATH`, you can add it in order to invoke Poetry as `poetry`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/docs/cli.md new/poetry-2.3.3/docs/cli.md --- old/poetry-2.3.2/docs/cli.md 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/docs/cli.md 2026-03-29 14:13:27.000000000 +0200 @@ -263,7 +263,7 @@ To only remove a specific package from a cache, you have to specify the cache entry in the following form `cache:package:version`: ```bash -poetry cache clear pypi:requests:2.24.0 +poetry cache clear PyPI:requests:2.24.0 ``` ### cache list @@ -475,14 +475,6 @@ The `install` command reads the `pyproject.toml` file from the current project, resolves the dependencies, and installs them. -{{% note %}} -Normally, you should prefer `poetry sync` to `poetry install` to avoid untracked outdated packages. -However, if you have set `virtualenvs.create = false` to install dependencies into your system environment, -which is discouraged, or `virtualenvs.options.system-site-packages = true` to make -system site-packages available in your virtual environment, you should use `poetry install` -because `poetry sync` will normally not work well in these cases. -{{% /note %}} - ```bash poetry install ``` @@ -493,6 +485,23 @@ If there is no `poetry.lock` file, Poetry will create one after dependency resolution. +{{% note %}} +**When to use `install` vs `update`:** +- Use `poetry install` to install dependencies as specified in `poetry.lock` (or resolve dependencies and create the lock file if it is missing). + This is what you run after cloning a project. For reproducible installs, prefer `poetry sync`, + which also removes packages that are not in the lock file. +- Use `poetry update` when you want to update dependencies to their latest versions (within the constraints from the `pyproject.toml`) + and refresh `poetry.lock`. +{{% /note %}} + +{{% note %}} +Normally, you should prefer `poetry sync` to `poetry install` to avoid untracked outdated packages. +However, if you have set `virtualenvs.create = false` to install dependencies into your system environment, +which is discouraged, or `virtualenvs.options.system-site-packages = true` to make +system site-packages available in your virtual environment, you should use `poetry install` +because `poetry sync` will normally not work well in these cases. +{{% /note %}} + If you want to exclude one or more dependency groups for the installation, you can use the `--without` option. @@ -1276,7 +1285,14 @@ poetry update ``` -This will resolve all dependencies of the project and write the exact versions into `poetry.lock`. +This will resolve all dependencies of the project, write the exact versions into `poetry.lock`, +and install them into your environment. + +{{% note %}} +The `update` command **does not** modify your `pyproject.toml` file. It only updates the +`poetry.lock` file with the latest compatible versions based on the constraints already +defined in `pyproject.toml`. To change version constraints, use the `add` command instead. +{{% /note %}} If you just want to update a few packages and not all, you can list them as such: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/docs/pyproject.md new/poetry-2.3.3/docs/pyproject.md --- old/poetry-2.3.2/docs/pyproject.md 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/docs/pyproject.md 2026-03-29 14:13:27.000000000 +0200 @@ -35,6 +35,7 @@ ```toml +[project] name = "my-package" ``` @@ -45,6 +46,8 @@ This should be a valid [PEP 440](https://peps.python.org/pep-0440/) string. ```toml +[project] +# ... version = "0.1.0" ``` @@ -66,6 +69,8 @@ A short description of the package. ```toml +[project] +# ... description = "A short description of the package." ``` @@ -94,6 +99,8 @@ More identifiers are listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/). ```toml +[project] +# ... license = "MIT" ``` @@ -136,6 +143,8 @@ A path to the README file or the content. ```toml +[project] +# ... readme = "README.md" ``` @@ -159,6 +168,8 @@ The Python version requirements of the project. ```toml +[project] +# ... requires-python = ">=3.8" ``` @@ -184,6 +195,8 @@ This is a list of authors and should contain at least one author. ```toml +[project] +# ... authors = [ { name = "Sébastien Eustace", email = "[email protected]" }, ] @@ -196,6 +209,8 @@ This is a list of maintainers and should be distinct from authors. ```toml +[project] +# ... maintainers = [ { name = "John Smith", email = "[email protected]" }, { name = "Jane Smith", email = "[email protected]" }, @@ -207,6 +222,8 @@ A list of keywords that the package is related to. ```toml +[project] +# ... keywords = [ "packaging", "poetry" ] ``` @@ -215,6 +232,8 @@ A list of PyPI [trove classifiers](https://pypi.org/classifiers/) that describe the project. ```toml +[project] +# ... classifiers = [ "Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Libraries :: Python Modules" @@ -326,6 +345,8 @@ The `dependencies` of the project. ```toml +[project] +# ... dependencies = [ "requests>=2.13.0", ] @@ -366,6 +387,8 @@ See [basic usage]({{< relref "basic-usage#operating-modes" >}}) for more information. ```toml +[tool.poetry] +# ... package-mode = false ``` @@ -379,6 +402,7 @@ ```toml +[tool.poetry] name = "my-package" ``` @@ -395,6 +419,8 @@ This should be a valid [PEP 440](https://peps.python.org/pep-0440/) string. ```toml +[tool.poetry] +# ... version = "0.1.0" ``` @@ -412,6 +438,8 @@ A short description of the package. ```toml +[tool.poetry] +# ... description = "A short description of the package." ``` @@ -441,6 +469,8 @@ More identifiers are listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/). ```toml +[tool.poetry] +# ... license = "MIT" ``` @@ -453,6 +483,8 @@ This is a list of authors and should contain at least one author. Authors must be in the form `name <email>`. ```toml +[tool.poetry] +# ... authors = [ "Sébastien Eustace <[email protected]>", ] @@ -467,6 +499,8 @@ This is a list of maintainers and should be distinct from authors. Maintainers may contain an email and be in the form `name <email>`. ```toml +[tool.poetry] +# ... maintainers = [ "John Smith <[email protected]>", "Jane Smith <[email protected]>", @@ -513,9 +547,11 @@ **Deprecated**: Use `project.urls` instead. -An URL to the website of the project. +A URL to the website of the project. ```toml +[tool.poetry] +# ... homepage = "https://python-poetry.org/" ``` @@ -523,9 +559,11 @@ **Deprecated**: Use `project.urls` instead. -An URL to the repository of the project. +A URL to the repository of the project. ```toml +[tool.poetry] +# ... repository = "https://github.com/python-poetry/poetry" ``` @@ -533,9 +571,11 @@ **Deprecated**: Use `project.urls` instead. -An URL to the documentation of the project. +A URL to the documentation of the project. ```toml +[tool.poetry] +# ... documentation = "https://python-poetry.org/docs/" ``` @@ -546,6 +586,8 @@ A list of keywords that the package is related to. ```toml +[tool.poetry] +# ... keywords = ["packaging", "poetry"] ``` @@ -642,6 +684,8 @@ another package named `extra_package`, you will need to specify `my_package` explicitly: ```toml +[tool.poetry] +# ... packages = [ { include = "my_package" }, { include = "extra_package" }, @@ -921,6 +965,7 @@ ```toml [tool.poetry] +# ... requires-poetry = ">=2.0" ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/poetry.lock new/poetry-2.3.3/poetry.lock --- old/poetry-2.3.2/poetry.lock 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/poetry.lock 2026-03-29 14:13:27.000000000 +0200 @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "anyio" @@ -1347,14 +1347,14 @@ [[package]] name = "poetry-core" -version = "2.3.1" +version = "2.3.2" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "poetry_core-2.3.1-py3-none-any.whl", hash = "sha256:db1cf63b782570deb38bfba61e2304a553eef0740dc17959a50cc0f5115ee634"}, - {file = "poetry_core-2.3.1.tar.gz", hash = "sha256:96f791d5d7d4e040f3983d76779425cf9532690e2756a24fd5ca0f86af19ef82"}, + {file = "poetry_core-2.3.2-py3-none-any.whl", hash = "sha256:23df641b64f87fbb4ce1873c1915a4d4bb1b7d808c596e4307edc073e68d7234"}, + {file = "poetry_core-2.3.2.tar.gz", hash = "sha256:20cb71be27b774628da9f384effd9183dfceb53bcef84063248a8672aa47031f"}, ] [[package]] @@ -2196,4 +2196,4 @@ [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "9ce6acbe11341f1c5fc0a8df9bfe5fe9237fedb54c90a756baf344b1c2b62e43" +content-hash = "69cddd52ffdedc12c4b7445a9f359d67a4a1dc4b5d139d261a2c228a832bf39a" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/pyproject.toml new/poetry-2.3.3/pyproject.toml --- old/poetry-2.3.2/pyproject.toml 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/pyproject.toml 2026-03-29 14:13:27.000000000 +0200 @@ -1,10 +1,10 @@ [project] name = "poetry" -version = "2.3.2" +version = "2.3.3" description = "Python dependency management and packaging made easy." requires-python = ">=3.10,<4.0" dependencies = [ - "poetry-core (==2.3.1)", + "poetry-core (==2.3.2)", "build (>=1.2.1,<2.0.0)", "cachecontrol[filecache] (>=0.14.0,<0.15.0)", "cleo (>=2.1.0,<3.0.0)", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/console/commands/env/remove.py new/poetry-2.3.3/src/poetry/console/commands/env/remove.py --- old/poetry-2.3.2/src/poetry/console/commands/env/remove.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/console/commands/env/remove.py 2026-03-29 14:13:27.000000000 +0200 @@ -7,6 +7,7 @@ from cleo.helpers import option from poetry.console.commands.command import Command +from poetry.utils._compat import is_relative_to if TYPE_CHECKING: @@ -54,8 +55,8 @@ self.line(f"Deleted virtualenv: <comment>{venv.path}</comment>") if remove_all_envs or is_in_project: for venv in manager.list(): - if not is_in_project or venv.path.is_relative_to( - self.poetry.pyproject_path.parent + if not is_in_project or is_relative_to( + venv.path, self.poetry.pyproject_path.parent ): manager.remove_venv(venv.path) self.line(f"Deleted virtualenv: <comment>{venv.path}</comment>") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/console/commands/publish.py new/poetry-2.3.3/src/poetry/console/commands/publish.py --- old/poetry-2.3.2/src/poetry/console/commands/publish.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/console/commands/publish.py 2026-03-29 14:13:27.000000000 +0200 @@ -72,13 +72,21 @@ # Building package first, if told if self.option("build"): - if publisher.files and not self.confirm( - f"There are <info>{len(publisher.files)}</info> files ready for" - " publishing. Build anyway?" - ): - self.line_error("<error>Aborted!</error>") + if publisher.files: + if self.io.is_interactive(): + if not self.confirm( + f"There are <info>{len(publisher.files)}</info> files ready for" + f" publishing in {dist_dir}. Build anyway?" + ): + self.line_error("<error>Aborted!</error>") - return 1 + return 1 + + else: + self.line( + f"<warning>Warning: There are <info>{len(publisher.files)}</info> files " + f"ready for publishing in {dist_dir}. Build anyway!</warning>" + ) self.call("build", args=f"--output {dist_dir}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/console/commands/python/list.py new/poetry-2.3.3/src/poetry/console/commands/python/list.py --- old/poetry-2.3.2/src/poetry/console/commands/python/list.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/console/commands/python/list.py 2026-03-29 14:13:27.000000000 +0200 @@ -10,6 +10,7 @@ from poetry.config.config import Config from poetry.console.commands.command import Command +from poetry.utils._compat import is_relative_to from poetry.utils.env.python import Python @@ -107,9 +108,8 @@ implementation = implementations.get( pv.implementation.lower(), pv.implementation ) - is_poetry_managed = ( - pv.executable is None - or pv.executable.resolve().is_relative_to(python_installation_path) + is_poetry_managed = pv.executable is None or is_relative_to( + pv.executable.resolve(), python_installation_path ) if self.option("managed") and not is_poetry_managed: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/installation/wheel_installer.py new/poetry-2.3.3/src/poetry/installation/wheel_installer.py --- old/poetry-2.3.2/src/poetry/installation/wheel_installer.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/installation/wheel_installer.py 2026-03-29 14:13:27.000000000 +0200 @@ -14,6 +14,7 @@ from poetry.__version__ import __version__ from poetry.utils._compat import WINDOWS +from poetry.utils._compat import is_relative_to logger = logging.getLogger(__name__) @@ -44,7 +45,20 @@ from installer.utils import copyfileobj_with_hashing from installer.utils import make_file_executable - target_path = Path(self.scheme_dict[scheme]) / path + target_dir = Path(self.scheme_dict[scheme]).resolve() + target_path = (target_dir / path).resolve() + + # Use is_relative_to() instead of Path.is_relative_to() + # because the latter does not work if one of both paths + # has a Windows long path prefix and the other path has not. + # (A long path prefix may be added when calling resolve().) + if not is_relative_to(target_path, target_dir): + raise ValueError( + f"Attempting to write {path} outside of the target directory\n" + f"Target directory: {target_dir}\n" + f"Target path: {target_path}" + ) + if target_path.exists(): # Contrary to the base library we don't raise an error here since it can # break pkgutil-style and pkg_resource-style namespace packages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/layouts/layout.py new/poetry-2.3.3/src/poetry/layouts/layout.py --- old/poetry-2.3.2/src/poetry/layouts/layout.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/layouts/layout.py 2026-03-29 14:13:27.000000000 +0200 @@ -32,7 +32,7 @@ description = "" authors = [ ] -license = {} +license = "" readme = "" requires-python = "" dependencies = [ @@ -158,7 +158,7 @@ project_content["authors"].append(author) if self._license: - project_content["license"]["text"] = self._license + project_content["license"] = self._license else: project_content.remove("license") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/utils/_compat.py new/poetry-2.3.3/src/poetry/utils/_compat.py --- old/poetry-2.3.2/src/poetry/utils/_compat.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/utils/_compat.py 2026-03-29 14:13:27.000000000 +0200 @@ -5,6 +5,7 @@ import warnings from contextlib import suppress +from pathlib import Path if sys.version_info < (3, 11): @@ -52,6 +53,33 @@ return locale.getencoding() +def is_relative_to(path1: Path, path2: Path) -> bool: + """ + Checks if path1 is relative to path2. + + Works also if one of both paths has a Windows long path prefix. + A long path prefix may be added when calling Path.resolve(). + """ + if WINDOWS: + # Work around an issue that is_relative_to() does not work if + # one of both paths has a long path prefix and the other path has not. + long_path_prefix = "\\\\?\\" + long_path_unc_prefix = f"{long_path_prefix}UNC\\" + + def remove_long_path_prefix(path: Path) -> Path: + if (path_str := str(path)).startswith(long_path_prefix): + if path_str.startswith(long_path_unc_prefix): + path = Path("\\\\" + path_str.removeprefix(long_path_unc_prefix)) + else: + path = Path(path_str.removeprefix(long_path_prefix)) + return path + + path1 = remove_long_path_prefix(path1) + path2 = remove_long_path_prefix(path2) + + return path1.is_relative_to(path2) + + def __getattr__(name: str) -> object: if name == "metadata": warnings.warn( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/utils/authenticator.py new/poetry-2.3.3/src/poetry/utils/authenticator.py --- old/poetry-2.3.2/src/poetry/utils/authenticator.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/utils/authenticator.py 2026-03-29 14:13:27.000000000 +0200 @@ -189,13 +189,15 @@ self, method: str, url: str, raise_for_status: bool = True, **kwargs: Any ) -> requests.Response: headers = kwargs.get("headers") - request = requests.Request(method, url, headers=headers) credential = self.get_credentials_for_url(url) + auth = None if credential.username is not None or credential.password is not None: - request = requests.auth.HTTPBasicAuth( + auth = requests.auth.HTTPBasicAuth( credential.username or "", credential.password or "" - )(request) + ) + + request = requests.Request(method, url, headers=headers, auth=auth) session = self.get_session(url=url) prepared_request = session.prepare_request(request) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/utils/env/env_manager.py new/poetry-2.3.3/src/poetry/utils/env/env_manager.py --- old/poetry-2.3.2/src/poetry/utils/env/env_manager.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/utils/env/env_manager.py 2026-03-29 14:13:27.000000000 +0200 @@ -125,7 +125,7 @@ if self.use_in_project_venv(): create = False venv = self.in_project_venv - if venv.exists(): + if venv.is_dir(): # We need to check if the patch version is correct _venv = VirtualEnv(venv) current_patch = ".".join(str(v) for v in _venv.version_info[:3]) @@ -159,7 +159,7 @@ # Create if needed if not venv.exists() or create: - in_venv = os.environ.get("VIRTUAL_ENV") is not None + in_venv = bool(os.environ.get("VIRTUAL_ENV")) if in_venv or not venv.exists(): create = True @@ -214,7 +214,9 @@ conda_env_name = os.environ.get("CONDA_DEFAULT_ENV") # It's probably not a good idea to pollute Conda's global "base" env, since # most users have it activated all the time. - in_venv = env_prefix is not None and conda_env_name != "base" + # Treat an empty env_prefix as if no virtualenv is active, since conda + # can leave CONDA_PREFIX set to an empty string after deactivation. + in_venv = bool(env_prefix) and conda_env_name != "base" if not in_venv or env is not None: # Checking if a local virtualenv exists @@ -249,14 +251,8 @@ return VirtualEnv(venv) - if env_prefix is not None: - prefix = Path(env_prefix) - base_prefix = None - else: - prefix = Path(sys.prefix) - base_prefix = self.get_base_prefix() - - return VirtualEnv(prefix, base_prefix) + assert env_prefix + return VirtualEnv(Path(env_prefix)) def list(self, name: str | None = None) -> list[VirtualEnv]: if name is None: @@ -456,7 +452,7 @@ f"Invalid template string in 'virtualenvs.prompt' setting: {e}" ) from e - if not venv.exists(): + if not venv.is_dir(): if create_venv is False: self._io.write_error_line( "<fg=black;bg=yellow>" @@ -467,6 +463,12 @@ return self.get_system_env() + if venv.is_file(): + self._io.write_error_line( + f"<warning>{venv} is not a virtual environment but a file. Removing it.</warning>" + ) + venv.unlink() + self._io.write_error_line( f"Creating virtualenv <c1>{name}</> in" f" {venv_path if not WINDOWS else get_real_windows_path(venv_path)!s}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/utils/env/python/manager.py new/poetry-2.3.3/src/poetry/utils/env/python/manager.py --- old/poetry-2.3.2/src/poetry/utils/env/python/manager.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/utils/env/python/manager.py 2026-03-29 14:13:27.000000000 +0200 @@ -23,6 +23,7 @@ from poetry.core.constraints.version import VersionConstraint from poetry.core.constraints.version import parse_constraint +from poetry.utils._compat import is_relative_to from poetry.utils.env.python.exceptions import NoCompatiblePythonVersionFoundError from poetry.utils.env.python.providers import PoetryPythonPathProvider from poetry.utils.env.python.providers import ShutilWhichPythonProvider @@ -84,10 +85,10 @@ @classmethod def find_all(cls) -> Iterator[Python]: venv_path: Path | None = ( - Path(os.environ["VIRTUAL_ENV"]) if "VIRTUAL_ENV" in os.environ else None + Path(venv) if (venv := os.environ.get("VIRTUAL_ENV")) else None ) for python in findpython.find_all(): - if venv_path and python.executable.is_relative_to(venv_path): + if venv_path and is_relative_to(python.executable, venv_path): continue yield cls(python=python) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/src/poetry/vcs/git/backend.py new/poetry-2.3.3/src/poetry/vcs/git/backend.py --- old/poetry-2.3.2/src/poetry/vcs/git/backend.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/src/poetry/vcs/git/backend.py 2026-03-29 14:13:27.000000000 +0200 @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib import dataclasses import logging import os @@ -20,6 +21,7 @@ from dulwich.errors import NotGitRepository from dulwich.file import FileLocked from dulwich.index import IndexEntry +from dulwich.object_store import peel_sha from dulwich.objects import ObjectID from dulwich.protocol import PEELED_TAG_SUFFIX from dulwich.refs import Ref @@ -63,8 +65,9 @@ "- the remote ({remote}) you have specified\n" " - was misspelled\n" " - does not exist\n" - " - requires credentials that were either not configured or is incorrect\n" - " - is in accessible due to network issues" + " - requires credentials that were either not configured or are incorrect\n" + " - contains Git submodules that require credentials that were either not configured or are incorrect\n" + " - is inaccessible due to network issues" ) ERROR_MESSAGE_FILE_LOCK = ( "- another process is holding the file lock\n" @@ -96,7 +99,7 @@ Resolve the ref using the provided remote refs. """ self._normalise(remote_refs=remote_refs, repo=repo) - self._set_head(remote_refs=remote_refs) + self._set_head(remote_refs=remote_refs, repo=repo) def _normalise(self, remote_refs: FetchPackResult, repo: Repo) -> None: """ @@ -142,7 +145,7 @@ self.revision = sha.decode("utf-8") return - def _set_head(self, remote_refs: FetchPackResult) -> None: + def _set_head(self, remote_refs: FetchPackResult, repo: Repo) -> None: """ Internal helper method to populate ref and set it's sha as the remote's head and default ref. @@ -165,6 +168,15 @@ ) head = remote_refs.refs[self.ref] + # Peel tag objects to get the underlying commit SHA. + # Annotated tags are Tag objects, not Commit objects. Operations like + # reset_index() expect HEAD to point to a Commit, so we must peel tags + # to extract the commit SHA they reference. + # Object not in store yet will be handled during fetch + if head is not None: + with contextlib.suppress(KeyError): + head = peel_sha(repo.object_store, head)[1].id + remote_refs.refs[self.ref] = remote_refs.refs[Ref(b"HEAD")] = head @property diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/tests/console/commands/conftest.py new/poetry-2.3.3/tests/console/commands/conftest.py --- old/poetry-2.3.2/tests/console/commands/conftest.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/tests/console/commands/conftest.py 2026-03-29 14:13:27.000000000 +0200 @@ -30,7 +30,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" readme = "README.md" requires-python = ">=3.6" """ @@ -55,7 +55,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" readme = "README.md" requires-python = ">=3.6" dependencies = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/tests/console/commands/test_init.py new/poetry-2.3.3/tests/console/commands/test_init.py --- old/poetry-2.3.2/tests/console/commands/test_init.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/tests/console/commands/test_init.py 2026-03-29 14:13:27.000000000 +0200 @@ -145,7 +145,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" dependencies = [ "pendulum (>=2.0.0,<3.0.0)", @@ -201,7 +201,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" """ @@ -269,7 +269,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" dependencies = [ "demo @ git+https://github.com/demo/demo.git" @@ -364,7 +364,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" dependencies = [ "demo @ git+https://github.com/demo/demo.git@develop" @@ -412,7 +412,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" dependencies = [ "demo @ git+https://github.com/demo/pyproject-demo.git" @@ -467,7 +467,7 @@ authors = [ {{name = "Your Name",email = "[email protected]"}} ] -license = {{text = "MIT"}} +license = "MIT" requires-python = ">=3.6" dependencies = [ "demo @ {demo_uri}" @@ -521,7 +521,7 @@ authors = [ {{name = "Your Name",email = "[email protected]"}} ] -license = {{text = "MIT"}} +license = "MIT" requires-python = ">=3.6" dependencies = [ "demo @ {demo_uri}" @@ -576,7 +576,7 @@ authors = [ {{name = "Your Name",email = "[email protected]"}} ] -license = {{text = "MIT"}} +license = "MIT" requires-python = ">=3.6" dependencies = [ "demo @ {demo_uri}" @@ -623,7 +623,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.8" dependencies = [ "foo (==1.19.2)", @@ -660,7 +660,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" """ @@ -691,7 +691,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" dependencies = [ "pendulum (>=2.0.0,<3.0.0)" @@ -733,7 +733,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" dependencies = [ "pendulum (>=2.0.0,<3.0.0)", @@ -768,7 +768,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" dependencies = [ ] @@ -814,7 +814,7 @@ authors = [ {name = "Your Name",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.6" dependencies = [ ] @@ -861,7 +861,7 @@ authors = [ {name = "Foo Bar",email = "[email protected]"} ] -license = {text = "MIT"} +license = "MIT" requires-python = ">=3.8" dependencies = [ "pendulum (>=2.0.0,<3.0.0)" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/tests/console/commands/test_publish.py new/poetry-2.3.3/tests/console/commands/test_publish.py --- old/poetry-2.3.2/tests/console/commands/test_publish.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/tests/console/commands/test_publish.py 2026-03-29 14:13:27.000000000 +0200 @@ -5,6 +5,7 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import NoReturn +from unittest.mock import PropertyMock import pytest import requests @@ -215,3 +216,31 @@ assert "Publishing simple-project (1.2.3) to PyPI" in output assert "- Uploading simple_project-1.2.3.tar.gz" in error assert "- Uploading simple_project-1.2.3-py2.py3-none-any.whl" in error + + +def test_publish_build_no_interaction_skips_confirmation( + app_tester: ApplicationTester, mocker: MockerFixture +) -> None: + mocker.patch( + "poetry.publishing.publisher.Publisher.files", + new_callable=PropertyMock, + return_value=[Path("dist/simple_project-1.2.3-py2.py3-none-any.whl")], + ) + confirm = mocker.patch("poetry.console.commands.publish.PublishCommand.confirm") + command_call = mocker.patch("poetry.console.commands.publish.PublishCommand.call") + publisher_publish = mocker.patch("poetry.publishing.Publisher.publish") + + exit_code = app_tester.execute("publish --build --no-interaction --dry-run") + + assert exit_code == 0 + output = app_tester.io.fetch_output() + error = app_tester.io.fetch_error() + + confirm.assert_not_called() + assert "Build anyway?" not in output + assert "Build anyway?" not in error + assert ( + "Warning: There are 1 files ready for publishing in dist. Build anyway!" + ) in output + command_call.assert_called_once_with("build", args="--output dist") + assert publisher_publish.call_count == 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/tests/installation/test_wheel_installer.py new/poetry-2.3.3/tests/installation/test_wheel_installer.py --- old/poetry-2.3.2/tests/installation/test_wheel_installer.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/tests/installation/test_wheel_installer.py 2026-03-29 14:13:27.000000000 +0200 @@ -81,3 +81,51 @@ assert not list(cache_dir.glob("*.opt-2.pyc")) else: assert not cache_dir.exists() + + +def test_install_dir_is_symlink(tmp_path: Path, demo_wheel: Path) -> None: + target_dir = tmp_path / "target" + target_dir.mkdir() + symlink_dir = tmp_path / "symlink" + symlink_dir.symlink_to(target_dir, target_is_directory=True) + + env = MockEnv(path=symlink_dir) + + installer = WheelInstaller(env) + installer.install(demo_wheel) + + assert (Path(env.paths["purelib"]) / "demo").exists() + + [email protected] +def wheel_with_path_traversal(tmp_path: Path) -> Path: + import zipfile + + wheel = tmp_path / "traversal-0.1-py3-none-any.whl" + files = { + "traversal/__init__.py": b"", + "../../traversal.txt": b"", + "traversal-0.1.dist-info/WHEEL": ( + b"Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n" + ), + "traversal-0.1.dist-info/METADATA": ( + b"Metadata-Version: 2.1\nName: traversal\nVersion: 0.1\n" + ), + } + files["traversal-0.1.dist-info/RECORD"] = ( + "\n".join([f"{k},," for k in files] + ["traversal-0.1.dist-info/RECORD,,"]) + + "\n" + ).encode() + + with zipfile.ZipFile(wheel, "w") as z: + for k, v in files.items(): + z.writestr(k, v) + + return wheel + + +def test_path_traversal(env: MockEnv, wheel_with_path_traversal: Path) -> None: + installer = WheelInstaller(env) + with pytest.raises(ValueError): + installer.install(wheel_with_path_traversal) + assert not (env.path.parent / "traversal.txt").exists() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/tests/utils/env/conftest.py new/poetry-2.3.3/tests/utils/env/conftest.py --- old/poetry-2.3.2/tests/utils/env/conftest.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/tests/utils/env/conftest.py 2026-03-29 14:13:27.000000000 +0200 @@ -4,6 +4,8 @@ import pytest +from cleo.io.buffered_io import BufferedIO + from poetry.utils.env import EnvManager @@ -19,5 +21,10 @@ @pytest.fixture -def manager(poetry: Poetry) -> EnvManager: - return EnvManager(poetry) +def io() -> BufferedIO: + return BufferedIO() + + [email protected] +def manager(poetry: Poetry, io: BufferedIO) -> EnvManager: + return EnvManager(poetry, io) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/tests/utils/env/test_env_manager.py new/poetry-2.3.3/tests/utils/env/test_env_manager.py --- old/poetry-2.3.2/tests/utils/env/test_env_manager.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/tests/utils/env/test_env_manager.py 2026-03-29 14:13:27.000000000 +0200 @@ -32,6 +32,7 @@ from collections.abc import Iterator from unittest.mock import MagicMock + from cleo.io.buffered_io import BufferedIO from pytest import LogCaptureFixture from pytest_mock import MockerFixture @@ -470,6 +471,56 @@ assert not envs_file.exists() +def test_activate_with_in_project_setting_if_venv_is_file( + manager: EnvManager, + poetry: Poetry, + io: BufferedIO, + config: Config, + tmp_path: Path, + mocker: MockerFixture, + venv_flags_default: dict[str, bool], + mocked_python_register: MockedPythonRegister, +) -> None: + if "VIRTUAL_ENV" in os.environ: + del os.environ["VIRTUAL_ENV"] + + config.merge( + { + "virtualenvs": { + "path": str(tmp_path / "virtualenvs"), + "in-project": True, + } + } + ) + + mocked_python_register("3.7.1") + m = mocker.patch("poetry.utils.env.EnvManager.build_venv") + + venv_path = poetry.file.path.parent / ".venv" + assert not venv_path.exists() + venv_path.touch() + assert venv_path.is_file() + + manager.activate("python3.7") + + m.assert_called_with( + poetry.file.path.parent / ".venv", + executable=Path("/usr/bin/python3.7"), + flags=venv_flags_default, + prompt="simple-project-py3.7", + ) + + envs_file = TOMLFile(tmp_path / "virtualenvs" / "envs.toml") + assert not envs_file.exists() + + # The .venv file is removed, but no .venv is created because we mocked build_venv. + assert not venv_path.exists() + assert ( + f"{venv_path} is not a virtual environment but a file. Removing it." + in io.fetch_error() + ) + + def test_deactivate_non_activated_but_existing( tmp_path: Path, manager: EnvManager, @@ -577,6 +628,33 @@ assert env.base == Path(sys.base_prefix) [email protected]("env_var", ["VIRTUAL_ENV", "CONDA_PREFIX"]) +def test_get_ignores_empty_env_prefix( + manager: EnvManager, + poetry: Poetry, + in_project_venv_dir: Path, + env_var: str, + mocker: MockerFixture, +) -> None: + """An empty VIRTUAL_ENV or CONDA_PREFIX should be treated as unset. + + After ``conda deactivate``, conda can leave CONDA_PREFIX set to an + empty string. Poetry should not consider that as an active + virtualenv and should fall back to the in-project .venv instead. + + See: https://github.com/python-poetry/poetry/issues/10770 + """ + os.environ.pop("VIRTUAL_ENV", None) + os.environ.pop("CONDA_PREFIX", None) + os.environ[env_var] = "" + mocker.patch( + "poetry.utils.env.virtual_env.VirtualEnv.__init__", + lambda self, *args, **kwargs: setattr(self, "_path", args[0]), + ) + venv = manager.get() + assert venv.path == in_project_venv_dir + + def test_list( tmp_path: Path, manager: EnvManager, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/tests/utils/test_compat.py new/poetry-2.3.3/tests/utils/test_compat.py --- old/poetry-2.3.2/tests/utils/test_compat.py 1970-01-01 01:00:00.000000000 +0100 +++ new/poetry-2.3.3/tests/utils/test_compat.py 2026-03-29 14:13:27.000000000 +0200 @@ -0,0 +1,71 @@ +from __future__ import annotations + +import sys + +from pathlib import Path + +import pytest + +from poetry.utils._compat import is_relative_to + + [email protected]( + ("path1", "path2", "expected"), + [ + ("a", "a", True), + ("a/b", "a/b", True), + ("a/b", "a", True), + ("a", "a/b", False), + ("a/b/c/d", "a/b", True), + ("a/b", "a/b/c/d", False), + ], +) +def test_is_relative_to(path1: str, path2: str, expected: bool) -> None: + assert is_relative_to(Path(path1), Path(path2)) is expected + + [email protected]( + ("path1", "path2", "expected"), + [ + ("/", "/", True), + ("/a/b", "/a/b", True), + ("/a/b", "/a", True), + ("/a", "/a/b", False), + ("/a/b/c/d", "/a/b", True), + ("/a/b", "/a/b/c/d", False), + ], +) [email protected](sys.platform == "win32", reason="non-Windows paths") +def test_is_relative_to_non_win32(path1: str, path2: str, expected: bool) -> None: + assert is_relative_to(Path(path1), Path(path2)) is expected + + [email protected]( + ("path1", "path2", "expected"), + [ + ("C:\\", "C:\\", True), + (r"C:\a\b", r"C:\a\b", True), + (r"C:\a\b", r"C:\a", True), + (r"C:\a", r"C:\a\b", False), + (r"C:\a\b\c\d", r"C:\a\b", True), + (r"C:\a\b", r"C:\a\b\c\d", False), + (r"C:\a\b", r"D:\a", False), + (r"C:\a\b", "D:\\", False), + (r"\\server\a\b", r"\\server\a", True), + (r"\\server\a", r"\\server\a\b", False), + (r"\\server2\a\b", r"\\server\a", False), + # long path prefix + (r"\\?\C:\a\b", r"\\?\C:\a", True), + (r"\\?\C:\a\b", r"C:\a", True), + (r"C:\a\b", r"\\?\C:\a", True), + (r"\\?\C:\a", r"\\?\C:\a\b", False), + # long path UNC prefix + (r"\\?\UNC\server\a\b", r"\\?\UNC\server\a", True), + (r"\\?\UNC\server\a\b", r"\\server\a", True), + (r"\\server\a\b", r"\\?\UNC\server\a", True), + (r"\\?\UNC\server\a", r"\\?\UNC\server\a\b", False), + ], +) [email protected](sys.platform != "win32", reason="Windows paths") +def test_is_relative_to_win32(path1: str, path2: str, expected: bool) -> None: + assert is_relative_to(Path(path1), Path(path2)) is expected diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-2.3.2/tests/vcs/git/test_backend.py new/poetry-2.3.3/tests/vcs/git/test_backend.py --- old/poetry-2.3.2/tests/vcs/git/test_backend.py 2026-02-01 16:31:39.000000000 +0100 +++ new/poetry-2.3.3/tests/vcs/git/test_backend.py 2026-03-29 14:13:27.000000000 +0200 @@ -8,6 +8,8 @@ import pytest from dulwich.client import FetchPackResult +from dulwich.refs import HEADREF +from dulwich.refs import Ref from dulwich.repo import Repo from poetry.console.exceptions import PoetryRuntimeError @@ -290,3 +292,135 @@ f"Try again later or remove the {tag_ref_lock} manually" " if you are sure no other process is holding it." ) + + [email protected]_git_mock +def test_clone_annotated_tag(tmp_path: Path) -> None: + """Test cloning at an annotated tag (issue #10658).""" + from dulwich import porcelain + from dulwich.objects import Commit + + # Create a source repository with an annotated tag + source_path = tmp_path / "source-repo" + source_path.mkdir() + repo = Repo.init(str(source_path)) + + # Create initial commit + test_file = source_path / "test.txt" + test_file.write_text("test content", encoding="utf-8") + porcelain.add(repo, str(test_file)) + expected_commit_sha = porcelain.commit( + repo, + message=b"Initial commit", + author=b"Test <[email protected]>", + committer=b"Test <[email protected]>", + ) + + # Create an annotated tag + porcelain.tag_create( + repo, + tag=b"v1.0.0", + message=b"Release 1.0.0", + author=b"Test <[email protected]>", + annotated=True, + ) + + # Clone at the annotated tag + source_root_dir = tmp_path / "clone-root" + source_root_dir.mkdir() + cloned_repo = Git.clone( + url=source_path.as_uri(), + source_root=source_root_dir, + name="clone-test", + tag="v1.0.0", + ) + + # Verify HEAD points to a commit, not a tag object + head_sha = cloned_repo.refs[HEADREF] + head_obj = cloned_repo.object_store[head_sha] + assert isinstance(head_obj, Commit), ( + f"HEAD should point to a Commit, got {type(head_obj).__name__}" + ) + # Verify it's the correct commit + assert head_sha == expected_commit_sha, ( + f"HEAD should point to the expected commit {expected_commit_sha.hex()}, " + f"got {head_sha.hex()}" + ) + + # Verify the clone succeeded and files are present + clone_dir = source_root_dir / "clone-test" + assert (clone_dir / ".git").is_dir() + assert (clone_dir / "test.txt").exists() + assert (clone_dir / "test.txt").read_text(encoding="utf-8") == "test content" + + [email protected]_git_mock +def test_clone_nested_annotated_tags(tmp_path: Path) -> None: + """Test cloning at a tag that points to another tag (nested tags).""" + from dulwich import porcelain + from dulwich.objects import Commit + from dulwich.objects import Tag + + # Create a source repository with nested annotated tags + source_path = tmp_path / "source-repo" + source_path.mkdir() + repo = Repo.init(str(source_path)) + + # Create initial commit + test_file = source_path / "test.txt" + test_file.write_text("nested tag test", encoding="utf-8") + porcelain.add(repo, paths=[b"test.txt"]) + commit_sha = porcelain.commit( + repo, + message=b"Initial commit", + committer=b"Test <[email protected]>", + author=b"Test <[email protected]>", + ) + + # Create first annotated tag pointing to the commit + tag1 = Tag() + tag1.name = b"v1.0.0" + tag1.object = (Commit, commit_sha) + tag1.message = b"First tag" + tag1.tag_time = 1234567890 + tag1.tag_timezone = 0 + tag1.tagger = b"Test <[email protected]>" + repo.object_store.add_object(tag1) + repo.refs[Ref(b"refs/tags/v1.0.0")] = tag1.id + + # Create second annotated tag pointing to the first tag + tag2 = Tag() + tag2.name = b"v1.0.0-release" + tag2.object = (Tag, tag1.id) + tag2.message = b"Second tag (points to first tag)" + tag2.tag_time = 1234567891 + tag2.tag_timezone = 0 + tag2.tagger = b"Test <[email protected]>" + repo.object_store.add_object(tag2) + repo.refs[Ref(b"refs/tags/v1.0.0-release")] = tag2.id + + # Clone at the nested tag + source_root_dir = tmp_path / "clone-root" + source_root_dir.mkdir() + cloned_repo = Git.clone( + url=source_path.as_uri(), + source_root=source_root_dir, + name="clone-test", + tag="v1.0.0-release", + ) + + # Verify HEAD points to a commit, not a tag object + head_sha = cloned_repo.refs[HEADREF] + head_obj = cloned_repo.object_store[head_sha] + assert isinstance(head_obj, Commit), ( + f"HEAD should point to a Commit (peeling nested tags), got {type(head_obj).__name__}" + ) + + # Verify it's the correct commit + assert head_sha == commit_sha + + # Verify the clone succeeded and files are present + clone_dir = source_root_dir / "clone-test" + assert (clone_dir / ".git").is_dir() + assert (clone_dir / "test.txt").exists() + assert (clone_dir / "test.txt").read_text(encoding="utf-8") == "nested tag test"
