Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 38 additions & 38 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dependencies = [
"build (>=1.2.1,<2.0.0)",
"cachecontrol[filecache] (>=0.14.0,<0.15.0)",
"cleo (>=2.1.0,<3.0.0)",
"dulwich (>=0.24.0,<0.25.0)",
"dulwich (>=0.25.0,<0.26.0)",
"fastjsonschema (>=2.18.0,<3.0.0)",
"installer (>=0.7.0,<0.8.0)",
"keyring (>=25.1.0,<26.0.0)",
Expand Down
78 changes: 47 additions & 31 deletions src/poetry/vcs/git/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
from dulwich.errors import NotGitRepository
from dulwich.file import FileLocked
from dulwich.index import IndexEntry
from dulwich.refs import ANNOTATED_TAG_SUFFIX
from dulwich.objects import ObjectID
from dulwich.protocol import PEELED_TAG_SUFFIX
from dulwich.refs import Ref
from dulwich.repo import Repo

from poetry.console.exceptions import PoetryRuntimeError
Expand Down Expand Up @@ -76,18 +78,18 @@ def is_revision_sha(revision: str | None) -> bool:
return re.match(r"^\b[0-9a-f]{5,40}\b$", revision or "") is not None


def annotated_tag(ref: str | bytes) -> bytes:
def peeled_tag(ref: str | bytes) -> Ref:
if isinstance(ref, str):
ref = ref.encode("utf-8")
return ref + ANNOTATED_TAG_SUFFIX
return Ref(ref + PEELED_TAG_SUFFIX)


@dataclasses.dataclass
class GitRefSpec:
branch: str | None = None
revision: str | None = None
tag: str | None = None
ref: bytes = dataclasses.field(default_factory=lambda: b"HEAD")
ref: Ref = dataclasses.field(default_factory=lambda: Ref(b"HEAD"))

def resolve(self, remote_refs: FetchPackResult, repo: Repo) -> None:
"""
Expand All @@ -104,7 +106,7 @@ def _normalise(self, remote_refs: FetchPackResult, repo: Repo) -> None:
"""
if self.revision:
ref = f"refs/tags/{self.revision}".encode()
if ref in remote_refs.refs or annotated_tag(ref) in remote_refs.refs:
if ref in remote_refs.refs or peeled_tag(ref) in remote_refs.refs:
# this is a tag, incorrectly specified as a revision, tags take priority
self.tag = self.revision
self.revision = None
Expand All @@ -120,7 +122,7 @@ def _normalise(self, remote_refs: FetchPackResult, repo: Repo) -> None:
and f"refs/heads/{self.branch}".encode() not in remote_refs.refs
and (
f"refs/tags/{self.branch}".encode() in remote_refs.refs
or annotated_tag(f"refs/tags/{self.branch}") in remote_refs.refs
or peeled_tag(f"refs/tags/{self.branch}") in remote_refs.refs
)
):
# this is a tag incorrectly specified as a branch
Expand All @@ -131,7 +133,7 @@ def _normalise(self, remote_refs: FetchPackResult, repo: Repo) -> None:
# revision is a short sha, resolve to full sha
short_sha = self.revision.encode("utf-8")
for sha in remote_refs.refs.values():
if sha.startswith(short_sha):
if sha is not None and sha.startswith(short_sha):
self.revision = sha.decode("utf-8")
return

