scmrepo 2.0.4__tar.gz → 3.3.1__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.

Files changed (75) hide show
  1. {scmrepo-2.0.4 → scmrepo-3.3.1}/.github/workflows/release.yaml +2 -2
  2. {scmrepo-2.0.4 → scmrepo-3.3.1}/.github/workflows/tests.yaml +3 -3
  3. {scmrepo-2.0.4 → scmrepo-3.3.1}/.pre-commit-config.yaml +1 -1
  4. {scmrepo-2.0.4 → scmrepo-3.3.1}/CONTRIBUTING.rst +1 -1
  5. {scmrepo-2.0.4/src/scmrepo.egg-info → scmrepo-3.3.1}/PKG-INFO +21 -21
  6. {scmrepo-2.0.4 → scmrepo-3.3.1}/noxfile.py +2 -1
  7. {scmrepo-2.0.4 → scmrepo-3.3.1}/pyproject.toml +22 -23
  8. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/asyn.py +4 -3
  9. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/base.py +1 -0
  10. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/fs.py +6 -6
  11. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/__init__.py +13 -14
  12. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/base.py +5 -4
  13. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/dulwich/__init__.py +33 -25
  14. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +14 -14
  15. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/dulwich/client.py +5 -4
  16. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/gitpython.py +9 -13
  17. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/pygit2/__init__.py +27 -32
  18. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/pygit2/callbacks.py +11 -6
  19. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/pygit2/filter.py +2 -2
  20. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/config.py +5 -4
  21. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/credentials.py +10 -12
  22. scmrepo-3.3.1/src/scmrepo/git/lfs/client.py +308 -0
  23. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/lfs/fetch.py +9 -8
  24. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/lfs/object.py +2 -2
  25. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/lfs/pointer.py +2 -2
  26. scmrepo-3.3.1/src/scmrepo/git/lfs/progress.py +159 -0
  27. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/lfs/smudge.py +4 -1
  28. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/lfs/storage.py +7 -4
  29. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/objects.py +3 -2
  30. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/noscm.py +3 -2
  31. scmrepo-3.3.1/src/scmrepo/urls.py +21 -0
  32. {scmrepo-2.0.4 → scmrepo-3.3.1/src/scmrepo.egg-info}/PKG-INFO +21 -21
  33. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo.egg-info/SOURCES.txt +2 -0
  34. scmrepo-3.3.1/src/scmrepo.egg-info/requires.txt +29 -0
  35. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/conftest.py +5 -4
  36. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/test_dulwich.py +5 -5
  37. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/test_git.py +6 -6
  38. scmrepo-3.3.1/tests/test_lfs.py +284 -0
  39. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/test_pygit2.py +2 -0
  40. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/test_scmrepo.py +1 -2
  41. scmrepo-3.3.1/tests/test_urls.py +31 -0
  42. scmrepo-2.0.4/src/scmrepo/git/lfs/client.py +0 -177
  43. scmrepo-2.0.4/src/scmrepo/git/lfs/progress.py +0 -61
  44. scmrepo-2.0.4/src/scmrepo.egg-info/requires.txt +0 -31
  45. scmrepo-2.0.4/tests/test_lfs.py +0 -76
  46. {scmrepo-2.0.4 → scmrepo-3.3.1}/.coveragerc +0 -0
  47. {scmrepo-2.0.4 → scmrepo-3.3.1}/.cruft.json +0 -0
  48. {scmrepo-2.0.4 → scmrepo-3.3.1}/.gitattributes +0 -0
  49. {scmrepo-2.0.4 → scmrepo-3.3.1}/.github/dependabot.yml +0 -0
  50. {scmrepo-2.0.4 → scmrepo-3.3.1}/.github/workflows/update-template.yaml +0 -0
  51. {scmrepo-2.0.4 → scmrepo-3.3.1}/.gitignore +0 -0
  52. {scmrepo-2.0.4 → scmrepo-3.3.1}/CODE_OF_CONDUCT.rst +0 -0
  53. {scmrepo-2.0.4 → scmrepo-3.3.1}/LICENSE +0 -0
  54. {scmrepo-2.0.4 → scmrepo-3.3.1}/README.rst +0 -0
  55. {scmrepo-2.0.4 → scmrepo-3.3.1}/setup.cfg +0 -0
  56. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/__init__.py +0 -0
  57. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/exceptions.py +0 -0
  58. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/backend/__init__.py +0 -0
  59. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/lfs/__init__.py +0 -0
  60. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/lfs/exceptions.py +0 -0
  61. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/git/stash.py +0 -0
  62. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/progress.py +0 -0
  63. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/py.typed +0 -0
  64. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo/utils.py +0 -0
  65. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo.egg-info/dependency_links.txt +0 -0
  66. {scmrepo-2.0.4 → scmrepo-3.3.1}/src/scmrepo.egg-info/top_level.txt +0 -0
  67. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/__init__.py +0 -0
  68. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/docker-compose.yml +0 -0
  69. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/git-init/git.sh +0 -0
  70. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/test_credentials.py +0 -0
  71. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/test_fs.py +0 -0
  72. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/test_noscm.py +0 -0
  73. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/test_stash.py +0 -0
  74. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/user.key +0 -0
  75. {scmrepo-2.0.4 → scmrepo-3.3.1}/tests/user.key.pub +0 -0
