diff --git a/src/scmrepo/git/__init__.py b/src/scmrepo/git/__init__.py index e96bc765..e86af4d5 100644 --- a/src/scmrepo/git/__init__.py +++ b/src/scmrepo/git/__init__.py @@ -3,11 +3,12 @@ import logging import os import re +import typing from collections import OrderedDict from collections.abc import Mapping from contextlib import contextmanager from functools import partialmethod -from typing import Dict, Iterable, Optional, Tuple, Type, Union +from typing import TYPE_CHECKING, Callable, Dict, Iterable, Optional, Tuple, Type, Union from funcy import cached_property, first from pathspec.patterns import GitWildMatchPattern @@ -16,12 +17,15 @@ from scmrepo.exceptions import FileNotInRepoError, GitHookAlreadyExists, RevError from scmrepo.utils import relpath -from .backend.base import BaseGitBackend, NoGitBackendError +from .backend.base import BaseGitBackend, NoGitBackendError, SyncStatus from .backend.dulwich import DulwichBackend from .backend.gitpython import GitPythonBackend from .backend.pygit2 import Pygit2Backend from .stash import Stash +if TYPE_CHECKING: + from scmrepo.progress import GitProgressEvent + logger = logging.getLogger(__name__) BackendCls = Type[BaseGitBackend] @@ -310,6 +314,35 @@ def add_commit( self.add(paths) self.commit(msg=message) + _fetch_refspecs = partialmethod( + _backend_func, "fetch_refspecs", backends=["pygit2", "dulwich"] + ) + + def fetch_refspecs( + self, + url: str, + refspecs: Union[str, Iterable[str]], + force: bool = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, + progress: Optional[Callable[["GitProgressEvent"], None]] = None, + **kwargs, + ) -> typing.Mapping[str, SyncStatus]: + from .credentials import get_matching_helper_commands + + if "dulwich" in kwargs.get("backends", self.backends.backends) and any( + get_matching_helper_commands(url, self.dulwich.repo.get_config_stack()) + ): + kwargs["backends"] = ["dulwich"] + + return self._fetch_refspecs( + url, + refspecs, + force=force, + on_diverged=on_diverged, + progress=progress, + **kwargs, + ) + is_ignored = partialmethod(_backend_func, "is_ignored") add = partialmethod(_backend_func, "add") commit = partialmethod(_backend_func, "commit") @@ -337,9 +370,6 @@ def add_commit( iter_remote_refs = partialmethod(_backend_func, "iter_remote_refs") get_refs_containing = partialmethod(_backend_func, "get_refs_containing") push_refspecs = partialmethod(_backend_func, "push_refspecs") - fetch_refspecs = partialmethod( - _backend_func, "fetch_refspecs", backends=["pygit2", "dulwich"] - ) _stash_iter = partialmethod(_backend_func, "_stash_iter") _stash_push = partialmethod(_backend_func, "_stash_push") _stash_apply = partialmethod(_backend_func, "_stash_apply") diff --git a/src/scmrepo/git/backend/dulwich/client.py b/src/scmrepo/git/backend/dulwich/client.py index ae65b5f7..a15d481c 100644 --- a/src/scmrepo/git/backend/dulwich/client.py +++ b/src/scmrepo/git/backend/dulwich/client.py @@ -4,7 +4,7 @@ from dulwich.client import Urllib3HttpGitClient from dulwich.config import StackedConfig -from .credentials import CredentialNotFoundError, get_credentials_from_helper +from scmrepo.git.credentials import CredentialNotFoundError, get_credentials_from_helper class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstract-method diff --git a/src/scmrepo/git/backend/dulwich/credentials.py b/src/scmrepo/git/credentials.py similarity index 91% rename from src/scmrepo/git/backend/dulwich/credentials.py rename to src/scmrepo/git/credentials.py index 950b2bf9..f5b5abde 100644 --- a/src/scmrepo/git/backend/dulwich/credentials.py +++ b/src/scmrepo/git/credentials.py @@ -159,8 +159,7 @@ def erase(self, *args, **kwargs): raise NotImplementedError -def get_credentials_from_helper(base_url: str, config) -> Tuple[bytes, bytes]: - """Retrieves credentials for the given url from git credential helpers""" +def get_matching_helper_commands(base_url: str, config): if isinstance(config, StackedConfig): backends = config.backends else: @@ -175,19 +174,22 @@ def get_credentials_from_helper(base_url: str, config) -> Tuple[bytes, bytes]: except KeyError: # no helper configured continue + yield command.decode(conf.encoding or sys.getdefaultencoding()) - helper = CredentialHelper( - command.decode(conf.encoding or sys.getdefaultencoding()) - ) - parsed = urlparse(base_url) - try: - return helper.get( - protocol=parsed.scheme, - hostname=parsed.hostname, - port=parsed.port, - username=parsed.username, - ) - except CredentialNotFoundError: - continue +def get_credentials_from_helper(base_url: str, config) -> Tuple[bytes, bytes]: + """Retrieves credentials for the given url from git credential helpers""" + + for command in get_matching_helper_commands(base_url, config): + helper = CredentialHelper(command) + parsed = urlparse(base_url) + try: + return helper.get( + protocol=parsed.scheme, + hostname=parsed.hostname, + port=parsed.port, + username=parsed.username, + ) + except CredentialNotFoundError: + continue raise CredentialNotFoundError