Expand All @@ -145,24 +147,25 @@ def _set_head(self, remote_refs: FetchPackResult) -> None:
Internal helper method to populate ref and set it's sha as the remote's head
and default ref.
"""
self.ref = remote_refs.symrefs[b"HEAD"]
self.ref = remote_refs.symrefs[Ref(b"HEAD")]

head: ObjectID | None
if self.revision:
head = self.revision.encode("utf-8")
head = ObjectID(self.revision.encode("utf-8"))
else:
if self.tag:
ref = f"refs/tags/{self.tag}".encode()
annotated = annotated_tag(ref)
self.ref = annotated if annotated in remote_refs.refs else ref
ref = Ref(f"refs/tags/{self.tag}".encode())
peeled = peeled_tag(ref)
self.ref = peeled if peeled in remote_refs.refs else ref
elif self.branch:
self.ref = (
self.branch.encode("utf-8")
Ref(self.branch.encode("utf-8"))
if self.is_ref
else f"refs/heads/{self.branch}".encode()
else Ref(f"refs/heads/{self.branch}".encode())
)
head = remote_refs.refs[self.ref]

remote_refs.refs[self.ref] = remote_refs.refs[b"HEAD"] = head
remote_refs.refs[self.ref] = remote_refs.refs[Ref(b"HEAD")] = head

@property
def key(self) -> str:
Expand Down Expand Up @@ -216,7 +219,7 @@ def get_remote_url(repo: Repo, remote: str = "origin") -> str:
@staticmethod
def get_revision(repo: Repo) -> str:
with repo:
return repo.get_peeled(b"HEAD").decode("utf-8")
return repo.get_peeled(Ref(b"HEAD")).decode("utf-8")

@classmethod
def info(cls, repo: Repo | Path) -> GitRepoLocalInfo:
Expand All @@ -234,21 +237,24 @@ def _fetch_remote_refs(cls, url: str, local: Repo) -> FetchPackResult:
client: GitClient
path: str

kwargs: dict[str, str] = {}
credentials = get_default_authenticator().get_credentials_for_git_url(url=url)

username = None
password = None
if credentials.password and credentials.username:
# we do this conditionally as otherwise, dulwich might complain if these
# parameters are passed in for an ssh url
kwargs["username"] = credentials.username
kwargs["password"] = credentials.password
username = credentials.username
password = credentials.password

config = local.get_config_stack()
client, path = get_transport_and_path(url, config=config, **kwargs)
client, path = get_transport_and_path(
url, config=config, username=username, password=password
)

with local:
result: FetchPackResult = client.fetch(
path,
path.encode(),
local,
determine_wants=local.object_store.determine_wants_all,
)
Expand Down Expand Up @@ -335,7 +341,9 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo:

try:
# ensure local HEAD matches remote
local.refs[b"HEAD"] = remote_refs.refs[b"HEAD"]
ref = remote_refs.refs[Ref(b"HEAD")]
if ref is not None:
local.refs[Ref(b"HEAD")] = ref
except ValueError:
raise PoetryRuntimeError.create(
reason=f"<error>Failed to clone {url} at '{refspec.key}', verify ref exists on remote.</>",
Expand All @@ -349,30 +357,36 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo:

if refspec.is_ref:
# set ref to current HEAD
local.refs[refspec.ref] = local.refs[b"HEAD"]
local.refs[refspec.ref] = local.refs[Ref(b"HEAD")]

for base, prefix in {
(b"refs/remotes/origin", b"refs/heads/"),
(b"refs/tags", b"refs/tags"),
(Ref(b"refs/remotes/origin"), b"refs/heads/"),
(Ref(b"refs/tags"), b"refs/tags"),
}:
try:
local.refs.import_refs(
base=base,
other={
n[len(prefix) :]: v
Ref(n[len(prefix) :]): v
for (n, v) in remote_refs.refs.items()
if n.startswith(prefix) and not n.endswith(ANNOTATED_TAG_SUFFIX)
if n.startswith(prefix)
and not n.endswith(PEELED_TAG_SUFFIX)
and v is not None
},
)
except FileLocked as e:

def to_str(path: bytes) -> str:
return path.decode().replace(os.sep * 2, os.sep)
def to_str(path: bytes | str) -> str:
if isinstance(path, bytes):
path = path.decode()
return path.replace(os.sep * 2, os.sep)

raise PoetryRuntimeError.create(
# <https://github.com/jelmer/dulwich/pull/2045> should clean up the
# ignore.
reason=(
f"<error>Failed to clone {url} at '{refspec.key}',"
f" unable to acquire file lock for {to_str(e.filename)}.</>"
f" unable to acquire file lock for {to_str(e.filename)}.</>" # type: ignore[arg-type]
),
info=[
ERROR_MESSAGE_NOTE,
Expand Down Expand Up @@ -519,7 +533,9 @@ def clone(

with current_repo:
# we use peeled sha here to ensure tags are resolved consistently
current_sha = current_repo.get_peeled(b"HEAD").decode("utf-8")
current_sha = current_repo.get_peeled(Ref(b"HEAD")).decode(
"utf-8"
)
except (NotGitRepository, AssertionError, KeyError):
# something is wrong with the current checkout, clean it
remove_directory(target, force=True)
Expand Down
Loading