@@ -17,12 +17,12 @@ jobs:
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
19
  - name: Check out the repository
20
- uses: actions/checkout@v3
20
+ uses: actions/checkout@v4
21
21
  with:
22
22
  fetch-depth: 0
23
23
 
24
24
  - name: Set up Python 3.10
25
- uses: actions/setup-python@v4
25
+ uses: actions/setup-python@v5
26
26
  with:
27
27
  python-version: '3.10'
28
28
 
@@ -21,16 +21,16 @@ jobs:
21
21
  fail-fast: false
22
22
  matrix:
23
23
  os: [ubuntu-20.04, windows-latest, macos-latest]
24
- pyv: ['3.8', '3.9', '3.10', '3.11']
24
+ pyv: ['3.9', '3.10', '3.11', '3.12']
25
25
 
26
26
  steps:
27
27
  - name: Check out the repository
28
- uses: actions/checkout@v3
28
+ uses: actions/checkout@v4
29
29
  with:
30
30
  fetch-depth: 0
31
31
 
32
32
  - name: Set up Python ${{ matrix.pyv }}
33
- uses: actions/setup-python@v4
33
+ uses: actions/setup-python@v5
34
34
  with:
35
35
  python-version: ${{ matrix.pyv }}
36
36
 
@@ -20,7 +20,7 @@ repos:
20
20
  - id: sort-simple-yaml
21
21
  - id: trailing-whitespace
22
22
  - repo: https://github.com/astral-sh/ruff-pre-commit
23
- rev: 'v0.1.13'
23
+ rev: 'v0.3.3'
24
24
  hooks:
25
25
  - id: ruff
26
26
  args: [--fix, --exit-non-zero-on-fix]
@@ -41,7 +41,7 @@ Request features on the `Issue Tracker`_.
41
41
  How to set up your development environment
42
42
  ------------------------------------------
43
43
 
44
- You need Python 3.8+ and the following tools:
44
+ You need Python 3.9+ and the following tools:
45
45
 
46
46
  - Nox_
47
47
 
@@ -1,47 +1,47 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scmrepo
3
- Version: 2.0.4
3
+ Version: 3.3.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.8
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.13.3
20
+ Requires-Dist: pygit2>=1.14.0
21
21
  Requires-Dist: pygtrie>=2.3.2
22
- Requires-Dist: fsspec>=2021.7.0
22
+ Requires-Dist: fsspec[tqdm]>=2024.2.0
23
23
  Requires-Dist: pathspec>=0.9.0
24
24
  Requires-Dist: asyncssh<3,>=2.13.1
25
25
  Requires-Dist: funcy>=1.14
26
- Requires-Dist: shortuuid>=0.5.0
27
- Requires-Dist: dvc-objects<4,>=3
28
- Requires-Dist: dvc-http>=2.29.0
26
+ Requires-Dist: aiohttp-retry>=2.5.0
27
+ Requires-Dist: tqdm
29
28
  Provides-Extra: tests
