scmrepo 3.3.7__tar.gz → 3.3.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of scmrepo might be problematic. Click here for more details.
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.github/workflows/tests.yaml +1 -1
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.gitignore +1 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.pre-commit-config.yaml +2 -2
- {scmrepo-3.3.7/src/scmrepo.egg-info → scmrepo-3.3.9}/PKG-INFO +4 -2
- {scmrepo-3.3.7 → scmrepo-3.3.9}/noxfile.py +1 -1
- {scmrepo-3.3.7 → scmrepo-3.3.9}/pyproject.toml +4 -1
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/dulwich/__init__.py +9 -4
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/pygit2/__init__.py +2 -4
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/pygit2/filter.py +3 -3
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/credentials.py +33 -2
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/client.py +1 -1
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/progress.py +1 -1
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/storage.py +2 -2
- {scmrepo-3.3.7 → scmrepo-3.3.9/src/scmrepo.egg-info}/PKG-INFO +4 -2
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo.egg-info/requires.txt +2 -1
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/conftest.py +1 -1
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_credentials.py +40 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_git.py +94 -2
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_pygit2.py +1 -1
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.coveragerc +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.cruft.json +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.gitattributes +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.github/dependabot.yml +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.github/workflows/release.yaml +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/.github/workflows/update-template.yaml +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/CODE_OF_CONDUCT.rst +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/CONTRIBUTING.rst +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/LICENSE +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/README.rst +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/setup.cfg +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/__init__.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/asyn.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/base.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/exceptions.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/fs.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/__init__.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/__init__.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/base.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/dulwich/client.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/gitpython.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/backend/pygit2/callbacks.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/config.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/__init__.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/exceptions.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/fetch.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/object.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/pointer.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/lfs/smudge.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/objects.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/git/stash.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/noscm.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/progress.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/py.typed +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/urls.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo/utils.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo.egg-info/SOURCES.txt +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo.egg-info/dependency_links.txt +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/src/scmrepo.egg-info/top_level.txt +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/__init__.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/docker-compose.yml +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/git-init/git.sh +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_dulwich.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_fs.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_lfs.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_noscm.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_scmrepo.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_stash.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/test_urls.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/user.key +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/user.key.pub +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/vendor/__init__.py +0 -0
- {scmrepo-3.3.7 → scmrepo-3.3.9}/tests/vendor/test_paramiko_vendor.py +0 -0
|
@@ -2,7 +2,7 @@ default_language_version:
|
|
|
2
2
|
python: python3
|
|
3
3
|
repos:
|
|
4
4
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
5
|
-
rev:
|
|
5
|
+
rev: v5.0.0
|
|
6
6
|
hooks:
|
|
7
7
|
- id: check-added-large-files
|
|
8
8
|
- id: check-case-conflict
|
|
@@ -20,7 +20,7 @@ repos:
|
|
|
20
20
|
- id: sort-simple-yaml
|
|
21
21
|
- id: trailing-whitespace
|
|
22
22
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
23
|
-
rev: 'v0.
|
|
23
|
+
rev: 'v0.7.4'
|
|
24
24
|
hooks:
|
|
25
25
|
- id: ruff
|
|
26
26
|
args: [--fix, --exit-non-zero-on-fix]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.9
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
15
|
Classifier: Development Status :: 4 - Beta
|
|
15
16
|
Requires-Python: >=3.9
|
|
16
17
|
Description-Content-Type: text/x-rst
|
|
@@ -35,8 +36,9 @@ Requires-Dist: pytest-docker<4,>=1; extra == "tests"
|
|
|
35
36
|
Requires-Dist: pytest-mock; extra == "tests"
|
|
36
37
|
Requires-Dist: pytest-sugar; extra == "tests"
|
|
37
38
|
Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
|
|
39
|
+
Requires-Dist: proxy.py; extra == "tests"
|
|
38
40
|
Provides-Extra: dev
|
|
39
|
-
Requires-Dist: mypy==1.
|
|
41
|
+
Requires-Dist: mypy==1.13.0; extra == "dev"
|
|
40
42
|
Requires-Dist: scmrepo[tests]; extra == "dev"
|
|
41
43
|
Requires-Dist: types-certifi; extra == "dev"
|
|
42
44
|
Requires-Dist: types-mock; extra == "dev"
|
|
@@ -10,7 +10,7 @@ nox.options.sessions = "lint", "tests"
|
|
|
10
10
|
locations = "src", "tests"
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@nox.session(python=["3.9", "3.10", "3.11", "3.12"])
|
|
13
|
+
@nox.session(python=["3.9", "3.10", "3.11", "3.12", "3.13"])
|
|
14
14
|
def tests(session: nox.Session) -> None:
|
|
15
15
|
session.install(".[tests]")
|
|
16
16
|
session.run(
|
|
@@ -16,6 +16,7 @@ classifiers = [
|
|
|
16
16
|
"Programming Language :: Python :: 3.10",
|
|
17
17
|
"Programming Language :: Python :: 3.11",
|
|
18
18
|
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
19
20
|
"Development Status :: 4 - Beta",
|
|
20
21
|
]
|
|
21
22
|
requires-python = ">=3.9"
|
|
@@ -48,9 +49,10 @@ tests = [
|
|
|
48
49
|
"pytest-mock",
|
|
49
50
|
"pytest-sugar",
|
|
50
51
|
"pytest-test-utils>=0.1.0,<0.2",
|
|
52
|
+
"proxy.py",
|
|
51
53
|
]
|
|
52
54
|
dev = [
|
|
53
|
-
"mypy==1.
|
|
55
|
+
"mypy==1.13.0",
|
|
54
56
|
"scmrepo[tests]",
|
|
55
57
|
"types-certifi",
|
|
56
58
|
"types-mock",
|
|
@@ -73,6 +75,7 @@ markers = [
|
|
|
73
75
|
]
|
|
74
76
|
asyncio_mode = "auto"
|
|
75
77
|
|
|
78
|
+
|
|
76
79
|
[tool.coverage.run]
|
|
77
80
|
branch = true
|
|
78
81
|
source = ["scmrepo", "tests"]
|
|
@@ -16,6 +16,7 @@ from typing import (
|
|
|
16
16
|
Union,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
from dulwich.config import ConfigFile, StackedConfig
|
|
19
20
|
from funcy import cached_property, reraise
|
|
20
21
|
|
|
21
22
|
from scmrepo.exceptions import AuthError, CloneError, InvalidRemote, RevError, SCMError
|
|
@@ -27,7 +28,7 @@ from scmrepo.utils import relpath
|
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
29
30
|
from dulwich.client import SSHVendor
|
|
30
|
-
from dulwich.config import ConfigFile
|
|
31
|
+
from dulwich.config import ConfigFile
|
|
31
32
|
from dulwich.repo import Repo
|
|
32
33
|
|
|
33
34
|
from scmrepo.git.objects import GitCommit
|
|
@@ -579,7 +580,8 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
579
580
|
|
|
580
581
|
try:
|
|
581
582
|
_remote, location = get_remote_repo(self.repo, url)
|
|
582
|
-
|
|
583
|
+
_config = kwargs.pop("config", StackedConfig.default())
|
|
584
|
+
client, path = get_transport_and_path(location, config=_config, **kwargs)
|
|
583
585
|
except Exception as exc:
|
|
584
586
|
raise InvalidRemote(url) from exc
|
|
585
587
|
|
|
@@ -616,7 +618,8 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
616
618
|
|
|
617
619
|
try:
|
|
618
620
|
_remote, location = get_remote_repo(self.repo, url)
|
|
619
|
-
|
|
621
|
+
_config = kwargs.pop("config", StackedConfig.default())
|
|
622
|
+
client, path = get_transport_and_path(location, config=_config, **kwargs)
|
|
620
623
|
except Exception as exc:
|
|
621
624
|
raise SCMError(f"'{url}' is not a valid Git remote or URL") from exc
|
|
622
625
|
|
|
@@ -723,7 +726,8 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
723
726
|
|
|
724
727
|
with reraise(Exception, SCMError(f"'{url}' is not a valid Git remote or URL")):
|
|
725
728
|
_remote, location = get_remote_repo(self.repo, url)
|
|
726
|
-
|
|
729
|
+
_config = kwargs.pop("config", StackedConfig.default())
|
|
730
|
+
client, path = get_transport_and_path(location, config=_config, **kwargs)
|
|
727
731
|
|
|
728
732
|
with reraise(
|
|
729
733
|
(NotGitRepository, KeyError),
|
|
@@ -909,6 +913,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
909
913
|
|
|
910
914
|
try:
|
|
911
915
|
_, location = get_remote_repo(self.repo, url)
|
|
916
|
+
_config = kwargs.pop("config", StackedConfig.default())
|
|
912
917
|
client, path = get_transport_and_path(location, **kwargs)
|
|
913
918
|
except Exception as exc:
|
|
914
919
|
raise InvalidRemote(url) from exc
|
|
@@ -701,15 +701,13 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
701
701
|
remote_refs: dict[str, Oid] = (
|
|
702
702
|
{
|
|
703
703
|
head["name"]: head["oid"]
|
|
704
|
-
for head in remote.ls_remotes(callbacks=cb)
|
|
704
|
+
for head in remote.ls_remotes(callbacks=cb, proxy=True)
|
|
705
705
|
}
|
|
706
706
|
if not force
|
|
707
707
|
else {}
|
|
708
708
|
)
|
|
709
709
|
remote.fetch(
|
|
710
|
-
refspecs=refspecs,
|
|
711
|
-
callbacks=cb,
|
|
712
|
-
message="fetch",
|
|
710
|
+
refspecs=refspecs, callbacks=cb, message="fetch", proxy=True
|
|
713
711
|
)
|
|
714
712
|
|
|
715
713
|
result: dict[str, SyncStatus] = {}
|
|
@@ -2,10 +2,10 @@ import io
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import TYPE_CHECKING, Callable, Optional
|
|
4
4
|
|
|
5
|
-
from pygit2 import GIT_FILTER_CLEAN, Filter, Passthrough
|
|
5
|
+
from pygit2 import GIT_FILTER_CLEAN, Filter, Passthrough # type: ignore[attr-defined]
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
-
from pygit2 import FilterSource
|
|
8
|
+
from pygit2 import FilterSource # type: ignore[attr-defined]
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ class LFSFilter(Filter):
|
|
|
17
17
|
self._smudge_buf: Optional[io.BytesIO] = None
|
|
18
18
|
self._smudge_root: Optional[str] = None
|
|
19
19
|
|
|
20
|
-
def check(self, src: "FilterSource", attr_values: list[str]):
|
|
20
|
+
def check(self, src: "FilterSource", attr_values: list[Optional[str]]):
|
|
21
21
|
if attr_values[0] == "lfs" and src.mode != GIT_FILTER_CLEAN:
|
|
22
22
|
self._smudge_buf = io.BytesIO()
|
|
23
23
|
self._smudge_root = src.repo.workdir or src.repo.path
|
|
@@ -173,11 +173,29 @@ class GitCredentialHelper(CredentialHelper):
|
|
|
173
173
|
if res.stderr:
|
|
174
174
|
logger.debug(res.stderr)
|
|
175
175
|
|
|
176
|
-
credentials = {}
|
|
176
|
+
credentials: dict[str, Any] = {}
|
|
177
177
|
for line in res.stdout.splitlines():
|
|
178
178
|
try:
|
|
179
179
|
key, value = line.split("=", maxsplit=1)
|
|
180
|
-
|
|
180
|
+
# Only include credential values that are used in the Credential
|
|
181
|
+
# constructor.
|
|
182
|
+
# Other values may be returned by the subprocess, but they must be
|
|
183
|
+
# ignored.
|
|
184
|
+
# e.g. osxkeychain credential helper >= 2.46.0 can return
|
|
185
|
+
# `capability[]` and `state`)
|
|
186
|
+
if key in [
|
|
187
|
+
"protocol",
|
|
188
|
+
"host",
|
|
189
|
+
"path",
|
|
190
|
+
"username",
|
|
191
|
+
"password",
|
|
192
|
+
"password_expiry_utc",
|
|
193
|
+
"url",
|
|
194
|
+
]:
|
|
195
|
+
# Garbage bytes were output from git-credential-osxkeychain from
|
|
196
|
+
# 2.45.0 to 2.47.0:
|
|
197
|
+
# https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69
|
|
198
|
+
credentials[key] = _strip_garbage_bytes(value)
|
|
181
199
|
except ValueError:
|
|
182
200
|
continue
|
|
183
201
|
if not credentials:
|
|
@@ -265,6 +283,19 @@ class GitCredentialHelper(CredentialHelper):
|
|
|
265
283
|
)
|
|
266
284
|
|
|
267
285
|
|
|
286
|
+
def _strip_garbage_bytes(s: str) -> str:
|
|
287
|
+
"""
|
|
288
|
+
Garbage (random) bytes were output from git-credential-osxkeychain from
|
|
289
|
+
2.45.0 to 2.47.0 so must be removed.
|
|
290
|
+
https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69
|
|
291
|
+
:param s: string that might contain garbage/random bytes
|
|
292
|
+
:return str: The string with the garbage bytes removed
|
|
293
|
+
"""
|
|
294
|
+
# Assume that any garbage bytes begin with a 0-byte
|
|
295
|
+
zero = s.find(chr(0))
|
|
296
|
+
return s[0:zero] if zero >= 0 else s
|
|
297
|
+
|
|
298
|
+
|
|
268
299
|
class _CredentialKey(NamedTuple):
|
|
269
300
|
protocol: str
|
|
270
301
|
host: Optional[str]
|
|
@@ -281,7 +281,7 @@ def _as_atomic(to_info: str, create_parents: bool = False) -> Iterator[str]:
|
|
|
281
281
|
if create_parents:
|
|
282
282
|
os.makedirs(parent, exist_ok=True)
|
|
283
283
|
|
|
284
|
-
tmp_file = NamedTemporaryFile(dir=parent, delete=False)
|
|
284
|
+
tmp_file = NamedTemporaryFile(dir=parent, delete=False) # noqa: SIM115
|
|
285
285
|
tmp_file.close()
|
|
286
286
|
try:
|
|
287
287
|
yield tmp_file.name
|
|
@@ -47,7 +47,7 @@ class LFSStorage:
|
|
|
47
47
|
oid = obj if isinstance(obj, str) else obj.oid
|
|
48
48
|
path = self.oid_to_path(oid)
|
|
49
49
|
try:
|
|
50
|
-
return open(path, **kwargs)
|
|
50
|
+
return open(path, **kwargs)
|
|
51
51
|
except FileNotFoundError:
|
|
52
52
|
if not fetch_url or not isinstance(obj, Pointer):
|
|
53
53
|
raise
|
|
@@ -57,7 +57,7 @@ class LFSStorage:
|
|
|
57
57
|
raise FileNotFoundError(
|
|
58
58
|
errno.ENOENT, os.strerror(errno.ENOENT), path
|
|
59
59
|
) from exc
|
|
60
|
-
return open(path, **kwargs)
|
|
60
|
+
return open(path, **kwargs)
|
|
61
61
|
|
|
62
62
|
def close(self):
|
|
63
63
|
pass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.9
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
15
|
Classifier: Development Status :: 4 - Beta
|
|
15
16
|
Requires-Python: >=3.9
|
|
16
17
|
Description-Content-Type: text/x-rst
|
|
@@ -35,8 +36,9 @@ Requires-Dist: pytest-docker<4,>=1; extra == "tests"
|
|
|
35
36
|
Requires-Dist: pytest-mock; extra == "tests"
|
|
36
37
|
Requires-Dist: pytest-sugar; extra == "tests"
|
|
37
38
|
Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
|
|
39
|
+
Requires-Dist: proxy.py; extra == "tests"
|
|
38
40
|
Provides-Extra: dev
|
|
39
|
-
Requires-Dist: mypy==1.
|
|
41
|
+
Requires-Dist: mypy==1.13.0; extra == "dev"
|
|
40
42
|
Requires-Dist: scmrepo[tests]; extra == "dev"
|
|
41
43
|
Requires-Dist: types-certifi; extra == "dev"
|
|
42
44
|
Requires-Dist: types-mock; extra == "dev"
|
|
@@ -58,7 +58,7 @@ email=dvctester@example.com
|
|
|
58
58
|
defaultBranch=master
|
|
59
59
|
"""
|
|
60
60
|
(home_dir / ".gitconfig").write_bytes(contents)
|
|
61
|
-
pygit2.settings.search_path[pygit2.GIT_CONFIG_LEVEL_GLOBAL] = str(home_dir)
|
|
61
|
+
pygit2.settings.search_path[pygit2.GIT_CONFIG_LEVEL_GLOBAL] = str(home_dir) # type: ignore[attr-defined]
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
@pytest.fixture
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import os
|
|
3
|
+
import random
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
5
6
|
|
|
@@ -45,6 +46,45 @@ def test_subprocess_get_use_http_path(git_helper, mocker):
|
|
|
45
46
|
assert creds == Credential(username="foo", password="bar")
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
def test_subprocess_ignore_unexpected_credential_keys(git_helper, mocker):
|
|
50
|
+
git_helper.use_http_path = True
|
|
51
|
+
run = mocker.patch(
|
|
52
|
+
"subprocess.run",
|
|
53
|
+
# Simulate git-credential-osxkeychain (version >=2.45)
|
|
54
|
+
return_value=mocker.Mock(
|
|
55
|
+
stdout="username=foo\npassword=bar\ncapability[]=state\nstate[]=osxkeychain:seen=1"
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
|
|
59
|
+
assert run.call_args.args[0] == ["git-credential-foo", "get"]
|
|
60
|
+
assert (
|
|
61
|
+
run.call_args.kwargs.get("input")
|
|
62
|
+
== "protocol=https\nhost=foo.com\npath=foo.git\n"
|
|
63
|
+
)
|
|
64
|
+
assert creds == Credential(username="foo", password="bar")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_subprocess_strip_trailing_garbage_bytes(git_helper, mocker):
|
|
68
|
+
"""Garbage bytes were output from git-credential-osxkeychain from 2.45.0 to 2.47.0
|
|
69
|
+
so must be removed
|
|
70
|
+
https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69"""
|
|
71
|
+
git_helper.use_http_path = True
|
|
72
|
+
run = mocker.patch(
|
|
73
|
+
"subprocess.run",
|
|
74
|
+
# Simulate git-credential-osxkeychain (version 2.45), assuming initial 0-byte
|
|
75
|
+
return_value=mocker.Mock(
|
|
76
|
+
stdout=f"username=foo\npassword=bar{chr(0)}{random.randbytes(15)}"
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
|
|
80
|
+
assert run.call_args.args[0] == ["git-credential-foo", "get"]
|
|
81
|
+
assert (
|
|
82
|
+
run.call_args.kwargs.get("input")
|
|
83
|
+
== "protocol=https\nhost=foo.com\npath=foo.git\n"
|
|
84
|
+
)
|
|
85
|
+
assert creds == Credential(username="foo", password="bar")
|
|
86
|
+
|
|
87
|
+
|
|
48
88
|
def test_subprocess_get_failed(git_helper, mocker):
|
|
49
89
|
from subprocess import CalledProcessError
|
|
50
90
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from typing import Any, Optional
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
@@ -7,14 +8,20 @@ from asyncssh import SFTPClient
|
|
|
7
8
|
from asyncssh.connection import SSHClientConnection
|
|
8
9
|
from dulwich.client import LocalGitClient
|
|
9
10
|
from git import Repo as GitPythonRepo
|
|
11
|
+
from proxy import TestCase as ProxyTestCase
|
|
10
12
|
from pygit2 import GitError
|
|
11
13
|
from pygit2.remotes import Remote
|
|
12
14
|
from pytest_mock import MockerFixture
|
|
13
15
|
from pytest_test_utils import TempDirFactory, TmpDir
|
|
14
16
|
from pytest_test_utils.matchers import Matcher
|
|
15
17
|
|
|
16
|
-
from scmrepo.exceptions import
|
|
17
|
-
|
|
18
|
+
from scmrepo.exceptions import (
|
|
19
|
+
InvalidRemote,
|
|
20
|
+
MergeConflictError,
|
|
21
|
+
RevError,
|
|
22
|
+
SCMError,
|
|
23
|
+
)
|
|
24
|
+
from scmrepo.git import Git, GitBackends
|
|
18
25
|
from scmrepo.git.objects import GitTag
|
|
19
26
|
|
|
20
27
|
from .conftest import backends
|
|
@@ -22,6 +29,13 @@ from .conftest import backends
|
|
|
22
29
|
# pylint: disable=redefined-outer-name,unused-argument,protected-access
|
|
23
30
|
|
|
24
31
|
|
|
32
|
+
BAD_PROXY_CONFIG = """[http]
|
|
33
|
+
proxy = "http://bad-proxy.dvc.org"
|
|
34
|
+
[https]
|
|
35
|
+
proxy = "http://bad-proxy.dvc.org"
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
25
39
|
@pytest.fixture
|
|
26
40
|
def submodule_dir(tmp_dir: TmpDir, scm: Git):
|
|
27
41
|
scm.commit("init")
|
|
@@ -941,6 +955,84 @@ def test_clone(
|
|
|
941
955
|
assert fobj.read().strip() == "foo"
|
|
942
956
|
|
|
943
957
|
|
|
958
|
+
@pytest.fixture
|
|
959
|
+
def proxy_server():
|
|
960
|
+
class _ProxyServer(ProxyTestCase):
|
|
961
|
+
pass
|
|
962
|
+
|
|
963
|
+
_ProxyServer.setUpClass()
|
|
964
|
+
yield f"http://{_ProxyServer.PROXY.flags.hostname}:{_ProxyServer.PROXY.flags.port}"
|
|
965
|
+
_ProxyServer.tearDownClass()
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
def test_clone_proxy_server(proxy_server: str, scm: Git, git: Git, tmp_dir: TmpDir):
|
|
969
|
+
url = "https://github.com/iterative/dvcyaml-schema"
|
|
970
|
+
|
|
971
|
+
p = (
|
|
972
|
+
Path(os.environ["HOME"] if "HOME" in os.environ else os.environ["USERPROFILE"])
|
|
973
|
+
/ ".gitconfig"
|
|
974
|
+
)
|
|
975
|
+
p.write_text(BAD_PROXY_CONFIG)
|
|
976
|
+
with pytest.raises(Exception): # noqa: PT011, B017
|
|
977
|
+
git.clone(url, "dir")
|
|
978
|
+
|
|
979
|
+
mock_config_content = f"""[http]\n
|
|
980
|
+
proxy = {proxy_server}
|
|
981
|
+
[https]
|
|
982
|
+
proxy = {proxy_server}
|
|
983
|
+
"""
|
|
984
|
+
|
|
985
|
+
p.write_text(mock_config_content)
|
|
986
|
+
git.clone(url, "dir")
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
def test_iter_remote_refs_proxy_server(proxy_server: str, scm: Git, tmp_dir: TmpDir):
|
|
990
|
+
url = "https://github.com/iterative/dvcyaml-schema"
|
|
991
|
+
git = GitBackends.DEFAULT["dulwich"](".")
|
|
992
|
+
|
|
993
|
+
p = (
|
|
994
|
+
Path(os.environ["HOME"] if "HOME" in os.environ else os.environ["USERPROFILE"])
|
|
995
|
+
/ ".gitconfig"
|
|
996
|
+
)
|
|
997
|
+
p.write_text(BAD_PROXY_CONFIG)
|
|
998
|
+
with pytest.raises(Exception): # noqa: PT011, B017
|
|
999
|
+
list(git.iter_remote_refs(url))
|
|
1000
|
+
|
|
1001
|
+
mock_config_content = f"""[http]
|
|
1002
|
+
proxy = {proxy_server}
|
|
1003
|
+
[https]
|
|
1004
|
+
proxy = {proxy_server}
|
|
1005
|
+
"""
|
|
1006
|
+
|
|
1007
|
+
p.write_text(mock_config_content)
|
|
1008
|
+
res = list(git.iter_remote_refs(url))
|
|
1009
|
+
assert res
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
@pytest.mark.skip_git_backend("gitpython")
|
|
1013
|
+
def test_fetch_refspecs_proxy_server(
|
|
1014
|
+
proxy_server: str, scm: Git, git: Git, tmp_dir: TmpDir
|
|
1015
|
+
):
|
|
1016
|
+
url = "https://github.com/iterative/dvcyaml-schema"
|
|
1017
|
+
|
|
1018
|
+
p = (
|
|
1019
|
+
Path(os.environ["HOME"] if "HOME" in os.environ else os.environ["USERPROFILE"])
|
|
1020
|
+
/ ".gitconfig"
|
|
1021
|
+
)
|
|
1022
|
+
p.write_text(BAD_PROXY_CONFIG)
|
|
1023
|
+
with pytest.raises(Exception): # noqa: PT011, B017
|
|
1024
|
+
git.fetch_refspecs(url, ["refs/heads/master:refs/heads/master"])
|
|
1025
|
+
|
|
1026
|
+
mock_config_content = f"""[http]
|
|
1027
|
+
proxy = {proxy_server}
|
|
1028
|
+
[https]
|
|
1029
|
+
proxy = {proxy_server}
|
|
1030
|
+
"""
|
|
1031
|
+
|
|
1032
|
+
p.write_text(mock_config_content)
|
|
1033
|
+
git.fetch_refspecs(url, "refs/heads/master:refs/heads/master")
|
|
1034
|
+
|
|
1035
|
+
|
|
944
1036
|
@pytest.mark.skip_git_backend("pygit2")
|
|
945
1037
|
def test_fetch(tmp_dir: TmpDir, scm: Git, git: Git, tmp_dir_factory: TempDirFactory):
|
|
946
1038
|
tmp_dir.gen("foo", "foo")
|
|
@@ -39,7 +39,7 @@ def test_pygit_resolve_refish(tmp_dir: TmpDir, scm: Git, use_sha: str):
|
|
|
39
39
|
def test_pygit_stash_apply_conflicts(
|
|
40
40
|
tmp_dir: TmpDir, scm: Git, skip_conflicts: bool, mocker: MockerFixture
|
|
41
41
|
):
|
|
42
|
-
from pygit2 import GIT_CHECKOUT_ALLOW_CONFLICTS
|
|
42
|
+
from pygit2 import GIT_CHECKOUT_ALLOW_CONFLICTS # type: ignore[attr-defined]
|
|
43
43
|
|
|
44
44
|
tmp_dir.gen("foo", "foo")
|
|
45
45
|
scm.add_commit("foo", message="foo")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|