diff --git a/news/278.bugfix.rst b/news/278.bugfix.rst new file mode 100644 index 0000000000000000000000000000000000000000..01f096913ce53826de3945cd9cf8997e3dadba35 --- /dev/null +++ b/news/278.bugfix.rst @@ -0,0 +1 @@ +Fix a bug that editable package doesn't override the non-editable version in the working set. diff --git a/pdm/installers/installers.py b/pdm/installers/installers.py index 27721504147a8f345164ff33fa810c4220bb8352..4d23fc64896f134361130cb7c65b99a17118f102 100644 --- a/pdm/installers/installers.py +++ b/pdm/installers/installers.py @@ -20,7 +20,7 @@ if TYPE_CHECKING: def is_dist_editable(dist: Distribution) -> bool: - return isinstance(dist, EggInfoDistribution) + return isinstance(dist, EggInfoDistribution) or getattr(dist, "editable", False) def format_dist(dist: Distribution) -> str: diff --git a/pdm/installers/synchronizers.py b/pdm/installers/synchronizers.py index febad897682177ef04864806ce63e8cad78b8871..1e1cf1920524b0c0bac71c8401f3905b570a9378 100644 --- a/pdm/installers/synchronizers.py +++ b/pdm/installers/synchronizers.py @@ -117,8 +117,11 @@ class Synchronizer: can = candidates.pop(key) if can.marker and not can.marker.evaluate(environment): to_remove.append(key) - elif not is_dist_editable(dist) and dist.version != can.version: - # XXX: An editable distribution is always considered as consistent. + elif not is_dist_editable(dist) and ( + dist.version != can.version or can.req.editable + ): + to_update.append(key) + elif is_dist_editable(dist) and not can.req.editable: to_update.append(key) elif key not in self.all_candidates and key not in self.SEQUENTIAL_PACKAGES: # Remove package only if it is not required by any section diff --git a/pdm/models/candidates.py b/pdm/models/candidates.py index c127f7099b3ff1a102d7d743390a4f422bd0d04c..3ada354b791aa45021c1905a71a75d1821052eea 100644 --- a/pdm/models/candidates.py +++ b/pdm/models/candidates.py @@ -237,7 +237,12 @@ class Candidate: } project_root = self.environment.project.root.as_posix() if self.req.is_vcs: - result.update({self.req.vcs: self.req.repo, "revision": self.revision}) + result.update( + { + self.req.vcs: self.req.repo, + "ref": self.req.ref if self.req.editable else self.revision, + } + ) elif not self.req.is_named: if self.req.is_file_or_url and self.req.is_local_dir: result.update(path=path_replace(project_root, ".", self.req.str_path)) diff --git a/tests/cli/test_actions.py b/tests/cli/test_actions.py index eedb7650e761ca17b6b59bfc2aefaedb5798d16b..527a8935f7324816f63bc99f90dd1bef8625cec9 100644 --- a/tests/cli/test_actions.py +++ b/tests/cli/test_actions.py @@ -450,3 +450,15 @@ def test_update_ignore_constraints(project, repository, working_set): def test_init_validate_python_requires(project_no_init): with pytest.raises(ValueError): actions.do_init(project_no_init, python_requires="3.7") + + +def test_editable_package_override_non_editable(project, repository, working_set, vcs): + project.environment.python_requires = PySpecSet(">=3.6") + actions.do_add( + project, packages=["git+https://github.com/test-root/demo.git#egg=demo"] + ) + actions.do_add( + project, + editables=["git+https://github.com/test-root/demo.git#egg=demo"], + ) + assert working_set["demo"].editable diff --git a/tests/conftest.py b/tests/conftest.py index eabefca1882d916e758de4f39dcb49bc8c1b6969..54497971ccd5e764f5b92e0653eecb809a6be8b3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -146,9 +146,10 @@ class TestProject(Project): class Distribution: - def __init__(self, key, version): + def __init__(self, key, version, editable=False): self.key = key self.version = version + self.editable = editable self.dependencies = [] def requires(self, extras=()): @@ -190,7 +191,7 @@ def working_set(mocker, repository): pip_logging._log_state.indentation = 0 dependencies = repository.get_dependencies(candidate)[0] key = safe_name(candidate.name).lower() - dist = Distribution(key, candidate.version) + dist = Distribution(key, candidate.version, candidate.req.editable) dist.dependencies = dependencies rv.add_distribution(dist)