30
- Requires-Dist: pytest==7.2.0; extra == "tests"
31
- Requires-Dist: pytest-sugar==0.9.5; extra == "tests"
32
- Requires-Dist: pytest-cov==3.0.0; extra == "tests"
33
- Requires-Dist: pytest-mock==3.8.2; extra == "tests"
34
- Requires-Dist: mypy==0.971; extra == "tests"
35
- Requires-Dist: pytest-test-utils==0.0.8; extra == "tests"
36
- Requires-Dist: pytest-asyncio==0.18.3; extra == "tests"
37
- Requires-Dist: pytest-docker==0.12.0; (python_version < "3.10" and implementation_name != "pypy") and extra == "tests"
38
- Requires-Dist: mock==5.1.0; extra == "tests"
39
- Requires-Dist: paramiko==3.3.1; extra == "tests"
40
- Requires-Dist: types-certifi==2021.10.8.3; extra == "tests"
41
- Requires-Dist: types-mock==5.1.0.2; extra == "tests"
42
- Requires-Dist: types-paramiko==3.4.0.20240120; extra == "tests"
29
+ Requires-Dist: aioresponses<0.8,>=0.7; extra == "tests"
30
+ Requires-Dist: paramiko<4,>=3.4.0; extra == "tests"
31
+ Requires-Dist: pytest<9,>=7; extra == "tests"
32
+ Requires-Dist: pytest-asyncio<1,>=0.23.2; extra == "tests"
33
+ Requires-Dist: pytest-cov>=4.1.0; extra == "tests"
34
+ Requires-Dist: pytest-docker<4,>=1; extra == "tests"
35
+ Requires-Dist: pytest-mock; extra == "tests"
36
+ Requires-Dist: pytest-sugar; extra == "tests"
37
+ Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
43
38
  Provides-Extra: dev
39
+ Requires-Dist: mypy==1.9.0; extra == "dev"
44
40
  Requires-Dist: scmrepo[tests]; extra == "dev"
41
+ Requires-Dist: types-certifi; extra == "dev"
42
+ Requires-Dist: types-mock; extra == "dev"
43
+ Requires-Dist: types-paramiko; extra == "dev"
44
+ Requires-Dist: types-tqdm; extra == "dev"
45
45
 
46
46
  scmrepo
47
47
  =======
@@ -1,4 +1,5 @@
1
1
  """Automation using nox."""
2
+
2
3
  import glob
3
4
  import os
4
5
 
@@ -9,7 +10,7 @@ nox.options.sessions = "lint", "tests"
9
10
  locations = "src", "tests"
10
11
 
11
12
 
12
- @nox.session(python=["3.8", "3.9", "3.10", "3.11"])
13
+ @nox.session(python=["3.9", "3.10", "3.11", "3.12"])
13
14
  def tests(session: nox.Session) -> None:
14
15
  session.install(".[tests]")
