From f3ef884db57317a2ee41c10ad053e1d38b4ff690 Mon Sep 17 00:00:00 2001 From: Peter Rowlands Date: Wed, 5 Apr 2023 15:00:01 +0900 Subject: [PATCH 1/2] dulwich: preserve data from generators when retrying request --- src/scmrepo/git/backend/dulwich/client.py | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/scmrepo/git/backend/dulwich/client.py b/src/scmrepo/git/backend/dulwich/client.py index d5f678e5..4aaa02b3 100644 --- a/src/scmrepo/git/backend/dulwich/client.py +++ b/src/scmrepo/git/backend/dulwich/client.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional +from typing import Dict, Iterator, List, Optional, Union from dulwich.client import HTTPUnauthorized, Urllib3HttpGitClient @@ -27,10 +27,28 @@ def _http_request( self, url: str, headers: Optional[Dict[str, str]] = None, - data: Any = None, + data: Optional[Union[bytes, Iterator[bytes]]] = None, ): + cached_chunks: List[bytes] = [] + + def _cached_data() -> Iterator[bytes]: + assert data is not None + if isinstance(data, bytes): + yield data + return + + if cached_chunks: + yield from cached_chunks + return + + for chunk in data: + cached_chunks.append(chunk) + yield chunk + try: - result = super()._http_request(url, headers=headers, data=data) + result = super()._http_request( + url, headers=headers, data=None if data is None else _cached_data() + ) except HTTPUnauthorized: auth_header = self._get_auth() if not auth_header: @@ -39,7 +57,9 @@ def _http_request( headers.update(auth_header) else: headers = auth_header - result = super()._http_request(url, headers=headers, data=data) + result = super()._http_request( + url, headers=headers, data=None if data is None else _cached_data() + ) if self._store_credentials is not None: self._store_credentials.approve() return result From 37d4689af22872cbf96efd1ab379b077c923810e Mon Sep 17 00:00:00 2001 From: Peter Rowlands Date: Wed, 5 Apr 2023 15:49:41 +0900 Subject: [PATCH 2/2] dulwich: check for rejected pack in push_refspecs --- src/scmrepo/git/backend/dulwich/__init__.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/scmrepo/git/backend/dulwich/__init__.py b/src/scmrepo/git/backend/dulwich/__init__.py index 7bb0c9da..8fc09f59 100644 --- a/src/scmrepo/git/backend/dulwich/__init__.py +++ b/src/scmrepo/git/backend/dulwich/__init__.py @@ -508,7 +508,7 @@ def iter_remote_refs(self, url: str, base: Optional[str] = None, **kwargs): def get_refs_containing(self, rev: str, pattern: Optional[str] = None): raise NotImplementedError - def push_refspecs( + def push_refspecs( # noqa: C901 self, url: str, refspecs: Union[str, Iterable[str]], @@ -568,7 +568,7 @@ def update_refs(refs): return new_refs try: - client.send_pack( + result = client.send_pack( path, update_refs, self.repo.object_store.generate_pack_data, @@ -579,6 +579,17 @@ def update_refs(refs): raise SCMError(f"Git failed to push '{src}' to '{url}'") from exc except HTTPUnauthorized as exc: raise AuthError(url) from exc + if result.ref_status and any( + (value is not None) for value in result.ref_status.values() + ): + reasons = ", ".join( + ( + f"{os.fsdecode(ref)}: {reason}" + for ref, reason in result.ref_status.items() + if reason is not None + ) + ) + raise SCMError(f"Git failed to push some refs to '{url}' ({reasons})") return change_result def fetch_refspecs(