File support-dulwich-0_25.patch of Package python-poetry
From 2b0723c277cb2a51c8b030e02e0830eeefc75632 Mon Sep 17 00:00:00 2001
From: David Hotham <david.hotham@blueyonder.co.uk>
Date: Mon, 29 Dec 2025 22:51:23 +0000
Subject: [PATCH] Dulwich upgrade (#10674)
---
poetry.lock | 76 ++++++++++++------------
pyproject.toml | 2 +-
src/poetry/vcs/git/backend.py | 78 +++++++++++++++----------
tests/integration/test_utils_vcs_git.py | 31 ++++++----
tests/vcs/git/conftest.py | 3 +
tests/vcs/git/test_backend.py | 14 ++---
6 files changed, 116 insertions(+), 88 deletions(-)
Index: poetry-2.2.1/pyproject.toml
===================================================================
--- poetry-2.2.1.orig/pyproject.toml
+++ poetry-2.2.1/pyproject.toml
@@ -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)",
"importlib-metadata (>=4.4) ; python_version < '3.10'",
"installer (>=0.7.0,<0.8.0)",
Index: poetry-2.2.1/src/poetry/vcs/git/backend.py
===================================================================
--- poetry-2.2.1.orig/src/poetry/vcs/git/backend.py
+++ poetry-2.2.1/src/poetry/vcs/git/backend.py
@@ -20,7 +20,9 @@ from dulwich.config import parse_submodu
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
@@ -76,10 +78,10 @@ def is_revision_sha(revision: str | None
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
@@ -87,7 +89,7 @@ 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:
"""
@@ -104,7 +106,7 @@ class GitRefSpec:
"""
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
@@ -120,7 +122,7 @@ class GitRefSpec:
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
@@ -131,7 +133,7 @@ class GitRefSpec:
# 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
@@ -145,24 +147,25 @@ class GitRefSpec:
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:
@@ -216,7 +219,7 @@ class Git:
@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:
@@ -234,21 +237,24 @@ class Git:
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,
)
@@ -335,7 +341,9 @@ class Git:
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.</>",
@@ -349,30 +357,36 @@ class Git:
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,
@@ -519,7 +533,9 @@ class Git:
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)
Index: poetry-2.2.1/tests/integration/test_utils_vcs_git.py
===================================================================
--- poetry-2.2.1.orig/tests/integration/test_utils_vcs_git.py
+++ poetry-2.2.1/tests/integration/test_utils_vcs_git.py
@@ -15,7 +15,9 @@ import pytest
from dulwich.client import HTTPUnauthorized
from dulwich.client import get_transport_and_path
+from dulwich.config import CaseInsensitiveOrderedMultiDict
from dulwich.config import ConfigFile
+from dulwich.refs import Ref
from dulwich.repo import Repo
from poetry.console.exceptions import PoetryConsoleError
@@ -108,7 +110,9 @@ def _remote_refs(source_url: str, local_
path: str
client, path = get_transport_and_path(source_url)
return client.fetch(
- path, local_repo, determine_wants=local_repo.object_store.determine_wants_all
+ path.encode(),
+ local_repo,
+ determine_wants=local_repo.object_store.determine_wants_all,
)
@@ -118,8 +122,8 @@ def remote_refs(_remote_refs: FetchPackR
@pytest.fixture(scope="module")
-def remote_default_ref(_remote_refs: FetchPackResult) -> bytes:
- ref: bytes = _remote_refs.symrefs[b"HEAD"]
+def remote_default_ref(_remote_refs: FetchPackResult) -> Ref:
+ ref: Ref = _remote_refs.symrefs[Ref(b"HEAD")]
return ref
@@ -136,12 +140,14 @@ def test_use_system_git_client_from_envi
def test_git_local_info(
- source_url: str, remote_refs: FetchPackResult, remote_default_ref: bytes
+ source_url: str, remote_refs: FetchPackResult, remote_default_ref: Ref
) -> None:
with Git.clone(url=source_url) as repo:
info = Git.info(repo=repo)
assert info.origin == source_url
- assert info.revision == remote_refs.refs[remote_default_ref].decode("utf-8")
+ ref = remote_refs.refs[remote_default_ref]
+ assert ref is not None
+ assert info.revision == ref.decode("utf-8")
@pytest.mark.parametrize(
@@ -151,7 +157,7 @@ def test_git_clone_default_branch_head(
specification: GitCloneKwargs,
source_url: str,
remote_refs: FetchPackResult,
- remote_default_ref: bytes,
+ remote_default_ref: Ref,
mocker: MockerFixture,
) -> None:
spy = mocker.spy(Git, "_clone")
@@ -294,7 +300,7 @@ def test_system_git_fallback_on_http_401
mocker.patch.object(
Git,
"_clone",
- side_effect=HTTPUnauthorized(None, None),
+ side_effect=HTTPUnauthorized(None, source_url),
)
# use tmp_path for source_root to get a shorter path,
@@ -307,7 +313,7 @@ def test_system_git_fallback_on_http_401
spy.assert_called_with(
url="https://github.com/python-poetry/test-fixture-vcs-repository.git",
target=path,
- refspec=GitRefSpec(branch="0.1", revision=None, tag=None, ref=b"HEAD"),
+ refspec=GitRefSpec(branch="0.1", revision=None, tag=None, ref=Ref(b"HEAD")),
)
spy.assert_called_once()
@@ -388,6 +394,8 @@ def test_username_password_parameter_is_
spy_get_transport_and_path.assert_called_with(
location=source_url,
config=dummy_git_config,
+ username=None,
+ password=None,
)
spy_get_transport_and_path.assert_called_once()
@@ -411,7 +419,7 @@ def test_system_git_called_when_configur
spy_legacy.assert_called_with(
url=source_url,
target=path,
- refspec=GitRefSpec(branch="0.1", revision=None, tag=None, ref=b"HEAD"),
+ refspec=GitRefSpec(branch="0.1", revision=None, tag=None, ref=Ref(b"HEAD")),
)
@@ -428,9 +436,10 @@ def test_relative_submodules_with_ssh(
)
# construct fake git config
- fake_config = ConfigFile(
- {(b"remote", b"origin"): {b"url": ssh_source_url.encode("utf-8")}}
+ values = CaseInsensitiveOrderedMultiDict.make(
+ {b"url": ssh_source_url.encode("utf-8")}
)
+ fake_config = ConfigFile({(b"remote", b"origin"): values})
# trick Git into thinking remote.origin is an ssh url
mock_get_config = mocker.patch.object(repo_with_unresolved_submodules, "get_config")
mock_get_config.return_value = fake_config
Index: poetry-2.2.1/tests/vcs/git/conftest.py
===================================================================
--- poetry-2.2.1.orig/tests/vcs/git/conftest.py
+++ poetry-2.2.1/tests/vcs/git/conftest.py
@@ -27,6 +27,7 @@ def temp_repo(tmp_path: Path) -> TempRep
author=b"User <user@example.com>",
message=b"init",
no_verify=True,
+ sign=False,
)
# one commit which is not "head"
@@ -37,6 +38,7 @@ def temp_repo(tmp_path: Path) -> TempRep
author=b"User <user@example.com>",
message=b"extra",
no_verify=True,
+ sign=False,
)
# extra commit
@@ -48,6 +50,7 @@ def temp_repo(tmp_path: Path) -> TempRep
author=b"User <user@example.com>",
message=b"extra",
no_verify=True,
+ sign=False,
)
repo[b"refs/tags/v1"] = head_commit
Index: poetry-2.2.1/tests/vcs/git/test_backend.py
===================================================================
--- poetry-2.2.1.orig/tests/vcs/git/test_backend.py
+++ poetry-2.2.1/tests/vcs/git/test_backend.py
@@ -11,7 +11,7 @@ from dulwich.repo import Repo
from poetry.console.exceptions import PoetryRuntimeError
from poetry.vcs.git.backend import Git
-from poetry.vcs.git.backend import annotated_tag
+from poetry.vcs.git.backend import peeled_tag
from poetry.vcs.git.backend import is_revision_sha
from poetry.vcs.git.backend import urlpathjoin
@@ -60,8 +60,8 @@ def test_get_name_from_source_url(url: s
@pytest.mark.parametrize(("tag"), ["my-tag", b"my-tag"])
-def test_annotated_tag(tag: str | bytes) -> None:
- tag = annotated_tag("my-tag")
+def test_peeled_tag(tag: str | bytes) -> None:
+ tag = peeled_tag("my-tag")
assert tag == b"my-tag^{}"