15
16
  session.run(
@@ -12,26 +12,25 @@ license = {text = "Apache-2.0"}
12
12
  authors = [{ name = "Iterative", email = "support@dvc.org" }]
13
13
  classifiers = [
14
14
  "Programming Language :: Python :: 3",
15
- "Programming Language :: Python :: 3.8",
16
15
  "Programming Language :: Python :: 3.9",
17
16
  "Programming Language :: Python :: 3.10",
18
17
  "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
19
  "Development Status :: 4 - Beta",
20
20
  ]
21
- requires-python = ">=3.8"
21
+ requires-python = ">=3.9"
22
22
  dynamic = ["version"]
23
23
  dependencies = [
24
24
  "gitpython>3",
25
25
  "dulwich>=0.21.6",
26
- "pygit2>=1.13.3",
26
+ "pygit2>=1.14.0",
27
27
  "pygtrie>=2.3.2",
28
- "fsspec>=2021.7.0",
28
+ "fsspec[tqdm]>=2024.2.0",
29
29
  "pathspec>=0.9.0",
30
30
  "asyncssh>=2.13.1,<3",
31
31
  "funcy>=1.14",
32
- "shortuuid>=0.5.0",
33
- "dvc-objects>=3,<4",
34
- "dvc-http>=2.29.0",
32
+ "aiohttp-retry>=2.5.0",
33
+ "tqdm",
35
34
  ]
36
35
 
37
36
  [project.urls]
@@ -40,27 +39,27 @@ Source = "https://github.com/iterative/scmrepo"
40
39
 
41
40
  [project.optional-dependencies]
42
41
  tests = [
43
- "pytest==7.2.0",
44
- "pytest-sugar==0.9.5",
45
- "pytest-cov==3.0.0",
46
- "pytest-mock==3.8.2",
47
- "mypy==0.971",
48
- "pytest-test-utils==0.0.8",
49
- "pytest-asyncio==0.18.3",
50
- # https://github.com/docker/docker-py/issues/2902
51
- "pytest-docker==0.12.0; python_version < '3.10' and implementation_name != 'pypy'",
52
- "mock==5.1.0",
53
- "paramiko==3.3.1",
54
- "types-certifi==2021.10.8.3",
55
- "types-mock==5.1.0.2",
56
- "types-paramiko==3.4.0.20240120",
42
+ "aioresponses>=0.7,<0.8",
43
+ "paramiko>=3.4.0,<4",
44
+ "pytest>=7,<9",
45
+ "pytest-asyncio>=0.23.2,<1",
46
+ "pytest-cov>=4.1.0",
47
+ "pytest-docker>=1,<4",
48
+ "pytest-mock",
49
+ "pytest-sugar",
50
+ "pytest-test-utils>=0.1.0,<0.2",
57
51
  ]
58
52
  dev = [
53
+ "mypy==1.9.0",
59
54
  "scmrepo[tests]",
55
+ "types-certifi",
56
+ "types-mock",
57
+ "types-paramiko",
58
+ "types-tqdm",
60
59
  ]
61
60
 
62
61
  [tool.setuptools.package-data]
63
- dvc_objects = ["py.typed"]
62
+ scmrepo = ["py.typed"]
64
63
 
65
64
  [tool.setuptools.packages.find]
66
65
  where = ["src"]
@@ -72,6 +71,7 @@ markers = [
72
71
  "skip_git_backend: skip tests for given backend",
73
72
  "slow: mark test as slow to run",
74
73
  ]
74
+ asyncio_mode = "auto"
75
75
 
76
76
  [tool.coverage.run]
77
77
  branch = true
@@ -109,7 +109,6 @@ files = ["src", "tests"]
109
109
  [[tool.mypy.overrides]]
110
110
  module = [
111
111
  "pygtrie",
112
- "dvc_http.*",
113
112
  "funcy",
114
113
  "git",
115
114
  "gitdb.*",
@@ -1,8 +1,9 @@
1
1
  """DVC re-implementation of fsspec's dedicated async event loop."""
2
+
2
3
  import asyncio
3
4
  import os
4
5
  import threading
5
- from typing import Any, List, Optional
6
+ from typing import Any, Optional
6
7
 
7
8
  from fsspec.asyn import ( # noqa: F401, pylint:disable=unused-import
8
9
  _selector_policy,
@@ -11,9 +12,9 @@ from fsspec.asyn import ( # noqa: F401, pylint:disable=unused-import
11
12
  )
12
13
 
13
14
  # dedicated async IO thread
14
- iothread: List[Optional[threading.Thread]] = [None]
15
+ iothread: list[Optional[threading.Thread]] = [None]
15
16
  # global DVC event loop
16
- default_loop: List[Optional[asyncio.AbstractEventLoop]] = [None]
17
+ default_loop: list[Optional[asyncio.AbstractEventLoop]] = [None]
17
18
  lock = threading.Lock()
18
19
 
19
20
 
@@ -1,4 +1,5 @@
1
1
  """Manages source control systems (e.g. Git) in DVC."""
2
+
2
3
  from contextlib import AbstractContextManager
3
4
 
4
5
 
@@ -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, Dict, Optional, Tuple
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
@@ -33,8 +33,8 @@ class GitFileSystem(AbstractFileSystem):
33
33
  self,
34
34
  path: Optional[str] = None,
35
35
  rev: Optional[str] = None,
36
- scm: "Git" = None,
37
- trie: "GitTrie" = None,
36
+ scm: Optional["Git"] = None,
37
+ trie: Optional["GitTrie"] = None,
38
38
  rev_resolver: Optional[Callable[["Git", str], str]] = None,
39
39
  **kwargs,
40
40
  ):
@@ -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) -> Tuple[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[Dict] = None,
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) -> Dict[str, 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
@@ -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 = Type[BaseGitBackend]
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[Dict[str, BackendCls]] = {
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: Dict[str, BaseGitBackend] = {}
71
+ self.initialized: dict[str, BaseGitBackend] = {}
76
72
 
77
73
  self.args = args
78
74
  self.kwargs = kwargs
@@ -146,7 +142,7 @@ class Git(Base):
146
142
  def clone(
147
143
  cls,
148
144
  url: str,
149
- to_path: str,
145
+ to_path: Union[str, os.PathLike[str]],
150
146
  rev: Optional[str] = None,
151
147
  bare: bool = False,
152
148
  mirror: bool = False,
@@ -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) -> Tuple[str, 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
 
@@ -323,13 +319,16 @@ class Git(Base):
323
319
 
324
320
  @classmethod
325
321
  def init(
326
- cls, path: str, bare: bool = False, _backend: Optional[str] = None
322
+ cls,
323
+ path: Union[str, os.PathLike[str]],
324
+ bare: bool = False,
325
+ _backend: Optional[str] = None,
327
326
  ) -> "Git":
328
327
  for name, backend in GitBackends.DEFAULT.items():
329
328
  if _backend and name != _backend:
330
329
  continue
331
330
  try:
332
- backend.init(path, bare=bare)
331
+ backend.init(os.fsdecode(path), bare=bare)
333
332
  # TODO: reuse created object instead of initializing a new one.
334
333
  return cls(path)
335
334
  except NotImplementedError:
@@ -492,8 +491,8 @@ class Git(Base):
492
491
  base: Optional[str] = None,
493
492
  match: Optional[str] = None,
494
493
  exclude: Optional[str] = None,
495
- ) -> Dict[str, Optional[str]]:
496
- results: Dict[str, Optional[str]] = {}
494
+ ) -> dict[str, Optional[str]]:
495
+ results: dict[str, Optional[str]] = {}
497
496
  remained_revs = set()
498
497
  if base == "refs/heads":
499
498
  current_rev = self.get_rev()
@@ -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, Iterable, Mapping, Optional, Tuple, Union
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
@@ -46,7 +47,7 @@ class BaseGitBackend(ABC):
46
47
  @abstractmethod
47
48
  def clone(
48
49
  url: str,
49
- to_path: str,
50
+ to_path: Union[str, os.PathLike[str]],
50
51
  shallow_branch: Optional[str] = None,
51
52
  progress: Optional[Callable[["GitProgressEvent"], None]] = None,
52
53
  bare: bool = False,
@@ -281,7 +282,7 @@ class BaseGitBackend(ABC):
281
282
  ref: str,
282
283
  message: Optional[str] = None,
283
284
  include_untracked: bool = False,
284
- ) -> Tuple[Optional[str], bool]:
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
- ) -> Tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
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: Tuple[str, ...], name: str) -> str:
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: Tuple[str, ...], name: str) -> bool:
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: Tuple[str, ...], name: str) -> Iterator[str]:
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: Dict[str, str] = self._find_submodules()
197
+ self._submodules: dict[str, str] = self._find_submodules()
203
198
  self._stashes: dict = {}
