scmrepo 3.3.11__tar.gz → 3.4.0__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.11 → scmrepo-3.4.0}/.pre-commit-config.yaml +1 -1
- {scmrepo-3.3.11/src/scmrepo.egg-info → scmrepo-3.4.0}/PKG-INFO +4 -4
- {scmrepo-3.3.11 → scmrepo-3.4.0}/pyproject.toml +14 -4
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/asyn.py +1 -3
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/__init__.py +2 -1
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/dulwich/__init__.py +24 -5
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +4 -2
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/dulwich/client.py +10 -3
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/pygit2/__init__.py +26 -1
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/pygit2/filter.py +1 -1
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/credentials.py +4 -4
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/smudge.py +1 -1
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/progress.py +13 -9
- {scmrepo-3.3.11 → scmrepo-3.4.0/src/scmrepo.egg-info}/PKG-INFO +4 -4
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo.egg-info/requires.txt +3 -3
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_git.py +161 -11
- {scmrepo-3.3.11 → scmrepo-3.4.0}/.coveragerc +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/.cruft.json +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/.gitattributes +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/.github/dependabot.yml +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/.github/workflows/release.yaml +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/.github/workflows/tests.yaml +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/.github/workflows/update-template.yaml +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/.gitignore +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/CODE_OF_CONDUCT.rst +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/CONTRIBUTING.rst +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/LICENSE +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/README.rst +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/noxfile.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/setup.cfg +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/__init__.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/base.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/exceptions.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/fs.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/__init__.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/base.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/gitpython.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/backend/pygit2/callbacks.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/config.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/__init__.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/client.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/exceptions.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/fetch.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/object.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/pointer.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/progress.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/lfs/storage.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/objects.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/git/stash.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/noscm.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/py.typed +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/urls.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo/utils.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo.egg-info/SOURCES.txt +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo.egg-info/dependency_links.txt +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/src/scmrepo.egg-info/top_level.txt +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/__init__.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/conftest.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/docker-compose.yml +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/git-init/git.sh +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_credentials.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_dulwich.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_fs.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_lfs.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_noscm.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_pygit2.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_scmrepo.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_stash.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/test_urls.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/user.key +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/user.key.pub +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/vendor/__init__.py +0 -0
- {scmrepo-3.3.11 → scmrepo-3.4.0}/tests/vendor/test_paramiko_vendor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -17,7 +17,7 @@ Requires-Python: >=3.9
|
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: gitpython>3
|
|
20
|
-
Requires-Dist: dulwich>=0.
|
|
20
|
+
Requires-Dist: dulwich>=0.23.1
|
|
21
21
|
Requires-Dist: pygit2>=1.14.0
|
|
22
22
|
Requires-Dist: pygtrie>=2.3.2
|
|
23
23
|
Requires-Dist: fsspec[tqdm]>=2024.2.0
|
|
@@ -30,7 +30,7 @@ Provides-Extra: tests
|
|
|
30
30
|
Requires-Dist: aioresponses<0.8,>=0.7; extra == "tests"
|
|
31
31
|
Requires-Dist: paramiko<4,>=3.4.0; extra == "tests"
|
|
32
32
|
Requires-Dist: pytest<9,>=7; extra == "tests"
|
|
33
|
-
Requires-Dist: pytest-asyncio<
|
|
33
|
+
Requires-Dist: pytest-asyncio<2,>=0.23.2; extra == "tests"
|
|
34
34
|
Requires-Dist: pytest-cov>=4.1.0; extra == "tests"
|
|
35
35
|
Requires-Dist: pytest-docker<4,>=1; extra == "tests"
|
|
36
36
|
Requires-Dist: pytest-mock; extra == "tests"
|
|
@@ -38,7 +38,7 @@ Requires-Dist: pytest-sugar; extra == "tests"
|
|
|
38
38
|
Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
|
|
39
39
|
Requires-Dist: proxy.py; extra == "tests"
|
|
40
40
|
Provides-Extra: dev
|
|
41
|
-
Requires-Dist: mypy==1.
|
|
41
|
+
Requires-Dist: mypy==1.17.0; extra == "dev"
|
|
42
42
|
Requires-Dist: scmrepo[tests]; extra == "dev"
|
|
43
43
|
Requires-Dist: types-certifi; extra == "dev"
|
|
44
44
|
Requires-Dist: types-mock; extra == "dev"
|
|
@@ -23,7 +23,7 @@ requires-python = ">=3.9"
|
|
|
23
23
|
dynamic = ["version"]
|
|
24
24
|
dependencies = [
|
|
25
25
|
"gitpython>3",
|
|
26
|
-
"dulwich>=0.
|
|
26
|
+
"dulwich>=0.23.1",
|
|
27
27
|
"pygit2>=1.14.0",
|
|
28
28
|
"pygtrie>=2.3.2",
|
|
29
29
|
"fsspec[tqdm]>=2024.2.0",
|
|
@@ -43,7 +43,7 @@ tests = [
|
|
|
43
43
|
"aioresponses>=0.7,<0.8",
|
|
44
44
|
"paramiko>=3.4.0,<4",
|
|
45
45
|
"pytest>=7,<9",
|
|
46
|
-
"pytest-asyncio>=0.23.2,<
|
|
46
|
+
"pytest-asyncio>=0.23.2,<2",
|
|
47
47
|
"pytest-cov>=4.1.0",
|
|
48
48
|
"pytest-docker>=1,<4",
|
|
49
49
|
"pytest-mock",
|
|
@@ -52,7 +52,7 @@ tests = [
|
|
|
52
52
|
"proxy.py",
|
|
53
53
|
]
|
|
54
54
|
dev = [
|
|
55
|
-
"mypy==1.
|
|
55
|
+
"mypy==1.17.0",
|
|
56
56
|
"scmrepo[tests]",
|
|
57
57
|
"types-certifi",
|
|
58
58
|
"types-mock",
|
|
@@ -147,6 +147,7 @@ ignore = [
|
|
|
147
147
|
"RET503", # implicit-return
|
|
148
148
|
"SIM117", # multiple-with-statements
|
|
149
149
|
"N818", # error-suffix-on-exception-name
|
|
150
|
+
"PLC0415", # import-outside-top-level
|
|
150
151
|
]
|
|
151
152
|
select = [
|
|
152
153
|
"A", # flake8-buitlins
|
|
@@ -191,8 +192,9 @@ select = [
|
|
|
191
192
|
|
|
192
193
|
[tool.ruff.lint.per-file-ignores]
|
|
193
194
|
"noxfile.py" = ["D", "PTH"]
|
|
194
|
-
"tests/**" = ["S", "ARG001", "ARG002", "ANN"]
|
|
195
|
+
"tests/**" = ["S", "ARG001", "ARG002", "ANN", "TID251", "TID253"]
|
|
195
196
|
"docs/**" = ["INP"]
|
|
197
|
+
"src/scmrepo/git/backend/gitpython.py" = ["TID251"]
|
|
196
198
|
|
|
197
199
|
[tool.ruff.lint.flake8-pytest-style]
|
|
198
200
|
fixture-parentheses = false
|
|
@@ -202,6 +204,14 @@ parametrize-names-type = "csv"
|
|
|
202
204
|
[tool.ruff.lint.flake8-type-checking]
|
|
203
205
|
strict = true
|
|
204
206
|
|
|
207
|
+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
208
|
+
"git".msg = "importing from 'git' is not allowed except inside `gitpython` backend"
|
|
209
|
+
|
|
210
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
211
|
+
# Ban certain modules from being imported at module level, instead requiring
|
|
212
|
+
# that they're imported lazily (e.g., within a function definition).
|
|
213
|
+
banned-module-level-imports = ["git"]
|
|
214
|
+
|
|
205
215
|
[tool.ruff.lint.isort]
|
|
206
216
|
known-first-party = ["scmrepo"]
|
|
207
217
|
|
|
@@ -6,7 +6,6 @@ import threading
|
|
|
6
6
|
from typing import Any, Optional
|
|
7
7
|
|
|
8
8
|
from fsspec.asyn import ( # noqa: F401, pylint:disable=unused-import
|
|
9
|
-
_selector_policy,
|
|
10
9
|
sync,
|
|
11
10
|
sync_wrapper,
|
|
12
11
|
)
|
|
@@ -23,8 +22,7 @@ def get_loop() -> asyncio.AbstractEventLoop:
|
|
|
23
22
|
if default_loop[0] is None:
|
|
24
23
|
with lock:
|
|
25
24
|
if default_loop[0] is None:
|
|
26
|
-
|
|
27
|
-
default_loop[0] = asyncio.new_event_loop()
|
|
25
|
+
default_loop[0] = asyncio.new_event_loop()
|
|
28
26
|
loop = default_loop[0]
|
|
29
27
|
th = threading.Thread(
|
|
30
28
|
target=loop.run_forever, # type: ignore[attr-defined]
|
|
@@ -10,6 +10,7 @@ from contextlib import contextmanager
|
|
|
10
10
|
from functools import partialmethod
|
|
11
11
|
from typing import (
|
|
12
12
|
TYPE_CHECKING,
|
|
13
|
+
Any,
|
|
13
14
|
Callable,
|
|
14
15
|
ClassVar,
|
|
15
16
|
Optional,
|
|
@@ -295,7 +296,7 @@ class Git(Base):
|
|
|
295
296
|
# See:
|
|
296
297
|
# https://github.com/iterative/dvc/issues/5641
|
|
297
298
|
# https://github.com/iterative/dvc/issues/7458
|
|
298
|
-
def _backend_func(self, name, *args, **kwargs):
|
|
299
|
+
def _backend_func(self, name, *args, **kwargs) -> Any:
|
|
299
300
|
backends: Iterable[str] = kwargs.pop("backends", self.backends)
|
|
300
301
|
for key in backends:
|
|
301
302
|
if self._last_backend is not None and key != self._last_backend:
|
|
@@ -17,6 +17,7 @@ from typing import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
from dulwich.config import ConfigFile, StackedConfig
|
|
20
|
+
from dulwich.walk import ORDER_DATE
|
|
20
21
|
from funcy import cached_property, reraise
|
|
21
22
|
|
|
22
23
|
from scmrepo.exceptions import AuthError, CloneError, InvalidRemote, RevError, SCMError
|
|
@@ -39,7 +40,7 @@ logger = logging.getLogger(__name__)
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class DulwichObject(GitObject):
|
|
42
|
-
def __init__(self, repo, name, mode, sha):
|
|
43
|
+
def __init__(self, repo, name, mode, sha) -> None:
|
|
43
44
|
self.repo = repo
|
|
44
45
|
self._name = name
|
|
45
46
|
self._mode = mode
|
|
@@ -136,7 +137,7 @@ def _get_ssh_vendor() -> "SSHVendor":
|
|
|
136
137
|
|
|
137
138
|
|
|
138
139
|
class DulwichConfig(Config):
|
|
139
|
-
def __init__(self, config: Union["ConfigFile", "StackedConfig"]):
|
|
140
|
+
def __init__(self, config: Union["ConfigFile", "StackedConfig"]) -> None:
|
|
140
141
|
self._config = config
|
|
141
142
|
|
|
142
143
|
@property
|
|
@@ -183,7 +184,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
183
184
|
|
|
184
185
|
def __init__( # pylint:disable=W0231
|
|
185
186
|
self, root_dir=os.curdir, search_parent_directories=True
|
|
186
|
-
):
|
|
187
|
+
) -> None:
|
|
187
188
|
from dulwich.errors import NotGitRepository
|
|
188
189
|
from dulwich.repo import Repo
|
|
189
190
|
|
|
@@ -473,7 +474,25 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
473
474
|
return sorted(ref[len(base) :] for ref in self.iter_refs(base))
|
|
474
475
|
|
|
475
476
|
def list_all_commits(self) -> Iterable[str]:
|
|
476
|
-
|
|
477
|
+
from dulwich.objects import Tag
|
|
478
|
+
|
|
479
|
+
repo = self.repo
|
|
480
|
+
starting_points: list[bytes] = []
|
|
481
|
+
|
|
482
|
+
# HEAD
|
|
483
|
+
head_rev = self.get_ref("HEAD")
|
|
484
|
+
if head_rev:
|
|
485
|
+
starting_points.append(head_rev.encode("utf-8"))
|
|
486
|
+
|
|
487
|
+
# Branches and remotes
|
|
488
|
+
for ref in repo.refs:
|
|
489
|
+
if ref.startswith((b"refs/heads/", b"refs/remotes/", b"refs/tags/")):
|
|
490
|
+
if isinstance(repo.refs[ref], Tag):
|
|
491
|
+
ref = self.repo.get_peeled(repo.refs[ref])
|
|
492
|
+
starting_points.append(repo.refs[ref])
|
|
493
|
+
|
|
494
|
+
walker = self.repo.get_walker(include=starting_points, order=ORDER_DATE)
|
|
495
|
+
return [e.commit.id.decode() for e in walker]
|
|
477
496
|
|
|
478
497
|
def get_tree_obj(self, rev: str, **kwargs) -> DulwichObject:
|
|
479
498
|
from dulwich.objectspec import parse_tree
|
|
@@ -990,7 +1009,7 @@ def ls_remote(url: str) -> dict[str, str]:
|
|
|
990
1009
|
from dulwich.client import HTTPUnauthorized
|
|
991
1010
|
|
|
992
1011
|
try:
|
|
993
|
-
refs = porcelain.ls_remote(url)
|
|
1012
|
+
refs = porcelain.ls_remote(url).refs
|
|
994
1013
|
return {os.fsdecode(ref): sha.decode("ascii") for ref, sha in refs.items()}
|
|
995
1014
|
except HTTPUnauthorized as exc:
|
|
996
1015
|
raise AuthError(url) from exc
|
|
@@ -71,7 +71,9 @@ class _StderrWrapper:
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
class AsyncSSHWrapper(BaseAsyncObject):
|
|
74
|
-
def __init__(
|
|
74
|
+
def __init__(
|
|
75
|
+
self, conn: "SSHClientConnection", proc: "SSHClientProcess", **kwargs
|
|
76
|
+
) -> None:
|
|
75
77
|
super().__init__(**kwargs)
|
|
76
78
|
self.conn: SSHClientConnection = conn
|
|
77
79
|
self.proc: SSHClientProcess = proc
|
|
@@ -109,7 +111,7 @@ class InteractiveSSHClient(SSHClient):
|
|
|
109
111
|
_keys_to_try: Optional[list["FilePath"]] = None
|
|
110
112
|
_passphrases: dict[str, str]
|
|
111
113
|
|
|
112
|
-
def __init__(self, *args, **kwargs):
|
|
114
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
113
115
|
super(*args, **kwargs)
|
|
114
116
|
self._passphrases: dict[str, str] = {}
|
|
115
117
|
|
|
@@ -14,7 +14,7 @@ class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstrac
|
|
|
14
14
|
password=None,
|
|
15
15
|
config=None,
|
|
16
16
|
**kwargs,
|
|
17
|
-
):
|
|
17
|
+
) -> None:
|
|
18
18
|
super().__init__(
|
|
19
19
|
base_url=base_url,
|
|
20
20
|
username=username,
|
|
@@ -29,6 +29,7 @@ class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstrac
|
|
|
29
29
|
url: str,
|
|
30
30
|
headers: Optional[dict[str, str]] = None,
|
|
31
31
|
data: Optional[Union[bytes, Iterator[bytes]]] = None,
|
|
32
|
+
raise_for_status: bool = True,
|
|
32
33
|
):
|
|
33
34
|
cached_chunks: list[bytes] = []
|
|
34
35
|
|
|
@@ -48,7 +49,10 @@ class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstrac
|
|
|
48
49
|
|
|
49
50
|
try:
|
|
50
51
|
result = super()._http_request(
|
|
51
|
-
url,
|
|
52
|
+
url,
|
|
53
|
+
headers=headers,
|
|
54
|
+
data=None if data is None else _cached_data(),
|
|
55
|
+
raise_for_status=raise_for_status,
|
|
52
56
|
)
|
|
53
57
|
except HTTPUnauthorized:
|
|
54
58
|
auth_header = self._get_auth()
|
|
@@ -59,7 +63,10 @@ class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstrac
|
|
|
59
63
|
else:
|
|
60
64
|
headers = auth_header
|
|
61
65
|
result = super()._http_request(
|
|
62
|
-
url,
|
|
66
|
+
url,
|
|
67
|
+
headers=headers,
|
|
68
|
+
data=None if data is None else _cached_data(),
|
|
69
|
+
raise_for_status=raise_for_status,
|
|
63
70
|
)
|
|
64
71
|
if self._store_credentials is not None:
|
|
65
72
|
self._store_credentials.approve()
|
|
@@ -452,7 +452,32 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
452
452
|
return sorted(ref[len(base) :] for ref in self.iter_refs(base))
|
|
453
453
|
|
|
454
454
|
def list_all_commits(self) -> Iterable[str]:
|
|
455
|
-
|
|
455
|
+
import pygit2
|
|
456
|
+
from pygit2.enums import SortMode
|
|
457
|
+
|
|
458
|
+
# Add HEAD
|
|
459
|
+
starting_points: list[Union[Oid, str]] = []
|
|
460
|
+
if not self.repo.head_is_unborn:
|
|
461
|
+
starting_points.append(self.repo.head.target)
|
|
462
|
+
|
|
463
|
+
# Add all branches, remotes, and tags
|
|
464
|
+
for ref in self.repo.references:
|
|
465
|
+
if ref.startswith(("refs/heads/", "refs/remotes/")):
|
|
466
|
+
oid = self.repo.revparse_single(ref).id
|
|
467
|
+
starting_points.append(oid)
|
|
468
|
+
elif ref.startswith("refs/tags/"):
|
|
469
|
+
tag_obj = self.repo.revparse_single(ref)
|
|
470
|
+
if isinstance(tag_obj, pygit2.Tag):
|
|
471
|
+
starting_points.append(tag_obj.target)
|
|
472
|
+
else:
|
|
473
|
+
starting_points.append(tag_obj.id)
|
|
474
|
+
|
|
475
|
+
# Walk all commits
|
|
476
|
+
walker = self.repo.walk(None)
|
|
477
|
+
for oid in starting_points:
|
|
478
|
+
walker.push(oid)
|
|
479
|
+
walker.sort(SortMode.TIME)
|
|
480
|
+
return [str(commit.id) for commit in walker]
|
|
456
481
|
|
|
457
482
|
def get_tree_obj(self, rev: str, **kwargs) -> Pygit2Object:
|
|
458
483
|
tree = self.repo[rev].tree
|
|
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
|
|
|
13
13
|
class LFSFilter(Filter):
|
|
14
14
|
attributes = "filter=*"
|
|
15
15
|
|
|
16
|
-
def __init__(self, *args, **kwargs):
|
|
16
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
17
17
|
self._smudge_buf: Optional[io.BytesIO] = None
|
|
18
18
|
self._smudge_root: Optional[str] = None
|
|
19
19
|
|
|
@@ -105,7 +105,7 @@ class GitCredentialHelper(CredentialHelper):
|
|
|
105
105
|
>>> password = credentials.password
|
|
106
106
|
"""
|
|
107
107
|
|
|
108
|
-
def __init__(self, command: str, use_http_path: bool = False):
|
|
108
|
+
def __init__(self, command: str, use_http_path: bool = False) -> None:
|
|
109
109
|
super().__init__()
|
|
110
110
|
self._command = command
|
|
111
111
|
self._run_kwargs: dict[str, Any] = {}
|
|
@@ -136,7 +136,7 @@ class GitCredentialHelper(CredentialHelper):
|
|
|
136
136
|
if not shutil.which(executable) and shutil.which("git"):
|
|
137
137
|
# If the helper cannot be found in PATH, it might be
|
|
138
138
|
# a C git helper in GIT_EXEC_PATH
|
|
139
|
-
git_exec_path = subprocess.check_output(
|
|
139
|
+
git_exec_path = subprocess.check_output(
|
|
140
140
|
("git", "--exec-path"),
|
|
141
141
|
text=True,
|
|
142
142
|
).strip()
|
|
@@ -353,7 +353,7 @@ def _input_tty(prompt: str = "Username: ") -> str:
|
|
|
353
353
|
class MemoryCredentialHelper(CredentialHelper):
|
|
354
354
|
"""Memory credential helper that supports optional interactive input."""
|
|
355
355
|
|
|
356
|
-
def __init__(self):
|
|
356
|
+
def __init__(self) -> None:
|
|
357
357
|
super().__init__()
|
|
358
358
|
self._credentials: dict[_CredentialKey, Credential] = {}
|
|
359
359
|
|
|
@@ -535,7 +535,7 @@ class Credential(Mapping[str, str]):
|
|
|
535
535
|
path: Optional[str] = None,
|
|
536
536
|
username: Optional[str] = None,
|
|
537
537
|
password: Optional[str] = None,
|
|
538
|
-
password_expiry_utc: Optional[
|
|
538
|
+
password_expiry_utc: Optional[str] = None,
|
|
539
539
|
url: Optional[str] = None,
|
|
540
540
|
):
|
|
541
541
|
self.protocol = protocol
|
|
@@ -17,7 +17,7 @@ def smudge(
|
|
|
17
17
|
batch_size: Optional[int] = None,
|
|
18
18
|
) -> BinaryIO:
|
|
19
19
|
"""Wrap the specified binary IO stream and run LFS smudge if necessary."""
|
|
20
|
-
reader = io.BufferedReader(fobj) # type: ignore[
|
|
20
|
+
reader = io.BufferedReader(fobj) # type: ignore[type-var]
|
|
21
21
|
data = reader.peek(100)
|
|
22
22
|
if any(data.startswith(header) for header in HEADERS):
|
|
23
23
|
# read the pointer data into memory since the raw stream is unseekable
|
|
@@ -4,7 +4,7 @@ from funcy import compose
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def code2desc(op_code):
|
|
7
|
-
from git import RootUpdateProgress as OP # noqa: N814
|
|
7
|
+
from git import RootUpdateProgress as OP # noqa: N814, TID251
|
|
8
8
|
|
|
9
9
|
ops = {
|
|
10
10
|
OP.COUNTING: "Counting",
|
|
@@ -44,16 +44,20 @@ class GitProgressEvent(NamedTuple):
|
|
|
44
44
|
|
|
45
45
|
class GitProgressReporter:
|
|
46
46
|
def __init__(self, fn) -> None:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
try:
|
|
48
|
+
from git.util import CallableRemoteProgress # noqa: TID251
|
|
49
|
+
except ImportError:
|
|
50
|
+
self._reporter = None
|
|
51
|
+
else:
|
|
52
|
+
self._reporter = CallableRemoteProgress(self.wrap_fn(fn))
|
|
50
53
|
|
|
51
54
|
def __call__(self, msg: Union[str, bytes]) -> None:
|
|
52
|
-
self._reporter
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
if self._reporter is not None:
|
|
56
|
+
self._reporter._parse_progress_line(
|
|
57
|
+
msg.decode("utf-8", errors="replace").strip()
|
|
58
|
+
if isinstance(msg, bytes)
|
|
59
|
+
else msg
|
|
60
|
+
)
|
|
57
61
|
|
|
58
62
|
@staticmethod
|
|
59
63
|
def wrap_fn(fn):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -17,7 +17,7 @@ Requires-Python: >=3.9
|
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: gitpython>3
|
|
20
|
-
Requires-Dist: dulwich>=0.
|
|
20
|
+
Requires-Dist: dulwich>=0.23.1
|
|
21
21
|
Requires-Dist: pygit2>=1.14.0
|
|
22
22
|
Requires-Dist: pygtrie>=2.3.2
|
|
23
23
|
Requires-Dist: fsspec[tqdm]>=2024.2.0
|
|
@@ -30,7 +30,7 @@ Provides-Extra: tests
|
|
|
30
30
|
Requires-Dist: aioresponses<0.8,>=0.7; extra == "tests"
|
|
31
31
|
Requires-Dist: paramiko<4,>=3.4.0; extra == "tests"
|
|
32
32
|
Requires-Dist: pytest<9,>=7; extra == "tests"
|
|
33
|
-
Requires-Dist: pytest-asyncio<
|
|
33
|
+
Requires-Dist: pytest-asyncio<2,>=0.23.2; extra == "tests"
|
|
34
34
|
Requires-Dist: pytest-cov>=4.1.0; extra == "tests"
|
|
35
35
|
Requires-Dist: pytest-docker<4,>=1; extra == "tests"
|
|
36
36
|
Requires-Dist: pytest-mock; extra == "tests"
|
|
@@ -38,7 +38,7 @@ Requires-Dist: pytest-sugar; extra == "tests"
|
|
|
38
38
|
Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
|
|
39
39
|
Requires-Dist: proxy.py; extra == "tests"
|
|
40
40
|
Provides-Extra: dev
|
|
41
|
-
Requires-Dist: mypy==1.
|
|
41
|
+
Requires-Dist: mypy==1.17.0; extra == "dev"
|
|
42
42
|
Requires-Dist: scmrepo[tests]; extra == "dev"
|
|
43
43
|
Requires-Dist: types-certifi; extra == "dev"
|
|
44
44
|
Requires-Dist: types-mock; extra == "dev"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
gitpython>3
|
|
2
|
-
dulwich>=0.
|
|
2
|
+
dulwich>=0.23.1
|
|
3
3
|
pygit2>=1.14.0
|
|
4
4
|
pygtrie>=2.3.2
|
|
5
5
|
fsspec[tqdm]>=2024.2.0
|
|
@@ -10,7 +10,7 @@ aiohttp-retry>=2.5.0
|
|
|
10
10
|
tqdm
|
|
11
11
|
|
|
12
12
|
[dev]
|
|
13
|
-
mypy==1.
|
|
13
|
+
mypy==1.17.0
|
|
14
14
|
scmrepo[tests]
|
|
15
15
|
types-certifi
|
|
16
16
|
types-mock
|
|
@@ -21,7 +21,7 @@ types-tqdm
|
|
|
21
21
|
aioresponses<0.8,>=0.7
|
|
22
22
|
paramiko<4,>=3.4.0
|
|
23
23
|
pytest<9,>=7
|
|
24
|
-
pytest-asyncio<
|
|
24
|
+
pytest-asyncio<2,>=0.23.2
|
|
25
25
|
pytest-cov>=4.1.0
|
|
26
26
|
pytest-docker<4,>=1
|
|
27
27
|
pytest-mock
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
|
+
import time
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Any, Optional
|
|
5
6
|
|
|
@@ -422,21 +423,170 @@ def test_iter_remote_refs(
|
|
|
422
423
|
} == set(git.iter_remote_refs(remote))
|
|
423
424
|
|
|
424
425
|
|
|
425
|
-
|
|
426
|
+
def _gen(scm: Git, s: str, commit_timestamp: Optional[float] = None) -> str:
|
|
427
|
+
with open(s, mode="w") as f:
|
|
428
|
+
f.write(s)
|
|
429
|
+
scm.dulwich.add([s])
|
|
430
|
+
scm.dulwich.repo.do_commit(
|
|
431
|
+
message=s.encode("utf-8"), commit_timestamp=commit_timestamp
|
|
432
|
+
)
|
|
433
|
+
return scm.get_rev()
|
|
434
|
+
|
|
435
|
+
|
|
426
436
|
def test_list_all_commits(tmp_dir: TmpDir, scm: Git, git: Git, matcher: type[Matcher]):
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
437
|
+
assert git.list_all_commits() == []
|
|
438
|
+
# https://github.com/libgit2/libgit2/issues/6336
|
|
439
|
+
now = time.time()
|
|
440
|
+
|
|
441
|
+
rev_a = _gen(scm, "a", commit_timestamp=now - 10)
|
|
442
|
+
rev_b = _gen(scm, "b", commit_timestamp=now - 8)
|
|
443
|
+
rev_c = _gen(scm, "c", commit_timestamp=now - 5)
|
|
444
|
+
rev_d = _gen(scm, "d", commit_timestamp=now - 2)
|
|
445
|
+
|
|
446
|
+
assert git.list_all_commits() == [rev_d, rev_c, rev_b, rev_a]
|
|
447
|
+
|
|
448
|
+
scm.gitpython.git.reset(rev_b, hard=True)
|
|
449
|
+
assert git.list_all_commits() == [rev_b, rev_a]
|
|
431
450
|
|
|
432
|
-
|
|
433
|
-
|
|
451
|
+
|
|
452
|
+
def test_list_all_commits_branch(
|
|
453
|
+
tmp_dir: TmpDir, scm: Git, git: Git, matcher: type[Matcher]
|
|
454
|
+
):
|
|
455
|
+
revs = {}
|
|
456
|
+
now = time.time()
|
|
457
|
+
|
|
458
|
+
revs["1"] = _gen(scm, "a", commit_timestamp=now - 10)
|
|
459
|
+
|
|
460
|
+
scm.checkout("branch", create_new=True)
|
|
461
|
+
revs["3"] = _gen(scm, "c", commit_timestamp=now - 9)
|
|
462
|
+
|
|
463
|
+
scm.checkout("master")
|
|
464
|
+
revs["2"] = _gen(scm, "b", commit_timestamp=now - 7)
|
|
465
|
+
|
|
466
|
+
scm.checkout("branch")
|
|
467
|
+
revs["5"] = _gen(scm, "e", commit_timestamp=now - 6)
|
|
468
|
+
|
|
469
|
+
scm.checkout("master")
|
|
470
|
+
revs["4"] = _gen(scm, "d", commit_timestamp=now - 5)
|
|
471
|
+
|
|
472
|
+
scm.checkout("branch")
|
|
473
|
+
revs["6"] = _gen(scm, "f", commit_timestamp=now - 4)
|
|
474
|
+
|
|
475
|
+
scm.checkout("master")
|
|
476
|
+
revs["7"] = _gen(scm, "g", commit_timestamp=now - 3)
|
|
477
|
+
revs["8"] = scm.merge("branch", msg="merge branch")
|
|
478
|
+
|
|
479
|
+
inv_map = {v: k for k, v in revs.items()}
|
|
480
|
+
assert [inv_map[k] for k in git.list_all_commits()] == [
|
|
481
|
+
"8",
|
|
482
|
+
"7",
|
|
483
|
+
"6",
|
|
484
|
+
"4",
|
|
485
|
+
"5",
|
|
486
|
+
"2",
|
|
487
|
+
"3",
|
|
488
|
+
"1",
|
|
489
|
+
]
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def test_list_all_tags(tmp_dir: TmpDir, scm: Git, git: Git, matcher: type[Matcher]):
|
|
493
|
+
rev_a = _gen(scm, "a")
|
|
434
494
|
scm.tag("tag")
|
|
435
|
-
|
|
495
|
+
rev_b = _gen(scm, "b")
|
|
496
|
+
scm.tag("annotated", annotated=True, message="Annotated Tag")
|
|
497
|
+
rev_c = _gen(scm, "c")
|
|
498
|
+
rev_d = _gen(scm, "d")
|
|
499
|
+
assert git.list_all_commits() == matcher.unordered(rev_d, rev_c, rev_b, rev_a)
|
|
500
|
+
|
|
501
|
+
rev_e = _gen(scm, "e")
|
|
502
|
+
scm.tag(
|
|
503
|
+
"annotated2",
|
|
504
|
+
target="refs/tags/annotated",
|
|
505
|
+
annotated=True,
|
|
506
|
+
message="Annotated Tag",
|
|
507
|
+
)
|
|
508
|
+
assert git.list_all_commits() == matcher.unordered(
|
|
509
|
+
rev_e, rev_d, rev_c, rev_b, rev_a
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
rev_f = _gen(scm, "f")
|
|
513
|
+
scm.tag(
|
|
514
|
+
"annotated3",
|
|
515
|
+
target="refs/tags/annotated2",
|
|
516
|
+
annotated=True,
|
|
517
|
+
message="Annotated Tag 3",
|
|
518
|
+
)
|
|
519
|
+
assert git.list_all_commits() == matcher.unordered(
|
|
520
|
+
rev_f, rev_e, rev_d, rev_c, rev_b, rev_a
|
|
521
|
+
)
|
|
522
|
+
|
|
436
523
|
scm.gitpython.git.reset(rev_a, hard=True)
|
|
437
|
-
|
|
524
|
+
assert git.list_all_commits() == matcher.unordered(rev_b, rev_a)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def test_list_all_commits_dangling_annotated_tag(tmp_dir: TmpDir, scm: Git, git: Git):
|
|
528
|
+
rev_a = _gen(scm, "a")
|
|
529
|
+
scm.tag("annotated", annotated=True, message="Annotated Tag")
|
|
530
|
+
|
|
531
|
+
_gen(scm, "b")
|
|
438
532
|
|
|
439
|
-
|
|
533
|
+
# Delete branch pointing to rev_a
|
|
534
|
+
scm.checkout(rev_a)
|
|
535
|
+
scm.gitpython.repo.delete_head("master", force=True)
|
|
536
|
+
|
|
537
|
+
assert git.list_all_commits() == [rev_a] # Only reachable via the tag
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def test_list_all_commits_orphan(
|
|
541
|
+
tmp_dir: TmpDir, scm: Git, git: Git, matcher: type[Matcher]
|
|
542
|
+
):
|
|
543
|
+
rev_a = _gen(scm, "a")
|
|
544
|
+
|
|
545
|
+
# Make an orphan branch
|
|
546
|
+
scm.gitpython.git.checkout("--orphan", "orphan-branch")
|
|
547
|
+
rev_orphan = _gen(scm, "orphanfile")
|
|
548
|
+
|
|
549
|
+
assert rev_orphan != rev_a
|
|
550
|
+
assert git.list_all_commits() == matcher.unordered(rev_orphan, rev_a)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def test_list_all_commits_refs(
|
|
554
|
+
tmp_dir: TmpDir, scm: Git, git: Git, matcher: type[Matcher]
|
|
555
|
+
):
|
|
556
|
+
assert git.list_all_commits() == []
|
|
557
|
+
|
|
558
|
+
rev_a = _gen(scm, "a")
|
|
559
|
+
|
|
560
|
+
assert git.list_all_commits() == [rev_a]
|
|
561
|
+
rev_b = _gen(scm, "b")
|
|
562
|
+
scm.set_ref("refs/remotes/origin/feature", rev_b)
|
|
563
|
+
assert git.list_all_commits() == matcher.unordered(rev_b, rev_a)
|
|
564
|
+
|
|
565
|
+
# also add refs/exps/foo/bar
|
|
566
|
+
rev_c = _gen(scm, "c")
|
|
567
|
+
scm.set_ref("refs/exps/foo/bar", rev_c)
|
|
568
|
+
assert git.list_all_commits() == matcher.unordered(rev_c, rev_b, rev_a)
|
|
569
|
+
|
|
570
|
+
# Dangling/broken ref ---
|
|
571
|
+
scm.set_ref("refs/heads/bad-ref", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
|
|
572
|
+
with pytest.raises(Exception): # noqa: B017, PT011
|
|
573
|
+
git.list_all_commits()
|
|
574
|
+
scm.remove_ref("refs/heads/bad-ref")
|
|
575
|
+
|
|
576
|
+
scm.gitpython.git.reset(rev_a, hard=True)
|
|
577
|
+
assert git.list_all_commits() == matcher.unordered(rev_b, rev_a)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def test_list_all_commits_detached_head(
|
|
581
|
+
tmp_dir: TmpDir, scm: Git, git: Git, matcher: type[Matcher]
|
|
582
|
+
):
|
|
583
|
+
rev_a = _gen(scm, "a")
|
|
584
|
+
rev_b = _gen(scm, "b")
|
|
585
|
+
rev_c = _gen(scm, "c")
|
|
586
|
+
scm.checkout(rev_b)
|
|
587
|
+
|
|
588
|
+
assert scm.pygit2.repo.head_is_detached
|
|
589
|
+
assert git.list_all_commits() == matcher.unordered(rev_c, rev_b, rev_a)
|
|
440
590
|
|
|
441
591
|
|
|
442
592
|
@pytest.mark.skip_git_backend("pygit2")
|
|
@@ -771,7 +921,7 @@ def test_ignored(tmp_dir: TmpDir, scm: Git, git: Git, git_backend: str):
|
|
|
771
921
|
assert not git.is_ignored(tmp_dir / "dir1" / "file2.txt")
|
|
772
922
|
|
|
773
923
|
|
|
774
|
-
@pytest.mark.skip_git_backend("pygit2", "gitpython")
|
|
924
|
+
@pytest.mark.skip_git_backend("pygit2", "gitpython", "dulwich")
|
|
775
925
|
def test_ignored_dir_unignored_subdirs(tmp_dir: TmpDir, scm: Git, git: Git):
|
|
776
926
|
tmp_dir.gen({".gitignore": "data/**\n!data/**/\n!data/**/*.csv"})
|
|
777
927
|
scm.add([".gitignore"])
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|