scmrepo 3.3.0__py3-none-any.whl → 3.3.2__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.
@@ -2,7 +2,7 @@ import locale
2
2
  import logging
3
3
  import os
4
4
  import stat
5
- from collections.abc import Generator, Iterable, Iterator, Mapping
5
+ from collections.abc import Iterable, Iterator, Mapping
6
6
  from contextlib import contextmanager
7
7
  from io import BytesIO, StringIO, TextIOWrapper
8
8
  from typing import (
@@ -25,6 +25,7 @@ from scmrepo.exceptions import (
25
25
  from scmrepo.git.backend.base import BaseGitBackend, SyncStatus
26
26
  from scmrepo.git.config import Config
27
27
  from scmrepo.git.objects import GitCommit, GitObject, GitTag
28
+ from scmrepo.urls import is_scp_style_url
28
29
  from scmrepo.utils import relpath
29
30
 
30
31
  logger = logging.getLogger(__name__)
@@ -636,7 +637,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
636
637
  raise SCMError("Unknown merge analysis result")
637
638
 
638
639
  @contextmanager
639
- def _get_remote(self, url: str) -> Generator["Remote", None, None]:
640
+ def _get_remote(self, url: str) -> Iterator["Remote"]:
640
641
  """Return a pygit2.Remote suitable for the specified Git URL or remote name."""
641
642
  try:
642
643
  remote = self.repo.remotes[url]
@@ -646,11 +647,11 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
646
647
  except KeyError as exc:
647
648
  raise SCMError(f"'{url}' is not a valid Git remote or URL") from exc
648
649
 
649
- if os.name == "nt" and url.startswith("file://"):
650
- url = url[len("file://") :]
650
+ if os.name == "nt":
651
+ url = url.removeprefix("file://")
651
652
  remote = self.repo.remotes.create_anonymous(url)
652
653
  parsed = urlparse(remote.url)
653
- if parsed.scheme in ("git", "git+ssh", "ssh") or remote.url.startswith("git@"):
654
+ if parsed.scheme in ("git", "git+ssh", "ssh") or is_scp_style_url(remote.url):
654
655
  raise NotImplementedError
655
656
  yield remote
656
657
 
scmrepo/git/lfs/client.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
- import re
5
4
  import shutil
6
5
  from abc import abstractmethod
7
6
  from collections.abc import Iterable, Iterator
@@ -10,6 +9,7 @@ from http import HTTPStatus
10
9
  from tempfile import NamedTemporaryFile
11
10
  from time import time
12
11
  from typing import TYPE_CHECKING, Any, Optional
12
+ from urllib.parse import urlparse
13
13
 
14
14
  import aiohttp
15
15
  from aiohttp_retry import ExponentialRetry, RetryClient
@@ -20,6 +20,7 @@ from funcy import cached_property
20
20
 
21
21
  from scmrepo.git.backend.dulwich import _get_ssh_vendor
22
22
  from scmrepo.git.credentials import Credential, CredentialNotFoundError
23
+ from scmrepo.urls import SCP_REGEX, is_scp_style_url
23
24
 
24
25
  from .exceptions import LFSError
25
26
  from .pointer import Pointer
@@ -84,9 +85,9 @@ class LFSClient(AbstractContextManager):
84
85
 
85
86
  @classmethod
86
87
  def from_git_url(cls, git_url: str) -> "LFSClient":
87
- if git_url.startswith(("ssh://", "git@")):
88
+ if git_url.startswith("ssh://") or is_scp_style_url(git_url):
88
89
  return _SSHLFSClient.from_git_url(git_url)
89
- if git_url.startswith("https://"):
90
+ if git_url.startswith(("http://", "https://")):
90
91
  return _HTTPLFSClient.from_git_url(git_url)
91
92
  raise NotImplementedError(f"Unsupported Git URL: {git_url}")
92
93
 
@@ -213,11 +214,9 @@ class _HTTPLFSClient(LFSClient):
213
214
 
214
215
 
215
216
  class _SSHLFSClient(LFSClient):
216
- _URL_PATTERN = re.compile(
217
- r"(?:ssh://)?git@(?P<host>\S+?)(?::(?P<port>\d+))?(?:[:/])(?P<path>\S+?)\.git"
218
- )
219
-
220
- def __init__(self, url: str, host: str, port: int, path: str):
217
+ def __init__(
218
+ self, url: str, host: str, port: int, username: Optional[str], path: str
219
+ ):
221
220
  """
222
221
  Args:
223
222
  url: LFS server URL.
@@ -228,25 +227,42 @@ class _SSHLFSClient(LFSClient):
228
227
  super().__init__(url)
229
228
  self.host = host
230
229
  self.port = port
230
+ self.username = username
231
231
  self.path = path
232
232
  self._ssh = _get_ssh_vendor()
233
233
 
234
234
  @classmethod
235
235
  def from_git_url(cls, git_url: str) -> "_SSHLFSClient":
236
- result = cls._URL_PATTERN.match(git_url)
237
- if not result:
236
+ if scp_match := SCP_REGEX.match(git_url):
237
+ # Add an ssh:// prefix and replace the ':' with a '/'.
238
+ git_url = scp_match.expand(r"ssh://\1\2/\3")
239
+
240
+ parsed = urlparse(git_url)
241
+ if parsed.scheme != "ssh" or not parsed.hostname:
238
242
  raise ValueError(f"Invalid Git SSH URL: {git_url}")
239
- host, port, path = result.group("host", "port", "path")
240
- url = f"https://{host}/{path}.git/info/lfs"
241
- return cls(url, host, int(port or 22), path)
243
+
244
+ host = parsed.hostname
245
+ port = parsed.port or 22
246
+ path = parsed.path.lstrip("/")
247
+ username = parsed.username
248
+
249
+ url_path = path.removesuffix(".git") + ".git/info/lfs"
250
+ url = f"https://{host}/{url_path}"
251
+ return cls(url, host, port, username, path)
242
252
 
243
253
  def _get_auth_header(self, *, upload: bool) -> dict:
244
254
  return self._git_lfs_authenticate(
245
- self.host, self.port, f"{self.path}.git", upload=upload
255
+ self.host, self.port, self.username, self.path, upload=upload
246
256
  ).get("header", {})
247
257
 
248
258
  def _git_lfs_authenticate(
249
- self, host: str, port: int, path: str, *, upload: bool = False
259
+ self,
260
+ host: str,
261
+ port: int,
262
+ username: Optional[str],
263
+ path: str,
264
+ *,
265
+ upload: bool = False,
250
266
  ) -> dict:
251
267
  action = "upload" if upload else "download"
252
268
  return json.loads(
@@ -254,7 +270,7 @@ class _SSHLFSClient(LFSClient):
254
270
  command=f"git-lfs-authenticate {path} {action}",
255
271
  host=host,
256
272
  port=port,
257
- username="git",
273
+ username=username,
258
274
  ).read()
259
275
  )
260
276
 
scmrepo/urls.py ADDED
@@ -0,0 +1,21 @@
1
+ import re
2
+
3
+ # from https://github.com/pypa/pip/blob/303fed36c1771de4063063a866776a9103972317/src/pip/_internal/vcs/git.py#L40
4
+ # SCP (Secure copy protocol) shorthand. e.g. 'git@example.com:foo/bar.git'
5
+ SCP_REGEX = re.compile(
6
+ r"""^
7
+ # Optional user, e.g. 'git@'
8
+ (\w+@)?
9
+ # Server, e.g. 'github.com'.
10
+ ([^/:]+):
11
+ # The server-side path. e.g. 'user/project.git'. Must start with an
12
+ # alphanumeric character so as not to be confusable with a Windows paths
13
+ # like 'C:/foo/bar' or 'C:\foo\bar'.
14
+ (\w[^:]*)
15
+ $""",
16
+ re.VERBOSE,
17
+ )
18
+
19
+
20
+ def is_scp_style_url(url: str) -> bool:
21
+ return bool(SCP_REGEX.match(url))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scmrepo
3
- Version: 3.3.0
3
+ Version: 3.3.2
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -16,7 +16,7 @@ Requires-Python: >=3.9
16
16
  Description-Content-Type: text/x-rst
17
17
  License-File: LICENSE
18
18
  Requires-Dist: gitpython >3
19
- Requires-Dist: dulwich >=0.21.6
19
+ Requires-Dist: dulwich >=0.22.1
20
20
  Requires-Dist: pygit2 >=1.14.0
21
21
  Requires-Dist: pygtrie >=2.3.2
22
22
  Requires-Dist: fsspec[tqdm] >=2024.2.0
@@ -26,22 +26,22 @@ Requires-Dist: funcy >=1.14
26
26
  Requires-Dist: aiohttp-retry >=2.5.0
27
27
  Requires-Dist: tqdm
28
28
  Provides-Extra: dev
29
+ Requires-Dist: mypy ==1.9.0 ; extra == 'dev'
29
30
  Requires-Dist: scmrepo[tests] ; extra == 'dev'
31
+ Requires-Dist: types-certifi ; extra == 'dev'
32
+ Requires-Dist: types-mock ; extra == 'dev'
33
+ Requires-Dist: types-paramiko ; extra == 'dev'
34
+ Requires-Dist: types-tqdm ; extra == 'dev'
30
35
  Provides-Extra: tests
31
- Requires-Dist: pytest ==8.1.1 ; extra == 'tests'
32
- Requires-Dist: pytest-sugar ==1.0.0 ; extra == 'tests'
33
- Requires-Dist: pytest-cov ==4.1.0 ; extra == 'tests'
34
- Requires-Dist: pytest-mock ==3.12.0 ; extra == 'tests'
35
- Requires-Dist: mypy ==1.9.0 ; extra == 'tests'
36
- Requires-Dist: pytest-test-utils ==0.1.0 ; extra == 'tests'
37
- Requires-Dist: pytest-asyncio ==0.23.5 ; extra == 'tests'
38
- Requires-Dist: pytest-docker ==3.1.1 ; extra == 'tests'
39
- Requires-Dist: mock ==5.1.0 ; extra == 'tests'
40
- Requires-Dist: paramiko ==3.4.0 ; extra == 'tests'
41
- Requires-Dist: types-certifi ==2021.10.8.3 ; extra == 'tests'
42
- Requires-Dist: types-mock ==5.1.0.20240311 ; extra == 'tests'
43
- Requires-Dist: types-paramiko ==3.4.0.20240205 ; extra == 'tests'
44
- Requires-Dist: types-tqdm ; extra == 'tests'
36
+ Requires-Dist: aioresponses <0.8,>=0.7 ; extra == 'tests'
37
+ Requires-Dist: paramiko <4,>=3.4.0 ; extra == 'tests'
38
+ Requires-Dist: pytest <9,>=7 ; extra == 'tests'
39
+ Requires-Dist: pytest-asyncio <1,>=0.23.2 ; extra == 'tests'
40
+ Requires-Dist: pytest-cov >=4.1.0 ; extra == 'tests'
41
+ Requires-Dist: pytest-docker <4,>=1 ; extra == 'tests'
42
+ Requires-Dist: pytest-mock ; extra == 'tests'
43
+ Requires-Dist: pytest-sugar ; extra == 'tests'
44
+ Requires-Dist: pytest-test-utils <0.2,>=0.1.0 ; extra == 'tests'
45
45
 
46
46
  scmrepo
47
47
  =======
@@ -6,6 +6,7 @@ scmrepo/fs.py,sha256=ARD8_TRSdHpuiaYwF7QXrQIyfl9AFj7ctv_Ltc6pAAs,7740
6
6
  scmrepo/noscm.py,sha256=xraqlBek4zhFlHf1LvTMkgBM0hykgcTfMVkTNCpVlcQ,491
7
7
  scmrepo/progress.py,sha256=fRUMvkcw6GLuVTP_tK7mGpKeJjbJulFP8rPUqyltkYQ,2157
8
8
  scmrepo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ scmrepo/urls.py,sha256=vEfW1h1lb7GXvMVBk-TNwqctpl-5xGMA2LbOPVn7t4Q,638
9
10
  scmrepo/utils.py,sha256=_F3rVvPhES-A2JxLGob0RV8BOnHzxbA9aDPClA7_V8M,1512
10
11
  scmrepo/git/__init__.py,sha256=6giWgQpgAlJslOkFmSAYkw8pb7nuefrbpZZf5Np3Iyo,17189
11
12
  scmrepo/git/config.py,sha256=0t0OBmJ9SIa5tf22QdcGzhZfdMzzppvEmceUDg8ZPyE,943
@@ -18,11 +19,11 @@ scmrepo/git/backend/gitpython.py,sha256=RTDNUVLiJmhcRgdaJpApTQi6sMN-an9P9xKZBKwk
18
19
  scmrepo/git/backend/dulwich/__init__.py,sha256=x6DsVddlEeUW39-0WPjRFjRlqc3U2f0opZ1Hm2pvBts,34820
19
20
  scmrepo/git/backend/dulwich/asyncssh_vendor.py,sha256=y0mvx3tRR99Nv85e2n39nPYlhme8dplmQQSbSpx7FDM,11562
20
21
  scmrepo/git/backend/dulwich/client.py,sha256=bcDroljSvNz6s5WWv9UVvZHKkOJOVTK_zU7YCq62TN4,2360
21
- scmrepo/git/backend/pygit2/__init__.py,sha256=_cMmUDuGQJA0ma2ug7QE4bpgj31o-s-YCgGf3RZllrM,37042
22
+ scmrepo/git/backend/pygit2/__init__.py,sha256=gNOFVLgB8gpXSKfuL_-Vk7GhVkZk6DiwuJMahel0Ue0,37035
22
23
  scmrepo/git/backend/pygit2/callbacks.py,sha256=Ky4YmUPhv9xjU_44ypBYIcaVHJixzaGb6t9HIeUmBP4,2751
23
24
  scmrepo/git/backend/pygit2/filter.py,sha256=2NlWfQ7soXN1H7Es6-LctE74hpj3QKQTlYqXRH83VpM,2128
24
25
  scmrepo/git/lfs/__init__.py,sha256=at5blRIKnKpg_g5dLRDsGWBFi6SbucRlF_DX6aAkGtE,257
25
- scmrepo/git/lfs/client.py,sha256=UCGyComKAp1OV7gFi2CxWp_pXqV4hxbg3JGSRTCBtIk,9707
26
+ scmrepo/git/lfs/client.py,sha256=SLlGFC09YD55nTDQ7MjuKD9alql-eOriyNePZikFaYo,10171
26
27
  scmrepo/git/lfs/exceptions.py,sha256=cLlImmPXWJJUl44S4xcRBa2T9wYRkWTaKQGwJylwOhA,77
27
28
  scmrepo/git/lfs/fetch.py,sha256=ADNpskbDrvMI7ru4AiOf_c1gfw8TQ7Wct0EiN2Pq-qc,4683
28
29
  scmrepo/git/lfs/object.py,sha256=rAYY_z9EYoHPfbpF1QHwL7ecYgaETPyCl-zBx0E1oIQ,337
@@ -30,8 +31,8 @@ scmrepo/git/lfs/pointer.py,sha256=BcVbtjoOUG9cEzyJSJDeweqehGZvq43P6NNLDYUGYEI,31
30
31
  scmrepo/git/lfs/progress.py,sha256=ELlBs2SeXhAcnPDN23w3FTeBRgB9RGqBD2CFMS6n9Xs,4750
31
32
  scmrepo/git/lfs/smudge.py,sha256=1O_fznptWo4CKXqcJgUoWP6cgWWhvGAZ3d87kasG3cQ,1610
32
33
  scmrepo/git/lfs/storage.py,sha256=2weDldy6MFrA8IDzBczsPy8fBWCp4FKaJTT0f6eIT64,2396
33
- scmrepo-3.3.0.dist-info/LICENSE,sha256=-1jhbPjoIVHR0cEgahL4Zhct75Ff4MzYCR_jOaJDPq8,11340
34
- scmrepo-3.3.0.dist-info/METADATA,sha256=JM8IWrpExncQB-RqmqbyazEAQwjr78ipbfuT5C5Zxvs,4785
35
- scmrepo-3.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
36
- scmrepo-3.3.0.dist-info/top_level.txt,sha256=iunjod6w3GogERsAYfLRupnANXnqzX3jbIfbeIQG5cc,8
37
- scmrepo-3.3.0.dist-info/RECORD,,
34
+ scmrepo-3.3.2.dist-info/LICENSE,sha256=-1jhbPjoIVHR0cEgahL4Zhct75Ff4MzYCR_jOaJDPq8,11340
35
+ scmrepo-3.3.2.dist-info/METADATA,sha256=RoEcoxC9ReqV3Eh0lgIQRHVnBLrZeRs5z4px6SpDZb0,4730
36
+ scmrepo-3.3.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
37
+ scmrepo-3.3.2.dist-info/top_level.txt,sha256=iunjod6w3GogERsAYfLRupnANXnqzX3jbIfbeIQG5cc,8
38
+ scmrepo-3.3.2.dist-info/RECORD,,