204
199
 
205
- def _find_submodules(self) -> Dict[str, str]:
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: Dict[str, str] = {}
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)
@@ -228,7 +223,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
228
223
  def clone(
229
224
  cls,
230
225
  url: str,
231
- to_path: str,
226
+ to_path: Union[str, os.PathLike[str]],
232
227
  shallow_branch: Optional[str] = None,
233
228
  progress: Optional[Callable[["GitProgressEvent"], None]] = None,
234
229
  bare: bool = False,
@@ -270,7 +265,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
270
265
  else:
271
266
  cls._set_default_tracking_branch(repo)
272
267
  except Exception as exc: # noqa: BLE001
273
- raise CloneError(url, to_path) from exc
268
+ raise CloneError(url, os.fsdecode(to_path)) from exc
274
269
 
275
270
  @staticmethod
276
271
  def _set_default_tracking_branch(repo: "Repo"):
@@ -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: List[bytes] = [
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: List[str], force: bool = False) -> Iterator[str]:
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: Dict[str, Any] = {} if untracked_files else {"untracked_files": "no"}
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: Dict[bytes, bytes],
705
+ remote_refs: dict[bytes, bytes],
711
706
  depth: Optional[int] = None, # pylint: disable=unused-argument
712
- ) -> List[bytes]:
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
- ) -> Tuple[Optional[str], bool]:
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: Dict[str, Optional[str]] = {}
840
- results: Dict[str, Optional[str]] = {}
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)
@@ -847,7 +842,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
847
842
  if revision and revision not in rev_mapping:
848
843
  rev_mapping[revision] = ref
849
844
  for rev in revs:
850
- results[rev] = rev_mapping.get(rev, None)
845
+ results[rev] = rev_mapping.get(rev)
851
846
  return results
852
847
 
853
848
  def diff(self, rev_a: str, rev_b: str, binary=False) -> str:
@@ -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
- ) -> Tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
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,8 +973,21 @@ 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) -> Tuple[str, 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}'")
985
980
  return m.group("name"), m.group("email")
981
+
982
+
983
+ def ls_remote(url: str) -> dict[str, str]:
984
+ from dulwich import porcelain
985
+ from dulwich.client import HTTPUnauthorized
986
+
987
+ try:
988
+ refs = porcelain.ls_remote(url)
989
+ return {os.fsdecode(ref): sha.decode("ascii") for ref, sha in refs.items()}
990
+ except HTTPUnauthorized as exc:
991
+ raise AuthError(url) from exc
992
+ except Exception as exc: # noqa: BLE001
993
+ raise InvalidRemote(url) from exc