scmrepo 2.0.4__py3-none-any.whl → 2.1.1__py3-none-any.whl
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/asyn.py +3 -3
- scmrepo/fs.py +4 -4
- scmrepo/git/__init__.py +7 -11
- scmrepo/git/backend/base.py +4 -3
- scmrepo/git/backend/dulwich/__init__.py +17 -22
- scmrepo/git/backend/dulwich/asyncssh_vendor.py +6 -10
- scmrepo/git/backend/dulwich/client.py +5 -4
- scmrepo/git/backend/gitpython.py +7 -11
- scmrepo/git/backend/pygit2/__init__.py +22 -28
- scmrepo/git/backend/pygit2/callbacks.py +11 -6
- scmrepo/git/backend/pygit2/filter.py +2 -2
- scmrepo/git/config.py +4 -4
- scmrepo/git/credentials.py +9 -12
- scmrepo/git/lfs/client.py +30 -38
- scmrepo/git/lfs/fetch.py +9 -8
- scmrepo/git/lfs/object.py +2 -2
- scmrepo/git/lfs/pointer.py +2 -2
- scmrepo/git/lfs/progress.py +2 -2
- scmrepo/git/lfs/storage.py +2 -1
- scmrepo/git/objects.py +3 -2
- {scmrepo-2.0.4.dist-info → scmrepo-2.1.1.dist-info}/METADATA +5 -5
- scmrepo-2.1.1.dist-info/RECORD +37 -0
- scmrepo-2.0.4.dist-info/RECORD +0 -37
- {scmrepo-2.0.4.dist-info → scmrepo-2.1.1.dist-info}/LICENSE +0 -0
- {scmrepo-2.0.4.dist-info → scmrepo-2.1.1.dist-info}/WHEEL +0 -0
- {scmrepo-2.0.4.dist-info → scmrepo-2.1.1.dist-info}/top_level.txt +0 -0
scmrepo/asyn.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import asyncio
|
|
3
3
|
import os
|
|
4
4
|
import threading
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Optional
|
|
6
6
|
|
|
7
7
|
from fsspec.asyn import ( # noqa: F401, pylint:disable=unused-import
|
|
8
8
|
_selector_policy,
|
|
@@ -11,9 +11,9 @@ from fsspec.asyn import ( # noqa: F401, pylint:disable=unused-import
|
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
# dedicated async IO thread
|
|
14
|
-
iothread:
|
|
14
|
+
iothread: list[Optional[threading.Thread]] = [None]
|
|
15
15
|
# global DVC event loop
|
|
16
|
-
default_loop:
|
|
16
|
+
default_loop: list[Optional[asyncio.AbstractEventLoop]] = [None]
|
|
17
17
|
lock = threading.Lock()
|
|
18
18
|
|
|
19
19
|
|
scmrepo/fs.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import errno
|
|
2
2
|
import os
|
|
3
3
|
import posixpath
|
|
4
|
-
from typing import TYPE_CHECKING, Any, BinaryIO, Callable,
|
|
4
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Callable, Optional
|
|
5
5
|
|
|
6
6
|
from fsspec.callbacks import _DEFAULT_CALLBACK
|
|
7
7
|
from fsspec.spec import AbstractFileSystem
|
|
@@ -169,7 +169,7 @@ class GitFileSystem(AbstractFileSystem):
|
|
|
169
169
|
def as_posix(cls, path):
|
|
170
170
|
return path
|
|
171
171
|
|
|
172
|
-
def _get_key(self, path: str) ->
|
|
172
|
+
def _get_key(self, path: str) -> tuple[str, ...]:
|
|
173
173
|
path = self.abspath(path)
|
|
174
174
|
if path == self.root_marker:
|
|
175
175
|
return ()
|
|
@@ -184,7 +184,7 @@ class GitFileSystem(AbstractFileSystem):
|
|
|
184
184
|
mode: str = "rb",
|
|
185
185
|
block_size: Optional[int] = None,
|
|
186
186
|
autocommit: bool = True,
|
|
187
|
-
cache_options: Optional[
|
|
187
|
+
cache_options: Optional[dict] = None,
|
|
188
188
|
raw: bool = False,
|
|
189
189
|
**kwargs: Any,
|
|
190
190
|
) -> BinaryIO:
|
|
@@ -204,7 +204,7 @@ class GitFileSystem(AbstractFileSystem):
|
|
|
204
204
|
errno.EISDIR, os.strerror(errno.EISDIR), path
|
|
205
205
|
) from exc
|
|
206
206
|
|
|
207
|
-
def info(self, path: str, **kwargs: Any) ->
|
|
207
|
+
def info(self, path: str, **kwargs: Any) -> dict[str, Any]:
|
|
208
208
|
key = self._get_key(path)
|
|
209
209
|
try:
|
|
210
210
|
# NOTE: to avoid wasting time computing object size, trie.info
|
scmrepo/git/__init__.py
CHANGED
|
@@ -5,18 +5,14 @@ import os
|
|
|
5
5
|
import re
|
|
6
6
|
import typing
|
|
7
7
|
from collections import OrderedDict
|
|
8
|
-
from collections.abc import Mapping
|
|
8
|
+
from collections.abc import Iterable, Mapping
|
|
9
9
|
from contextlib import contextmanager
|
|
10
10
|
from functools import partialmethod
|
|
11
11
|
from typing import (
|
|
12
12
|
TYPE_CHECKING,
|
|
13
13
|
Callable,
|
|
14
14
|
ClassVar,
|
|
15
|
-
Dict,
|
|
16
|
-
Iterable,
|
|
17
15
|
Optional,
|
|
18
|
-
Tuple,
|
|
19
|
-
Type,
|
|
20
16
|
Union,
|
|
21
17
|
)
|
|
22
18
|
|
|
@@ -44,14 +40,14 @@ if TYPE_CHECKING:
|
|
|
44
40
|
|
|
45
41
|
logger = logging.getLogger(__name__)
|
|
46
42
|
|
|
47
|
-
BackendCls =
|
|
43
|
+
BackendCls = type[BaseGitBackend]
|
|
48
44
|
|
|
49
45
|
|
|
50
46
|
_LOW_PRIO_BACKENDS = ("gitpython",)
|
|
51
47
|
|
|
52
48
|
|
|
53
49
|
class GitBackends(Mapping):
|
|
54
|
-
DEFAULT: ClassVar[
|
|
50
|
+
DEFAULT: ClassVar[dict[str, BackendCls]] = {
|
|
55
51
|
"dulwich": DulwichBackend,
|
|
56
52
|
"pygit2": Pygit2Backend,
|
|
57
53
|
"gitpython": GitPythonBackend,
|
|
@@ -72,7 +68,7 @@ class GitBackends(Mapping):
|
|
|
72
68
|
selected = selected or list(self.DEFAULT)
|
|
73
69
|
self.backends = OrderedDict((key, self.DEFAULT[key]) for key in selected)
|
|
74
70
|
|
|
75
|
-
self.initialized:
|
|
71
|
+
self.initialized: dict[str, BaseGitBackend] = {}
|
|
76
72
|
|
|
77
73
|
self.args = args
|
|
78
74
|
self.kwargs = kwargs
|
|
@@ -169,7 +165,7 @@ class Git(Base):
|
|
|
169
165
|
return rev and cls.RE_HEXSHA.search(rev)
|
|
170
166
|
|
|
171
167
|
@classmethod
|
|
172
|
-
def split_ref_pattern(cls, ref: str) ->
|
|
168
|
+
def split_ref_pattern(cls, ref: str) -> tuple[str, str]:
|
|
173
169
|
name = cls.BAD_REF_CHARS_RE.split(ref, maxsplit=1)[0]
|
|
174
170
|
return name, ref[len(name) :]
|
|
175
171
|
|
|
@@ -492,8 +488,8 @@ class Git(Base):
|
|
|
492
488
|
base: Optional[str] = None,
|
|
493
489
|
match: Optional[str] = None,
|
|
494
490
|
exclude: Optional[str] = None,
|
|
495
|
-
) ->
|
|
496
|
-
results:
|
|
491
|
+
) -> dict[str, Optional[str]]:
|
|
492
|
+
results: dict[str, Optional[str]] = {}
|
|
497
493
|
remained_revs = set()
|
|
498
494
|
if base == "refs/heads":
|
|
499
495
|
current_rev = self.get_rev()
|
scmrepo/git/backend/base.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
+
from collections.abc import Iterable, Mapping
|
|
3
4
|
from enum import Enum
|
|
4
|
-
from typing import TYPE_CHECKING, Callable,
|
|
5
|
+
from typing import TYPE_CHECKING, Callable, Optional, Union
|
|
5
6
|
|
|
6
7
|
from scmrepo.exceptions import SCMError
|
|
7
8
|
from scmrepo.git.objects import GitObject
|
|
@@ -281,7 +282,7 @@ class BaseGitBackend(ABC):
|
|
|
281
282
|
ref: str,
|
|
282
283
|
message: Optional[str] = None,
|
|
283
284
|
include_untracked: bool = False,
|
|
284
|
-
) ->
|
|
285
|
+
) -> tuple[Optional[str], bool]:
|
|
285
286
|
"""Push a commit onto the specified stash.
|
|
286
287
|
|
|
287
288
|
Returns a tuple of the form (rev, need_reset) where need_reset
|
|
@@ -347,7 +348,7 @@ class BaseGitBackend(ABC):
|
|
|
347
348
|
@abstractmethod
|
|
348
349
|
def status(
|
|
349
350
|
self, ignored: bool = False, untracked_files: str = "all"
|
|
350
|
-
) ->
|
|
351
|
+
) -> tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
|
|
351
352
|
"""Return tuple of (staged_files, unstaged_files, untracked_files).
|
|
352
353
|
|
|
353
354
|
staged_files will be a dict mapping status (add, delete, modify) to a
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
6
|
import stat
|
|
7
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
7
8
|
from contextlib import closing
|
|
8
9
|
from functools import partial
|
|
9
10
|
from io import BytesIO, StringIO
|
|
@@ -11,13 +12,7 @@ from typing import (
|
|
|
11
12
|
TYPE_CHECKING,
|
|
12
13
|
Any,
|
|
13
14
|
Callable,
|
|
14
|
-
Dict,
|
|
15
|
-
Iterable,
|
|
16
|
-
Iterator,
|
|
17
|
-
List,
|
|
18
|
-
Mapping,
|
|
19
15
|
Optional,
|
|
20
|
-
Tuple,
|
|
21
16
|
Union,
|
|
22
17
|
)
|
|
23
18
|
|
|
@@ -151,18 +146,18 @@ class DulwichConfig(Config):
|
|
|
151
146
|
return self._config.encoding
|
|
152
147
|
return self._config.backends[0].encoding
|
|
153
148
|
|
|
154
|
-
def get(self, section:
|
|
149
|
+
def get(self, section: tuple[str, ...], name: str) -> str:
|
|
155
150
|
"""Return the specified setting as a string."""
|
|
156
151
|
return self._config.get(section, name).decode(self.encoding)
|
|
157
152
|
|
|
158
|
-
def get_bool(self, section:
|
|
153
|
+
def get_bool(self, section: tuple[str, ...], name: str) -> bool:
|
|
159
154
|
"""Return the specified setting as a boolean."""
|
|
160
155
|
value = self._config.get_boolean(section, name)
|
|
161
156
|
if value is None:
|
|
162
157
|
raise ValueError("setting is not a valid boolean")
|
|
163
158
|
return value
|
|
164
159
|
|
|
165
|
-
def get_multivar(self, section:
|
|
160
|
+
def get_multivar(self, section: tuple[str, ...], name: str) -> Iterator[str]:
|
|
166
161
|
"""Iterate over string values in the specified multivar setting."""
|
|
167
162
|
for value in self._config.get_multivar(section, name):
|
|
168
163
|
yield value.decode(self.encoding)
|
|
@@ -199,17 +194,17 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
199
194
|
except NotGitRepository as exc:
|
|
200
195
|
raise SCMError(f"{root_dir} is not a git repository") from exc
|
|
201
196
|
|
|
202
|
-
self._submodules:
|
|
197
|
+
self._submodules: dict[str, str] = self._find_submodules()
|
|
203
198
|
self._stashes: dict = {}
|
|
204
199
|
|
|
205
|
-
def _find_submodules(self) ->
|
|
200
|
+
def _find_submodules(self) -> dict[str, str]:
|
|
206
201
|
"""Return dict mapping submodule names to submodule paths.
|
|
207
202
|
|
|
208
203
|
Submodule paths will be relative to Git repo root.
|
|
209
204
|
"""
|
|
210
205
|
from dulwich.config import ConfigFile, parse_submodules
|
|
211
206
|
|
|
212
|
-
submodules:
|
|
207
|
+
submodules: dict[str, str] = {}
|
|
213
208
|
config_path = os.path.join(self.root_dir, ".gitmodules")
|
|
214
209
|
if os.path.isfile(config_path):
|
|
215
210
|
config = ConfigFile.from_path(config_path)
|
|
@@ -332,7 +327,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
332
327
|
self.repo.stage(list(self.repo.open_index()))
|
|
333
328
|
return
|
|
334
329
|
|
|
335
|
-
files:
|
|
330
|
+
files: list[bytes] = [
|
|
336
331
|
os.fsencode(fpath) for fpath in self._expand_paths(paths, force=force)
|
|
337
332
|
]
|
|
338
333
|
if update:
|
|
@@ -348,7 +343,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
348
343
|
else:
|
|
349
344
|
self.repo.stage(files)
|
|
350
345
|
|
|
351
|
-
def _expand_paths(self, paths:
|
|
346
|
+
def _expand_paths(self, paths: list[str], force: bool = False) -> Iterator[str]:
|
|
352
347
|
for path in paths:
|
|
353
348
|
if not os.path.isabs(path) and self._submodules:
|
|
354
349
|
# NOTE: If path is inside a submodule, Dulwich expects the
|
|
@@ -459,7 +454,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
459
454
|
return any(p == rel or p.startswith(rel_dir) for p in self.repo.open_index())
|
|
460
455
|
|
|
461
456
|
def is_dirty(self, untracked_files: bool = False) -> bool:
|
|
462
|
-
kwargs:
|
|
457
|
+
kwargs: dict[str, Any] = {} if untracked_files else {"untracked_files": "no"}
|
|
463
458
|
return any(self.status(**kwargs))
|
|
464
459
|
|
|
465
460
|
def active_branch(self) -> str:
|
|
@@ -707,9 +702,9 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
707
702
|
fetch_refs = []
|
|
708
703
|
|
|
709
704
|
def determine_wants(
|
|
710
|
-
remote_refs:
|
|
705
|
+
remote_refs: dict[bytes, bytes],
|
|
711
706
|
depth: Optional[int] = None, # pylint: disable=unused-argument
|
|
712
|
-
) ->
|
|
707
|
+
) -> list[bytes]:
|
|
713
708
|
fetch_refs.extend(
|
|
714
709
|
parse_reftuples(
|
|
715
710
|
DictRefsContainer(remote_refs),
|
|
@@ -782,7 +777,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
782
777
|
ref: str,
|
|
783
778
|
message: Optional[str] = None,
|
|
784
779
|
include_untracked: bool = False,
|
|
785
|
-
) ->
|
|
780
|
+
) -> tuple[Optional[str], bool]:
|
|
786
781
|
from dulwich.repo import InvalidUserIdentity
|
|
787
782
|
|
|
788
783
|
from scmrepo.git import Stash
|
|
@@ -836,8 +831,8 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
836
831
|
) -> Mapping[str, Optional[str]]:
|
|
837
832
|
if not base:
|
|
838
833
|
base = "refs/tags"
|
|
839
|
-
rev_mapping:
|
|
840
|
-
results:
|
|
834
|
+
rev_mapping: dict[str, Optional[str]] = {}
|
|
835
|
+
results: dict[str, Optional[str]] = {}
|
|
841
836
|
for ref in self.iter_refs(base=base):
|
|
842
837
|
if (match and not fnmatch.fnmatch(ref, match)) or (
|
|
843
838
|
exclude and fnmatch.fnmatch(ref, exclude)
|
|
@@ -877,7 +872,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
877
872
|
|
|
878
873
|
def status(
|
|
879
874
|
self, ignored: bool = False, untracked_files: str = "all"
|
|
880
|
-
) ->
|
|
875
|
+
) -> tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
|
|
881
876
|
from dulwich.porcelain import Error
|
|
882
877
|
from dulwich.porcelain import status as git_status
|
|
883
878
|
|
|
@@ -978,7 +973,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
978
973
|
_IDENTITY_RE = re.compile(r"(?P<name>.+)\s+<(?P<email>.+)>")
|
|
979
974
|
|
|
980
975
|
|
|
981
|
-
def _parse_identity(identity: str) ->
|
|
976
|
+
def _parse_identity(identity: str) -> tuple[str, str]:
|
|
982
977
|
m = _IDENTITY_RE.match(identity)
|
|
983
978
|
if not m:
|
|
984
979
|
raise SCMError("Could not parse tagger identity '{identity}'")
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"""asyncssh SSH vendor for Dulwich."""
|
|
2
2
|
import asyncio
|
|
3
3
|
import os
|
|
4
|
+
from collections.abc import Coroutine, Iterator, Sequence
|
|
4
5
|
from typing import (
|
|
5
6
|
TYPE_CHECKING,
|
|
6
7
|
Callable,
|
|
7
|
-
Coroutine,
|
|
8
|
-
Dict,
|
|
9
|
-
Iterator,
|
|
10
|
-
List,
|
|
11
8
|
Optional,
|
|
12
|
-
Sequence,
|
|
13
9
|
cast,
|
|
14
10
|
)
|
|
15
11
|
|
|
@@ -47,7 +43,7 @@ class _StderrWrapper:
|
|
|
47
43
|
self.stderr = stderr
|
|
48
44
|
self.loop = loop
|
|
49
45
|
|
|
50
|
-
async def _readlines(self) ->
|
|
46
|
+
async def _readlines(self) -> list[bytes]:
|
|
51
47
|
lines = []
|
|
52
48
|
while True:
|
|
53
49
|
line = await self.stderr.readline()
|
|
@@ -140,12 +136,12 @@ def _process_public_key_ok_gh(self, _pkttype, _pktid, packet):
|
|
|
140
136
|
|
|
141
137
|
class InteractiveSSHClient(SSHClient):
|
|
142
138
|
_conn: Optional["SSHClientConnection"] = None
|
|
143
|
-
_keys_to_try: Optional[
|
|
144
|
-
_passphrases:
|
|
139
|
+
_keys_to_try: Optional[list["FilePath"]] = None
|
|
140
|
+
_passphrases: dict[str, str]
|
|
145
141
|
|
|
146
142
|
def __init__(self, *args, **kwargs):
|
|
147
143
|
super(*args, **kwargs)
|
|
148
|
-
_passphrases:
|
|
144
|
+
_passphrases: dict[str, str] = {}
|
|
149
145
|
|
|
150
146
|
def connection_made(self, conn: "SSHClientConnection") -> None:
|
|
151
147
|
self._conn = conn
|
|
@@ -267,7 +263,7 @@ class AsyncSSHVendor(BaseAsyncObject, SSHVendor):
|
|
|
267
263
|
async def _run_command(
|
|
268
264
|
self,
|
|
269
265
|
host: str,
|
|
270
|
-
command:
|
|
266
|
+
command: list[str],
|
|
271
267
|
username: Optional[str] = None,
|
|
272
268
|
port: Optional[int] = None,
|
|
273
269
|
password: Optional[str] = None,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from typing import Optional, Union
|
|
2
3
|
|
|
3
4
|
from dulwich.client import HTTPUnauthorized, Urllib3HttpGitClient
|
|
4
5
|
|
|
@@ -26,10 +27,10 @@ class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstrac
|
|
|
26
27
|
def _http_request(
|
|
27
28
|
self,
|
|
28
29
|
url: str,
|
|
29
|
-
headers: Optional[
|
|
30
|
+
headers: Optional[dict[str, str]] = None,
|
|
30
31
|
data: Optional[Union[bytes, Iterator[bytes]]] = None,
|
|
31
32
|
):
|
|
32
|
-
cached_chunks:
|
|
33
|
+
cached_chunks: list[bytes] = []
|
|
33
34
|
|
|
34
35
|
def _cached_data() -> Iterator[bytes]:
|
|
35
36
|
assert data is not None
|
|
@@ -64,7 +65,7 @@ class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstrac
|
|
|
64
65
|
self._store_credentials.approve()
|
|
65
66
|
return result
|
|
66
67
|
|
|
67
|
-
def _get_auth(self) ->
|
|
68
|
+
def _get_auth(self) -> dict[str, str]:
|
|
68
69
|
from urllib3.util import make_headers
|
|
69
70
|
|
|
70
71
|
try:
|
scmrepo/git/backend/gitpython.py
CHANGED
|
@@ -4,17 +4,13 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
6
|
import sys
|
|
7
|
+
from collections.abc import Iterable, Mapping
|
|
7
8
|
from functools import partial, wraps
|
|
8
9
|
from typing import (
|
|
9
10
|
TYPE_CHECKING,
|
|
10
11
|
Any,
|
|
11
12
|
Callable,
|
|
12
|
-
Dict,
|
|
13
|
-
Iterable,
|
|
14
|
-
List,
|
|
15
|
-
Mapping,
|
|
16
13
|
Optional,
|
|
17
|
-
Tuple,
|
|
18
14
|
Union,
|
|
19
15
|
)
|
|
20
16
|
|
|
@@ -63,7 +59,7 @@ def is_binary() -> bool:
|
|
|
63
59
|
return getattr(sys, "frozen", False)
|
|
64
60
|
|
|
65
61
|
|
|
66
|
-
def fix_env(env: Optional[
|
|
62
|
+
def fix_env(env: Optional[dict[str, str]] = None) -> dict[str, str]:
|
|
67
63
|
if env is None:
|
|
68
64
|
environ = os.environ.copy()
|
|
69
65
|
else:
|
|
@@ -257,7 +253,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
257
253
|
if update or not force:
|
|
258
254
|
# NOTE: git-python index.add() defines force parameter but
|
|
259
255
|
# ignores it (index.add() behavior is always force=True)
|
|
260
|
-
kwargs:
|
|
256
|
+
kwargs: dict[str, Any] = {}
|
|
261
257
|
if update:
|
|
262
258
|
kwargs["update"] = True
|
|
263
259
|
if isinstance(paths, str):
|
|
@@ -573,7 +569,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
573
569
|
ref: str,
|
|
574
570
|
message: Optional[str] = None,
|
|
575
571
|
include_untracked: bool = False,
|
|
576
|
-
) ->
|
|
572
|
+
) -> tuple[Optional[str], bool]:
|
|
577
573
|
from scmrepo.git import Stash
|
|
578
574
|
|
|
579
575
|
if not self.is_dirty(untracked_files=include_untracked):
|
|
@@ -651,7 +647,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
651
647
|
|
|
652
648
|
def reset(self, hard: bool = False, paths: Optional[Iterable[str]] = None):
|
|
653
649
|
if paths:
|
|
654
|
-
paths_list: Optional[
|
|
650
|
+
paths_list: Optional[list[str]] = [
|
|
655
651
|
relpath(path, self.root_dir) for path in paths
|
|
656
652
|
]
|
|
657
653
|
if os.name == "nt":
|
|
@@ -684,7 +680,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
684
680
|
self.repo.git.checkout(*args)
|
|
685
681
|
else:
|
|
686
682
|
if paths:
|
|
687
|
-
paths_list: Optional[
|
|
683
|
+
paths_list: Optional[list[str]] = [
|
|
688
684
|
relpath(path, self.root_dir) for path in paths
|
|
689
685
|
]
|
|
690
686
|
if os.name == "nt":
|
|
@@ -698,7 +694,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
698
694
|
|
|
699
695
|
def status(
|
|
700
696
|
self, ignored: bool = False, untracked_files: str = "all"
|
|
701
|
-
) ->
|
|
697
|
+
) -> tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
|
|
702
698
|
raise NotImplementedError
|
|
703
699
|
|
|
704
700
|
def merge(
|
|
@@ -2,19 +2,13 @@ import locale
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
import stat
|
|
5
|
+
from collections.abc import Generator, Iterable, Iterator, Mapping
|
|
5
6
|
from contextlib import contextmanager
|
|
6
7
|
from io import BytesIO, StringIO, TextIOWrapper
|
|
7
8
|
from typing import (
|
|
8
9
|
TYPE_CHECKING,
|
|
9
10
|
Callable,
|
|
10
|
-
Dict,
|
|
11
|
-
Generator,
|
|
12
|
-
Iterable,
|
|
13
|
-
Iterator,
|
|
14
|
-
List,
|
|
15
|
-
Mapping,
|
|
16
11
|
Optional,
|
|
17
|
-
Tuple,
|
|
18
12
|
Union,
|
|
19
13
|
)
|
|
20
14
|
from urllib.parse import urlparse
|
|
@@ -39,7 +33,7 @@ logger = logging.getLogger(__name__)
|
|
|
39
33
|
if TYPE_CHECKING:
|
|
40
34
|
from pygit2 import Commit, Oid, Signature
|
|
41
35
|
from pygit2.config import Config as _Pygit2Config
|
|
42
|
-
from pygit2.
|
|
36
|
+
from pygit2.remotes import Remote
|
|
43
37
|
from pygit2.repository import Repository
|
|
44
38
|
|
|
45
39
|
from scmrepo.progress import GitProgressEvent
|
|
@@ -54,7 +48,7 @@ class Pygit2Object(GitObject):
|
|
|
54
48
|
self,
|
|
55
49
|
mode: str = "r",
|
|
56
50
|
encoding: Optional[str] = None,
|
|
57
|
-
key: Optional[
|
|
51
|
+
key: Optional[tuple[str, ...]] = None,
|
|
58
52
|
raw: bool = True,
|
|
59
53
|
rev: Optional[str] = None,
|
|
60
54
|
**kwargs,
|
|
@@ -124,13 +118,13 @@ class Pygit2Config(Config):
|
|
|
124
118
|
def __init__(self, config: "_Pygit2Config"):
|
|
125
119
|
self._config = config
|
|
126
120
|
|
|
127
|
-
def _key(self, section:
|
|
121
|
+
def _key(self, section: tuple[str, ...], name: str) -> str:
|
|
128
122
|
return ".".join((*section, name))
|
|
129
123
|
|
|
130
|
-
def get(self, section:
|
|
124
|
+
def get(self, section: tuple[str, ...], name: str) -> str:
|
|
131
125
|
return self._config[self._key(section, name)]
|
|
132
126
|
|
|
133
|
-
def get_bool(self, section:
|
|
127
|
+
def get_bool(self, section: tuple[str, ...], name: str) -> bool:
|
|
134
128
|
from pygit2 import GitError
|
|
135
129
|
|
|
136
130
|
try:
|
|
@@ -138,7 +132,7 @@ class Pygit2Config(Config):
|
|
|
138
132
|
except GitError as exc:
|
|
139
133
|
raise ValueError("invalid boolean config entry") from exc
|
|
140
134
|
|
|
141
|
-
def get_multivar(self, section:
|
|
135
|
+
def get_multivar(self, section: tuple[str, ...], name: str) -> Iterator[str]:
|
|
142
136
|
from pygit2 import GitError
|
|
143
137
|
|
|
144
138
|
try:
|
|
@@ -652,13 +646,13 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
652
646
|
except KeyError as exc:
|
|
653
647
|
raise SCMError(f"'{url}' is not a valid Git remote or URL") from exc
|
|
654
648
|
|
|
655
|
-
parsed = urlparse(url)
|
|
656
|
-
if parsed.scheme in ("git", "git+ssh", "ssh") or url.startswith("git@"):
|
|
657
|
-
raise NotImplementedError
|
|
658
649
|
if os.name == "nt" and url.startswith("file://"):
|
|
659
650
|
url = url[len("file://") :]
|
|
660
|
-
|
|
661
|
-
|
|
651
|
+
remote = self.repo.remotes.create_anonymous(url)
|
|
652
|
+
parsed = urlparse(remote.url)
|
|
653
|
+
if parsed.scheme in ("git", "git+ssh", "ssh") or remote.url.startswith("git@"):
|
|
654
|
+
raise NotImplementedError
|
|
655
|
+
yield remote
|
|
662
656
|
|
|
663
657
|
def fetch_refspecs(
|
|
664
658
|
self,
|
|
@@ -681,7 +675,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
681
675
|
# when a ref was rejected so we have to determine whether no callback
|
|
682
676
|
# means up to date or rejected
|
|
683
677
|
def _default_status(
|
|
684
|
-
src: str, dst: str, remote_refs:
|
|
678
|
+
src: str, dst: str, remote_refs: dict[str, "Oid"]
|
|
685
679
|
) -> SyncStatus:
|
|
686
680
|
try:
|
|
687
681
|
if remote_refs[src] != self.repo.references[dst].target:
|
|
@@ -698,7 +692,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
698
692
|
SCMError(f"Git failed to fetch ref from '{url}'"),
|
|
699
693
|
):
|
|
700
694
|
with RemoteCallbacks(progress=progress) as cb:
|
|
701
|
-
remote_refs:
|
|
695
|
+
remote_refs: dict[str, "Oid"] = (
|
|
702
696
|
{
|
|
703
697
|
head["name"]: head["oid"]
|
|
704
698
|
for head in remote.ls_remotes(callbacks=cb)
|
|
@@ -712,7 +706,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
712
706
|
message="fetch",
|
|
713
707
|
)
|
|
714
708
|
|
|
715
|
-
result:
|
|
709
|
+
result: dict[str, "SyncStatus"] = {}
|
|
716
710
|
for refspec in refspecs:
|
|
717
711
|
lh, rh = refspec.split(":")
|
|
718
712
|
if lh.endswith("*"):
|
|
@@ -735,7 +729,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
735
729
|
def _refspecs_list(
|
|
736
730
|
refspecs: Union[str, Iterable[str]],
|
|
737
731
|
force: bool = False,
|
|
738
|
-
) ->
|
|
732
|
+
) -> list[str]:
|
|
739
733
|
if isinstance(refspecs, str):
|
|
740
734
|
if force and not refspecs.startswith("+"):
|
|
741
735
|
refspecs = f"+{refspecs}"
|
|
@@ -755,7 +749,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
755
749
|
ref: str,
|
|
756
750
|
message: Optional[str] = None,
|
|
757
751
|
include_untracked: bool = False,
|
|
758
|
-
) ->
|
|
752
|
+
) -> tuple[Optional[str], bool]:
|
|
759
753
|
from scmrepo.git import Stash
|
|
760
754
|
|
|
761
755
|
try:
|
|
@@ -880,7 +874,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
880
874
|
|
|
881
875
|
index = self.repo.index
|
|
882
876
|
if paths:
|
|
883
|
-
path_list: Optional[
|
|
877
|
+
path_list: Optional[list[str]] = [
|
|
884
878
|
relpath(path, self.root_dir) for path in paths
|
|
885
879
|
]
|
|
886
880
|
if os.name == "nt":
|
|
@@ -911,7 +905,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
911
905
|
|
|
912
906
|
def status(
|
|
913
907
|
self, ignored: bool = False, untracked_files: str = "all"
|
|
914
|
-
) ->
|
|
908
|
+
) -> tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
|
|
915
909
|
from pygit2 import (
|
|
916
910
|
GIT_STATUS_IGNORED,
|
|
917
911
|
GIT_STATUS_INDEX_DELETED,
|
|
@@ -925,13 +919,13 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
925
919
|
GIT_STATUS_WT_UNREADABLE,
|
|
926
920
|
)
|
|
927
921
|
|
|
928
|
-
staged: Mapping[str,
|
|
922
|
+
staged: Mapping[str, list[str]] = {
|
|
929
923
|
"add": [],
|
|
930
924
|
"delete": [],
|
|
931
925
|
"modify": [],
|
|
932
926
|
}
|
|
933
|
-
unstaged:
|
|
934
|
-
untracked:
|
|
927
|
+
unstaged: list[str] = []
|
|
928
|
+
untracked: list[str] = []
|
|
935
929
|
|
|
936
930
|
states = {
|
|
937
931
|
GIT_STATUS_WT_NEW: untracked,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from contextlib import AbstractContextManager
|
|
2
2
|
from types import TracebackType
|
|
3
|
-
from typing import TYPE_CHECKING, Callable,
|
|
3
|
+
from typing import TYPE_CHECKING, Callable, Optional, Union
|
|
4
4
|
|
|
5
5
|
from pygit2 import RemoteCallbacks as _RemoteCallbacks
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ from scmrepo.progress import GitProgressReporter
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from pygit2 import Oid
|
|
13
13
|
from pygit2.credentials import Keypair, Username, UserPass
|
|
14
|
+
from pygit2.enums import CredentialType
|
|
14
15
|
|
|
15
16
|
from scmrepo.progress import GitProgressEvent
|
|
16
17
|
|
|
@@ -29,11 +30,11 @@ class RemoteCallbacks(_RemoteCallbacks, AbstractContextManager):
|
|
|
29
30
|
self.progress = GitProgressReporter(progress) if progress else None
|
|
30
31
|
self._store_credentials: Optional["Credential"] = None
|
|
31
32
|
self._tried_credentials = False
|
|
32
|
-
self.result:
|
|
33
|
+
self.result: dict[str, SyncStatus] = {}
|
|
33
34
|
|
|
34
35
|
def __exit__(
|
|
35
36
|
self,
|
|
36
|
-
exc_type: Optional[
|
|
37
|
+
exc_type: Optional[type[BaseException]],
|
|
37
38
|
exc_value: Optional[BaseException],
|
|
38
39
|
traceback: Optional[TracebackType],
|
|
39
40
|
):
|
|
@@ -45,16 +46,20 @@ class RemoteCallbacks(_RemoteCallbacks, AbstractContextManager):
|
|
|
45
46
|
self.progress(string)
|
|
46
47
|
|
|
47
48
|
def credentials(
|
|
48
|
-
self,
|
|
49
|
+
self,
|
|
50
|
+
url: str,
|
|
51
|
+
username_from_url: Optional[str],
|
|
52
|
+
allowed_types: "CredentialType",
|
|
49
53
|
) -> "_Pygit2Credential":
|
|
50
54
|
from pygit2 import GitError, Passthrough
|
|
51
|
-
from pygit2.credentials import
|
|
55
|
+
from pygit2.credentials import UserPass
|
|
56
|
+
from pygit2.enums import CredentialType
|
|
52
57
|
|
|
53
58
|
if self._tried_credentials:
|
|
54
59
|
raise GitError(f"authentication failed for '{url}'")
|
|
55
60
|
self._tried_credentials = True
|
|
56
61
|
|
|
57
|
-
if allowed_types &
|
|
62
|
+
if allowed_types & CredentialType.USERPASS_PLAINTEXT:
|
|
58
63
|
try:
|
|
59
64
|
if self._store_credentials:
|
|
60
65
|
creds = self._store_credentials
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import logging
|
|
3
|
-
from typing import TYPE_CHECKING, Callable,
|
|
3
|
+
from typing import TYPE_CHECKING, Callable, Optional
|
|
4
4
|
|
|
5
5
|
from pygit2 import GIT_FILTER_CLEAN, Filter, Passthrough
|
|
6
6
|
|
|
@@ -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:
|
|
20
|
+
def check(self, src: "FilterSource", attr_values: list[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
|
scmrepo/git/config.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""git config convenience wrapper."""
|
|
2
2
|
import logging
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Iterator
|
|
5
5
|
|
|
6
6
|
logger = logging.getLogger(__name__)
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ class Config(ABC):
|
|
|
10
10
|
"""Read-only Git config."""
|
|
11
11
|
|
|
12
12
|
@abstractmethod
|
|
13
|
-
def get(self, section:
|
|
13
|
+
def get(self, section: tuple[str, ...], name: str) -> str:
|
|
14
14
|
"""Return the specified setting as a string.
|
|
15
15
|
|
|
16
16
|
Raises:
|
|
@@ -18,7 +18,7 @@ class Config(ABC):
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
@abstractmethod
|
|
21
|
-
def get_bool(self, section:
|
|
21
|
+
def get_bool(self, section: tuple[str, ...], name: str) -> bool:
|
|
22
22
|
"""Return the specified setting as a boolean.
|
|
23
23
|
|
|
24
24
|
Raises:
|
|
@@ -27,7 +27,7 @@ class Config(ABC):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
@abstractmethod
|
|
30
|
-
def get_multivar(self, section:
|
|
30
|
+
def get_multivar(self, section: tuple[str, ...], name: str) -> Iterator[str]:
|
|
31
31
|
"""Iterate over string values in the specified multivar setting.
|
|
32
32
|
|
|
33
33
|
Raises:
|
scmrepo/git/credentials.py
CHANGED
|
@@ -34,18 +34,13 @@ import shutil
|
|
|
34
34
|
import subprocess # nosec B404
|
|
35
35
|
import sys
|
|
36
36
|
from abc import ABC, abstractmethod
|
|
37
|
+
from collections.abc import Iterator, Mapping
|
|
37
38
|
from typing import (
|
|
38
39
|
TYPE_CHECKING,
|
|
39
40
|
Any,
|
|
40
41
|
Callable,
|
|
41
|
-
Dict,
|
|
42
|
-
Iterable,
|
|
43
|
-
Iterator,
|
|
44
|
-
List,
|
|
45
|
-
Mapping,
|
|
46
42
|
NamedTuple,
|
|
47
43
|
Optional,
|
|
48
|
-
Tuple,
|
|
49
44
|
Union,
|
|
50
45
|
)
|
|
51
46
|
from urllib.parse import urlparse, urlunparse
|
|
@@ -57,11 +52,13 @@ from funcy import cached_property
|
|
|
57
52
|
from scmrepo.exceptions import SCMError
|
|
58
53
|
|
|
59
54
|
if TYPE_CHECKING:
|
|
55
|
+
from collections.abc import Iterable
|
|
56
|
+
|
|
60
57
|
from dulwich.config import ConfigDict
|
|
61
58
|
|
|
62
59
|
logger = logging.getLogger(__name__)
|
|
63
60
|
|
|
64
|
-
SectionLike = Union[bytes, str,
|
|
61
|
+
SectionLike = Union[bytes, str, tuple[Union[bytes, str], ...]]
|
|
65
62
|
|
|
66
63
|
|
|
67
64
|
class CredentialNotFoundError(SCMError):
|
|
@@ -110,14 +107,14 @@ class GitCredentialHelper(CredentialHelper):
|
|
|
110
107
|
def __init__(self, command: str, use_http_path: bool = False):
|
|
111
108
|
super().__init__()
|
|
112
109
|
self._command = command
|
|
113
|
-
self._run_kwargs:
|
|
110
|
+
self._run_kwargs: dict[str, Any] = {}
|
|
114
111
|
if self._command[0] == "!":
|
|
115
112
|
# On Windows this will only work in git-bash and/or WSL2
|
|
116
113
|
self._run_kwargs["shell"] = True
|
|
117
114
|
self._encoding = locale.getpreferredencoding()
|
|
118
115
|
self.use_http_path = use_http_path
|
|
119
116
|
|
|
120
|
-
def _prepare_command(self, action: Optional[str] = None) -> Union[str,
|
|
117
|
+
def _prepare_command(self, action: Optional[str] = None) -> Union[str, list[str]]:
|
|
121
118
|
if self._command[0] == "!":
|
|
122
119
|
return self._command[1:] + (f" {action}" if action else "")
|
|
123
120
|
|
|
@@ -242,7 +239,7 @@ class GitCredentialHelper(CredentialHelper):
|
|
|
242
239
|
@staticmethod
|
|
243
240
|
def get_matching_commands(
|
|
244
241
|
base_url: str, config: Optional[Union["ConfigDict", "StackedConfig"]] = None
|
|
245
|
-
) -> Iterator[
|
|
242
|
+
) -> Iterator[tuple[str, bool]]:
|
|
246
243
|
config = config or StackedConfig.default()
|
|
247
244
|
if isinstance(config, StackedConfig):
|
|
248
245
|
backends: Iterable["ConfigDict"] = config.backends
|
|
@@ -326,7 +323,7 @@ class MemoryCredentialHelper(CredentialHelper):
|
|
|
326
323
|
|
|
327
324
|
def __init__(self):
|
|
328
325
|
super().__init__()
|
|
329
|
-
self._credentials:
|
|
326
|
+
self._credentials: dict["_CredentialKey", "Credential"] = {}
|
|
330
327
|
|
|
331
328
|
def __getitem__(self, key: object) -> "Credential":
|
|
332
329
|
if isinstance(key, _CredentialKey):
|
|
@@ -572,7 +569,7 @@ class Credential(Mapping[str, str]):
|
|
|
572
569
|
return urlunparse((self.protocol or "", netloc, path, "", "", ""))
|
|
573
570
|
|
|
574
571
|
@cached_property
|
|
575
|
-
def helpers(self) ->
|
|
572
|
+
def helpers(self) -> list["CredentialHelper"]:
|
|
576
573
|
url = self.url
|
|
577
574
|
return [
|
|
578
575
|
GitCredentialHelper(command, use_http_path=use_http_path)
|
scmrepo/git/lfs/client.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from collections.abc import Iterable
|
|
2
3
|
from contextlib import AbstractContextManager
|
|
3
|
-
from
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Iterable, Optional
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
5
5
|
|
|
6
6
|
import aiohttp
|
|
7
7
|
from dvc_http import HTTPFileSystem
|
|
@@ -31,29 +31,6 @@ class _LFSFileSystem(HTTPFileSystem):
|
|
|
31
31
|
return {}
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def _authed(f: Callable[..., Awaitable]):
|
|
35
|
-
"""Set credentials and retry the given coroutine if needed."""
|
|
36
|
-
|
|
37
|
-
# pylint: disable=protected-access
|
|
38
|
-
@wraps(f) # type: ignore[arg-type]
|
|
39
|
-
async def wrapper(self, *args, **kwargs):
|
|
40
|
-
try:
|
|
41
|
-
return await f(self, *args, **kwargs)
|
|
42
|
-
except aiohttp.ClientResponseError as exc:
|
|
43
|
-
if exc.status != 401:
|
|
44
|
-
raise
|
|
45
|
-
session = await self._set_session()
|
|
46
|
-
if session.auth:
|
|
47
|
-
raise
|
|
48
|
-
auth = self._get_auth()
|
|
49
|
-
if auth is None:
|
|
50
|
-
raise
|
|
51
|
-
self._session._auth = auth
|
|
52
|
-
return await f(self, *args, **kwargs)
|
|
53
|
-
|
|
54
|
-
return wrapper
|
|
55
|
-
|
|
56
|
-
|
|
57
34
|
class LFSClient(AbstractContextManager):
|
|
58
35
|
"""Naive read-only LFS HTTP client."""
|
|
59
36
|
|
|
@@ -63,7 +40,7 @@ class LFSClient(AbstractContextManager):
|
|
|
63
40
|
self,
|
|
64
41
|
url: str,
|
|
65
42
|
git_url: Optional[str] = None,
|
|
66
|
-
headers: Optional[
|
|
43
|
+
headers: Optional[dict[str, str]] = None,
|
|
67
44
|
):
|
|
68
45
|
"""
|
|
69
46
|
Args:
|
|
@@ -71,7 +48,7 @@ class LFSClient(AbstractContextManager):
|
|
|
71
48
|
"""
|
|
72
49
|
self.url = url
|
|
73
50
|
self.git_url = git_url
|
|
74
|
-
self.headers:
|
|
51
|
+
self.headers: dict[str, str] = headers or {}
|
|
75
52
|
|
|
76
53
|
def __exit__(self, *args, **kwargs):
|
|
77
54
|
self.close()
|
|
@@ -111,17 +88,16 @@ class LFSClient(AbstractContextManager):
|
|
|
111
88
|
async def _set_session(self) -> aiohttp.ClientSession:
|
|
112
89
|
return await self.fs.fs.set_session()
|
|
113
90
|
|
|
114
|
-
@_authed
|
|
115
91
|
async def _batch_request(
|
|
116
92
|
self,
|
|
117
93
|
objects: Iterable[Pointer],
|
|
118
94
|
upload: bool = False,
|
|
119
95
|
ref: Optional[str] = None,
|
|
120
96
|
hash_algo: str = "sha256",
|
|
121
|
-
) ->
|
|
97
|
+
) -> dict[str, Any]:
|
|
122
98
|
"""Send LFS API /objects/batch request."""
|
|
123
99
|
url = f"{self.url}/objects/batch"
|
|
124
|
-
body:
|
|
100
|
+
body: dict[str, Any] = {
|
|
125
101
|
"operation": "upload" if upload else "download",
|
|
126
102
|
"transfers": ["basic"],
|
|
127
103
|
"objects": [{"oid": obj.oid, "size": obj.size} for obj in objects],
|
|
@@ -133,14 +109,30 @@ class LFSClient(AbstractContextManager):
|
|
|
133
109
|
headers = dict(self.headers)
|
|
134
110
|
headers["Accept"] = self.JSON_CONTENT_TYPE
|
|
135
111
|
headers["Content-Type"] = self.JSON_CONTENT_TYPE
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
112
|
+
try:
|
|
113
|
+
async with session.post(
|
|
114
|
+
url,
|
|
115
|
+
headers=headers,
|
|
116
|
+
json=body,
|
|
117
|
+
raise_for_status=True,
|
|
118
|
+
) as resp:
|
|
119
|
+
data = await resp.json()
|
|
120
|
+
except aiohttp.ClientResponseError as exc:
|
|
121
|
+
if exc.status != 401:
|
|
122
|
+
raise
|
|
123
|
+
auth = self._get_auth()
|
|
124
|
+
if auth is None:
|
|
125
|
+
raise
|
|
126
|
+
async with session.post(
|
|
127
|
+
url,
|
|
128
|
+
auth=auth,
|
|
129
|
+
headers=headers,
|
|
130
|
+
json=body,
|
|
131
|
+
raise_for_status=True,
|
|
132
|
+
) as resp:
|
|
133
|
+
data = await resp.json()
|
|
134
|
+
return data
|
|
135
|
+
|
|
144
136
|
async def _download(
|
|
145
137
|
self,
|
|
146
138
|
storage: "LFSStorage",
|
scmrepo/git/lfs/fetch.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fnmatch
|
|
2
2
|
import io
|
|
3
3
|
import os
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Iterable, Iterator
|
|
5
|
+
from typing import TYPE_CHECKING, Callable, Optional
|
|
5
6
|
|
|
6
7
|
from scmrepo.exceptions import InvalidRemote, SCMError
|
|
7
8
|
|
|
@@ -15,16 +16,16 @@ if TYPE_CHECKING:
|
|
|
15
16
|
|
|
16
17
|
def fetch(
|
|
17
18
|
scm: "Git",
|
|
18
|
-
revs: Optional[
|
|
19
|
+
revs: Optional[list[str]] = None,
|
|
19
20
|
remote: Optional[str] = None,
|
|
20
|
-
include: Optional[
|
|
21
|
-
exclude: Optional[
|
|
21
|
+
include: Optional[list[str]] = None,
|
|
22
|
+
exclude: Optional[list[str]] = None,
|
|
22
23
|
progress: Optional[Callable[["GitProgressEvent"], None]] = None,
|
|
23
24
|
):
|
|
24
25
|
# NOTE: This currently does not support fetching objects from the worktree
|
|
25
26
|
if not revs:
|
|
26
27
|
revs = ["HEAD"]
|
|
27
|
-
objects:
|
|
28
|
+
objects: set[Pointer] = set()
|
|
28
29
|
for rev in revs:
|
|
29
30
|
objects.update(
|
|
30
31
|
pointer
|
|
@@ -100,8 +101,8 @@ def get_fetch_url(scm: "Git", remote: Optional[str] = None): # noqa: C901,PLR09
|
|
|
100
101
|
def _collect_objects(
|
|
101
102
|
scm: "Git",
|
|
102
103
|
rev: str,
|
|
103
|
-
include: Optional[
|
|
104
|
-
exclude: Optional[
|
|
104
|
+
include: Optional[list[str]],
|
|
105
|
+
exclude: Optional[list[str]],
|
|
105
106
|
) -> Iterator[Pointer]:
|
|
106
107
|
fs = scm.get_fs(rev)
|
|
107
108
|
for path in _filter_paths(fs.find("/"), include, exclude):
|
|
@@ -118,7 +119,7 @@ def _collect_objects(
|
|
|
118
119
|
|
|
119
120
|
|
|
120
121
|
def _filter_paths(
|
|
121
|
-
paths: Iterable[str], include: Optional[
|
|
122
|
+
paths: Iterable[str], include: Optional[list[str]], exclude: Optional[list[str]]
|
|
122
123
|
) -> Iterator[str]:
|
|
123
124
|
filtered = set()
|
|
124
125
|
if include:
|
scmrepo/git/lfs/object.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass, fields
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
@dataclass(frozen=True)
|
|
@@ -11,5 +11,5 @@ class LFSObject:
|
|
|
11
11
|
return self.oid
|
|
12
12
|
|
|
13
13
|
@classmethod
|
|
14
|
-
def from_dict(cls, d:
|
|
14
|
+
def from_dict(cls, d: dict[str, Any]) -> "LFSObject":
|
|
15
15
|
return cls(**{k: v for k, v in d.items() if k in fields(cls)})
|
scmrepo/git/lfs/pointer.py
CHANGED
|
@@ -2,7 +2,7 @@ import hashlib
|
|
|
2
2
|
import io
|
|
3
3
|
import logging
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import IO, BinaryIO, TextIO
|
|
5
|
+
from typing import IO, BinaryIO, TextIO
|
|
6
6
|
|
|
7
7
|
logger = logging.getLogger(__name__)
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ ALLOWED_VERSIONS = (LFS_VERSION, LEGACY_LFS_VERSION)
|
|
|
13
13
|
HEADERS = [(b"version " + version.encode("utf-8")) for version in ALLOWED_VERSIONS]
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def _get_kv(line: str) ->
|
|
16
|
+
def _get_kv(line: str) -> tuple[str, str]:
|
|
17
17
|
key, value = line.strip().split(maxsplit=1)
|
|
18
18
|
return key, value
|
|
19
19
|
|
scmrepo/git/lfs/progress.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, BinaryIO, Callable,
|
|
1
|
+
from typing import Any, BinaryIO, Callable, Optional, Union
|
|
2
2
|
|
|
3
3
|
from dvc_objects.fs.callbacks import DEFAULT_CALLBACK, Callback, TqdmCallback
|
|
4
4
|
|
|
@@ -37,7 +37,7 @@ class LFSCallback(Callback):
|
|
|
37
37
|
self,
|
|
38
38
|
path_1: Union[str, BinaryIO],
|
|
39
39
|
path_2: str,
|
|
40
|
-
kwargs:
|
|
40
|
+
kwargs: dict[str, Any],
|
|
41
41
|
child: Optional[Callback] = None,
|
|
42
42
|
):
|
|
43
43
|
if child:
|
scmrepo/git/lfs/storage.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import errno
|
|
2
2
|
import os
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
from typing import TYPE_CHECKING, BinaryIO, Callable, Optional, Union
|
|
4
5
|
|
|
5
6
|
from .pointer import Pointer
|
|
6
7
|
from .progress import LFSCallback
|
scmrepo/git/objects.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import stat
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Iterable
|
|
4
5
|
from dataclasses import dataclass
|
|
5
|
-
from typing import
|
|
6
|
+
from typing import Optional, cast
|
|
6
7
|
|
|
7
8
|
from pygtrie import Trie
|
|
8
9
|
|
|
@@ -168,7 +169,7 @@ class GitCommit:
|
|
|
168
169
|
commit_time: int
|
|
169
170
|
commit_time_offset: int
|
|
170
171
|
message: str
|
|
171
|
-
parents:
|
|
172
|
+
parents: list[str]
|
|
172
173
|
committer_name: str
|
|
173
174
|
committer_email: str
|
|
174
175
|
author_name: str
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
7
7
|
Project-URL: Issues, https://github.com/iterative/scmrepo/issues
|
|
8
8
|
Project-URL: Source, https://github.com/iterative/scmrepo
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Classifier: Development Status :: 4 - Beta
|
|
15
|
-
Requires-Python: >=3.
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
16
|
Description-Content-Type: text/x-rst
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: gitpython >3
|
|
19
19
|
Requires-Dist: dulwich >=0.21.6
|
|
20
|
-
Requires-Dist: pygit2 >=1.
|
|
20
|
+
Requires-Dist: pygit2 >=1.14.0
|
|
21
21
|
Requires-Dist: pygtrie >=2.3.2
|
|
22
22
|
Requires-Dist: fsspec >=2021.7.0
|
|
23
23
|
Requires-Dist: pathspec >=0.9.0
|
|
@@ -41,7 +41,7 @@ Requires-Dist: paramiko ==3.3.1 ; extra == 'tests'
|
|
|
41
41
|
Requires-Dist: types-certifi ==2021.10.8.3 ; extra == 'tests'
|
|
42
42
|
Requires-Dist: types-mock ==5.1.0.2 ; extra == 'tests'
|
|
43
43
|
Requires-Dist: types-paramiko ==3.4.0.20240120 ; extra == 'tests'
|
|
44
|
-
Requires-Dist: pytest-docker ==
|
|
44
|
+
Requires-Dist: pytest-docker ==2.2.0 ; (python_version < "3.10" and implementation_name != "pypy") and extra == 'tests'
|
|
45
45
|
|
|
46
46
|
scmrepo
|
|
47
47
|
=======
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
scmrepo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
scmrepo/asyn.py,sha256=1l4Npa6gmIeZY6t_8S1LK5CJ3qAW_NMgKPrsN-8f9kU,1523
|
|
3
|
+
scmrepo/base.py,sha256=UjEw7iQhYRAxj4Ia9x5NFMjI__AO7ql6IERoFo7hslo,3006
|
|
4
|
+
scmrepo/exceptions.py,sha256=vR8BuCKgKh9lMnCemzNYGHiJctioOmhn_Kv5m8XO69Y,1053
|
|
5
|
+
scmrepo/fs.py,sha256=0izyq5in6b20H2eSjpud7ykDzKE3230qvcM_3oko7iY,7720
|
|
6
|
+
scmrepo/noscm.py,sha256=aiGE-fabmgF6WTp5mdE5KLeqiOobPVQYa-Ie1zSHs8U,463
|
|
7
|
+
scmrepo/progress.py,sha256=fRUMvkcw6GLuVTP_tK7mGpKeJjbJulFP8rPUqyltkYQ,2157
|
|
8
|
+
scmrepo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
scmrepo/utils.py,sha256=_F3rVvPhES-A2JxLGob0RV8BOnHzxbA9aDPClA7_V8M,1512
|
|
10
|
+
scmrepo/git/__init__.py,sha256=3NEW9rBH2ohprKjXIfodDXnpZxAbAtEYUPMO5uk5Hxw,17101
|
|
11
|
+
scmrepo/git/config.py,sha256=oMoxRq8oJOTRZUNArsT6xpNlfP2n1eTXDMlfHtv1pEs,942
|
|
12
|
+
scmrepo/git/credentials.py,sha256=qJZLLyaDH4t2nut3Yep9Jk9ZEkThjJfVMDZXOFajdZU,20943
|
|
13
|
+
scmrepo/git/objects.py,sha256=vqeFpUlMFHL9Yv1h3wTA7mbRWHCVC_4KgLy5aAISD2g,4674
|
|
14
|
+
scmrepo/git/stash.py,sha256=rnZDeOsO9P-k2e7ulCLUmZKSxSCxaRKl3XJlh97F084,2801
|
|
15
|
+
scmrepo/git/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
scmrepo/git/backend/base.py,sha256=acxuSQ0Z-UGNkGraCdLQxBxDHbTdYWi-FzXtwdb-1O8,13535
|
|
17
|
+
scmrepo/git/backend/gitpython.py,sha256=6L47iX1SmqfM04_Ghwd_DEHegOKewCLu-5MTkBZO_Zo,25312
|
|
18
|
+
scmrepo/git/backend/dulwich/__init__.py,sha256=aQeuLqdprJcxnk8Jkp4mlLXkLRzqMnUwiuA3wjf3_dA,34366
|
|
19
|
+
scmrepo/git/backend/dulwich/asyncssh_vendor.py,sha256=OuZ_bWe5-LiZCIMwBRaX_uj03oEcrRgr1uf9i2Xv4Fk,11497
|
|
20
|
+
scmrepo/git/backend/dulwich/client.py,sha256=bcDroljSvNz6s5WWv9UVvZHKkOJOVTK_zU7YCq62TN4,2360
|
|
21
|
+
scmrepo/git/backend/pygit2/__init__.py,sha256=tapIRAh--nzGUcVGijaMSal2ZPscab9c1mHdqiUIxBU,37004
|
|
22
|
+
scmrepo/git/backend/pygit2/callbacks.py,sha256=Ky4YmUPhv9xjU_44ypBYIcaVHJixzaGb6t9HIeUmBP4,2751
|
|
23
|
+
scmrepo/git/backend/pygit2/filter.py,sha256=2NlWfQ7soXN1H7Es6-LctE74hpj3QKQTlYqXRH83VpM,2128
|
|
24
|
+
scmrepo/git/lfs/__init__.py,sha256=at5blRIKnKpg_g5dLRDsGWBFi6SbucRlF_DX6aAkGtE,257
|
|
25
|
+
scmrepo/git/lfs/client.py,sha256=MitdVwI_1_17qwMM-totHG3-qAt80oAkHiVTcLaYapY,5347
|
|
26
|
+
scmrepo/git/lfs/exceptions.py,sha256=cLlImmPXWJJUl44S4xcRBa2T9wYRkWTaKQGwJylwOhA,77
|
|
27
|
+
scmrepo/git/lfs/fetch.py,sha256=ADNpskbDrvMI7ru4AiOf_c1gfw8TQ7Wct0EiN2Pq-qc,4683
|
|
28
|
+
scmrepo/git/lfs/object.py,sha256=rAYY_z9EYoHPfbpF1QHwL7ecYgaETPyCl-zBx0E1oIQ,337
|
|
29
|
+
scmrepo/git/lfs/pointer.py,sha256=BcVbtjoOUG9cEzyJSJDeweqehGZvq43P6NNLDYUGYEI,3181
|
|
30
|
+
scmrepo/git/lfs/progress.py,sha256=C-HGap81Gif_abhUIzQrM4lL4Tbq9I6C909A7zLTiOM,1726
|
|
31
|
+
scmrepo/git/lfs/smudge.py,sha256=sMSatXCTHZ5cAo-gaE_6KXwCHlXGqzkF0dVgIbPwTJU,1563
|
|
32
|
+
scmrepo/git/lfs/storage.py,sha256=FmdltddhyRg4jrCc1PaiC049_m8Ya9ELMikb-iOuvgw,2241
|
|
33
|
+
scmrepo-2.1.1.dist-info/LICENSE,sha256=-1jhbPjoIVHR0cEgahL4Zhct75Ff4MzYCR_jOaJDPq8,11340
|
|
34
|
+
scmrepo-2.1.1.dist-info/METADATA,sha256=uAlODk9oF8tRBPOYX3Cv8r3DgJhjT0StqdJTQTr7PZM,4833
|
|
35
|
+
scmrepo-2.1.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
36
|
+
scmrepo-2.1.1.dist-info/top_level.txt,sha256=iunjod6w3GogERsAYfLRupnANXnqzX3jbIfbeIQG5cc,8
|
|
37
|
+
scmrepo-2.1.1.dist-info/RECORD,,
|
scmrepo-2.0.4.dist-info/RECORD
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
scmrepo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
scmrepo/asyn.py,sha256=YdoLzqF7drmnf1yN24-0IwyosPi1Pf5qlLRVUI4L2qo,1529
|
|
3
|
-
scmrepo/base.py,sha256=UjEw7iQhYRAxj4Ia9x5NFMjI__AO7ql6IERoFo7hslo,3006
|
|
4
|
-
scmrepo/exceptions.py,sha256=vR8BuCKgKh9lMnCemzNYGHiJctioOmhn_Kv5m8XO69Y,1053
|
|
5
|
-
scmrepo/fs.py,sha256=y-SHgIHC5pSfr0CV9H9m5sd0HNydz7o2WWeHYxk0a5U,7733
|
|
6
|
-
scmrepo/noscm.py,sha256=aiGE-fabmgF6WTp5mdE5KLeqiOobPVQYa-Ie1zSHs8U,463
|
|
7
|
-
scmrepo/progress.py,sha256=fRUMvkcw6GLuVTP_tK7mGpKeJjbJulFP8rPUqyltkYQ,2157
|
|
8
|
-
scmrepo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
scmrepo/utils.py,sha256=_F3rVvPhES-A2JxLGob0RV8BOnHzxbA9aDPClA7_V8M,1512
|
|
10
|
-
scmrepo/git/__init__.py,sha256=LW8ARrqkNsbVMftoPTREeMXhroD-ZS6h5fXMxf6F0Oc,17136
|
|
11
|
-
scmrepo/git/config.py,sha256=_pt8Ygdz71xcbo4KvX_5dW14ujHmPJX3z3W8Ikasd-M,940
|
|
12
|
-
scmrepo/git/credentials.py,sha256=R0boJz9UTRENPac8m3TV42KXhn0yT0Dy7V15txXCxPw,20927
|
|
13
|
-
scmrepo/git/objects.py,sha256=tYlwcrbs4sI50VPaqllvY9PujjNDdYwRISeVEozNImA,4653
|
|
14
|
-
scmrepo/git/stash.py,sha256=rnZDeOsO9P-k2e7ulCLUmZKSxSCxaRKl3XJlh97F084,2801
|
|
15
|
-
scmrepo/git/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
scmrepo/git/backend/base.py,sha256=Un_DD84HKjxC3QAULJGnvdfVfDnI6f_K0KQtidfNOdk,13515
|
|
17
|
-
scmrepo/git/backend/gitpython.py,sha256=FJs4Y-Gp8N9MLgkiRBK2KnvSfDBBVv1BAZ5UG9penBw,25324
|
|
18
|
-
scmrepo/git/backend/dulwich/__init__.py,sha256=iXD5LXb0tdsLvsJLmwtp_LDECNzUyM1R-UufyJWAC-Y,34382
|
|
19
|
-
scmrepo/git/backend/dulwich/asyncssh_vendor.py,sha256=443ivGFvttlZ_F8dL_PvAd6J4XbUcP5e26T9DyJP6HA,11502
|
|
20
|
-
scmrepo/git/backend/dulwich/client.py,sha256=sxz6eJ1VNVQA32AxWnpjyearcS8O99NnbyCUyO2cPx4,2345
|
|
21
|
-
scmrepo/git/backend/pygit2/__init__.py,sha256=eEhY1SEW8TEVVfNhzHVTa_ZbSyeFv7jDvcd_FHXLM10,36986
|
|
22
|
-
scmrepo/git/backend/pygit2/callbacks.py,sha256=RXhzUU3IhKFinbZPGzdTn3bcmcPa1dF7CNpFW-kTAHk,2668
|
|
23
|
-
scmrepo/git/backend/pygit2/filter.py,sha256=v7HWCy0hmYwk0gLhMxzJXihtJxCA6YXw14ctvujNafk,2134
|
|
24
|
-
scmrepo/git/lfs/__init__.py,sha256=at5blRIKnKpg_g5dLRDsGWBFi6SbucRlF_DX6aAkGtE,257
|
|
25
|
-
scmrepo/git/lfs/client.py,sha256=5h7BlcSvc9c1VBMCt0p-7XTIEDhX2Y07GOzhjtzl8T0,5561
|
|
26
|
-
scmrepo/git/lfs/exceptions.py,sha256=cLlImmPXWJJUl44S4xcRBa2T9wYRkWTaKQGwJylwOhA,77
|
|
27
|
-
scmrepo/git/lfs/fetch.py,sha256=2UKkjqqjsFOr3zH1ualVyesUGdZ216veUqpxS_DIz9k,4667
|
|
28
|
-
scmrepo/git/lfs/object.py,sha256=2wHbVjloTcAWPtyCnEroEiCr15hq3o-Q0d2qESaEEN4,343
|
|
29
|
-
scmrepo/git/lfs/pointer.py,sha256=D7Phz5TgTn7o3GeQWxKVT639OftW4PGSlHRqBIPDpq0,3188
|
|
30
|
-
scmrepo/git/lfs/progress.py,sha256=J0j9jlTWeLIK0vLWK9UXEvHsVfKbhJjJp4Bkk55bQ7c,1732
|
|
31
|
-
scmrepo/git/lfs/smudge.py,sha256=sMSatXCTHZ5cAo-gaE_6KXwCHlXGqzkF0dVgIbPwTJU,1563
|
|
32
|
-
scmrepo/git/lfs/storage.py,sha256=q-bJasUKBmuuI9Nt15ze16miFIpmMkLU9v7MvbQE9cY,2214
|
|
33
|
-
scmrepo-2.0.4.dist-info/LICENSE,sha256=-1jhbPjoIVHR0cEgahL4Zhct75Ff4MzYCR_jOaJDPq8,11340
|
|
34
|
-
scmrepo-2.0.4.dist-info/METADATA,sha256=tUInr08Sc5Aa8tePMm_v7FCzkf7bmnxuIrIM11NV5PA,4833
|
|
35
|
-
scmrepo-2.0.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
36
|
-
scmrepo-2.0.4.dist-info/top_level.txt,sha256=iunjod6w3GogERsAYfLRupnANXnqzX3jbIfbeIQG5cc,8
|
|
37
|
-
scmrepo-2.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|