Skip to content

Commit 357582a

Browse files
authored
Merge pull request #547 from pdm-project/bugfix/vcs-url
2 parents 22d8bd0 + 92f76c9 commit 357582a

File tree

5 files changed

+53
-21
lines changed

5 files changed

+53
-21
lines changed

news/547.bugfix.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix the VCS url to be consistent between lock and install.

pdm/models/requirements.py

+21-19
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,7 @@ def from_req_dict(cls, name: str, req_dict: RequirementDict) -> "Requirement":
164164
for vcs in VCS_SCHEMA:
165165
if vcs in req_dict:
166166
repo = cast(str, req_dict.pop(vcs, None))
167-
url = (
168-
vcs
169-
+ "+"
170-
+ VcsRequirement._build_url_from_req_dict(name, repo, req_dict)
171-
)
167+
url = vcs + "+" + repo
172168
return VcsRequirement.create(name=name, vcs=vcs, url=url, **req_dict)
173169
if "path" in req_dict or "url" in req_dict:
174170
return FileRequirement.create(name=name, **req_dict)
@@ -366,33 +362,39 @@ def __post_init__(self) -> None:
366362
if not self.vcs:
367363
self.vcs = self.url.split("+", 1)[0]
368364

369-
def as_ireq(self, **kwargs: Any) -> InstallRequirement:
370-
ireq = super().as_ireq(**kwargs)
371-
if not self.editable and self.revision:
372-
# For non-editable VCS requirements, commit-hash should be used as the
373-
# rev-options for InstallRequirement to consume.
374-
parsed = urlparse.urlparse(cast(Link, ireq.link).url)
375-
new_path = "@".join((parsed.path.split("@", 1)[0], self.revision))
376-
new_url = urlparse.urlunparse(parsed._replace(path=new_path))
377-
ireq.link = Link(new_url)
378-
return ireq
365+
def as_line(self) -> str:
366+
project_name = f"{self.project_name}" if self.project_name else ""
367+
extras = f"[{','.join(sorted(self.extras))}]" if self.extras else ""
368+
marker = self._format_marker()
369+
url = url_without_fragments(self.url)
370+
if self.revision and not self.editable:
371+
url += f"@{self.revision}"
372+
elif self.ref:
373+
url += f"@{self.ref}"
374+
if self.editable or self.subdirectory:
375+
fragments = f"egg={project_name}{extras}"
376+
if self.subdirectory:
377+
fragments = f"{fragments}&subdirectory={self.subdirectory}"
378+
return f"{'-e ' if self.editable else ''}{url}#{fragments}{marker}"
379+
delimiter = " @ " if project_name else ""
380+
return f"{project_name}{extras}{delimiter}{url}{marker}"
379381

380382
def _parse_url(self) -> None:
381383
vcs, url_no_vcs = self.url.split("+", 1)
382384
if url_no_vcs.startswith("git@"):
383385
url_no_vcs = add_ssh_scheme_to_git_uri(url_no_vcs)
384-
self.url = f"{vcs}+{url_no_vcs}"
385386
if not self.name:
386387
self._parse_name_from_url()
387-
repo = url_without_fragments(url_no_vcs)
388-
ref: str | None = None
388+
ref = self.ref
389389
parsed = urlparse.urlparse(url_no_vcs)
390+
path = parsed.path
390391
fragments = dict(urlparse.parse_qsl(parsed.fragment))
391392
if "subdirectory" in fragments:
392393
self.subdirectory = fragments["subdirectory"]
393394
if "@" in parsed.path:
394395
path, ref = parsed.path.split("@", 1)
395-
repo = urlparse.urlunparse(parsed._replace(path=path))
396+
repo = urlparse.urlunparse(parsed._replace(path=path, fragment=""))
397+
self.url = f"{vcs}+{repo}"
396398
self.repo, self.ref = repo, ref # type: ignore
397399

398400
@staticmethod

pdm/project/core.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class Project:
4949

5050
PYPROJECT_FILENAME = "pyproject.toml"
5151
DEPENDENCIES_RE = re.compile(r"(?:(.+?)-)?dependencies")
52-
LOCKFILE_VERSION = "3"
52+
LOCKFILE_VERSION = "3.1"
5353
GLOBAL_PROJECT = Path.home() / ".pdm" / "global-project"
5454

5555
def __init__(
@@ -437,7 +437,7 @@ def is_lockfile_hash_match(self) -> bool:
437437

438438
def is_lockfile_compatible(self) -> bool:
439439
if not self.lockfile_file.exists():
440-
return False
440+
return True
441441
lockfile_version = str(
442442
self.lockfile.get("metadata", {}).get("lock_version", "")
443443
)

tests/cli/test_add.py

+24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import shutil
2+
from pathlib import Path
3+
14
import pytest
25

36
from pdm.cli import actions
7+
from pdm.models.pip_shims import Link
48
from pdm.models.specifiers import PySpecSet
9+
from tests import FIXTURES
510

611

712
@pytest.mark.usefixtures("repository")
@@ -207,3 +212,22 @@ def test_add_package_unconstrained_rewrite_specifier(project):
207212
locked_candidates = project.locked_repository.all_candidates
208213
assert locked_candidates["django"].version == "1.11.8"
209214
assert project.meta.dependencies[0] == "django~=1.11"
215+
216+
217+
@pytest.mark.usefixtures("repository", "working_set", "vcs")
218+
def test_add_cached_vcs_requirement(project, mocker):
219+
url = "git+https://github.com/test-root/demo.git@1234567890abcdef#egg=demo"
220+
built_path = FIXTURES / "artifacts/demo-0.0.1-py2.py3-none-any.whl"
221+
wheel_cache = project.make_wheel_cache()
222+
cache_path = Path(wheel_cache.get_path_for_link(Link(url)))
223+
if not cache_path.exists():
224+
cache_path.mkdir(parents=True)
225+
shutil.copy2(built_path, cache_path)
226+
actions.do_add(project, packages=[url], no_self=True)
227+
lockfile_entry = next(p for p in project.lockfile["package"] if p["name"] == "demo")
228+
assert lockfile_entry["revision"] == "1234567890abcdef"
229+
230+
downloader = mocker.patch("pdm.models.pip_shims.unpack_url")
231+
builder = mocker.patch("pdm.builders.WheelBuilder.build")
232+
downloader.assert_not_called()
233+
builder.assert_not_called()

tests/conftest.py

+5
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ def obtain(self, dest, url):
7373
def get_revision(cls, location):
7474
return "1234567890abcdef"
7575

76+
def is_immutable_rev_checkout(self, url: str, dest: str) -> bool:
77+
if "@1234567890abcdef" in url:
78+
return True
79+
return super().is_immutable_rev_checkout(url, dest)
80+
7681

7782
class _FakeLink:
7883
is_wheel = False

0 commit comments

Comments
 (0)