scmrepo 3.3.12__tar.gz → 3.5.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.12 → scmrepo-3.5.0}/.github/workflows/release.yaml +2 -2
- {scmrepo-3.3.12 → scmrepo-3.5.0}/.github/workflows/tests.yaml +1 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/.pre-commit-config.yaml +2 -2
- {scmrepo-3.3.12/src/scmrepo.egg-info → scmrepo-3.5.0}/PKG-INFO +3 -3
- {scmrepo-3.3.12 → scmrepo-3.5.0}/noxfile.py +5 -1
- {scmrepo-3.3.12 → scmrepo-3.5.0}/pyproject.toml +15 -5
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/base.py +1 -1
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/dulwich/__init__.py +33 -6
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/gitpython.py +16 -13
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/pygit2/__init__.py +66 -40
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/pygit2/filter.py +4 -4
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/progress.py +13 -9
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/utils.py +11 -7
- {scmrepo-3.3.12 → scmrepo-3.5.0/src/scmrepo.egg-info}/PKG-INFO +3 -3
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo.egg-info/requires.txt +1 -1
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_credentials.py +1 -1
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_dulwich.py +9 -9
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_git.py +162 -11
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/vendor/test_paramiko_vendor.py +6 -6
- {scmrepo-3.3.12 → scmrepo-3.5.0}/.coveragerc +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/.cruft.json +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/.gitattributes +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/.github/dependabot.yml +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/.github/workflows/update-template.yaml +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/.gitignore +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/CODE_OF_CONDUCT.rst +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/CONTRIBUTING.rst +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/LICENSE +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/README.rst +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/setup.cfg +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/__init__.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/asyn.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/base.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/exceptions.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/fs.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/__init__.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/__init__.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/dulwich/client.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/backend/pygit2/callbacks.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/config.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/credentials.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/__init__.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/client.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/exceptions.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/fetch.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/object.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/pointer.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/progress.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/smudge.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/lfs/storage.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/objects.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/git/stash.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/noscm.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/py.typed +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo/urls.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo.egg-info/SOURCES.txt +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo.egg-info/dependency_links.txt +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/src/scmrepo.egg-info/top_level.txt +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/__init__.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/conftest.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/docker-compose.yml +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/git-init/git.sh +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_fs.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_lfs.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_noscm.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_pygit2.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_scmrepo.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_stash.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/test_urls.py +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/user.key +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/user.key.pub +0 -0
- {scmrepo-3.3.12 → scmrepo-3.5.0}/tests/vendor/__init__.py +0 -0
|
@@ -20,9 +20,9 @@ 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.12.
|
|
23
|
+
rev: 'v0.12.5'
|
|
24
24
|
hooks:
|
|
25
|
-
- id: ruff
|
|
25
|
+
- id: ruff-check
|
|
26
26
|
args: [--fix, --exit-non-zero-on-fix]
|
|
27
27
|
- id: ruff-format
|
|
28
28
|
- repo: https://github.com/codespell-project/codespell
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
|
-
License: Apache-2.0
|
|
6
|
+
License-Expression: 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
|
|
@@ -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.24.0
|
|
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
|
|
@@ -10,7 +10,11 @@ nox.options.sessions = "lint", "tests"
|
|
|
10
10
|
locations = "src", "tests"
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
project = nox.project.load_toml()
|
|
14
|
+
python_versions = nox.project.python_versions(project)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@nox.session(python=python_versions)
|
|
14
18
|
def tests(session: nox.Session) -> None:
|
|
15
19
|
session.install(".[tests]")
|
|
16
20
|
session.run(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=77", "setuptools_scm[toml]>=8"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[tool.setuptools_scm]
|
|
@@ -8,7 +8,8 @@ build-backend = "setuptools.build_meta"
|
|
|
8
8
|
name = "scmrepo"
|
|
9
9
|
description = "scmrepo"
|
|
10
10
|
readme = "README.rst"
|
|
11
|
-
license =
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
12
13
|
authors = [{ name = "Iterative", email = "support@dvc.org" }]
|
|
13
14
|
classifiers = [
|
|
14
15
|
"Programming Language :: Python :: 3",
|
|
@@ -23,7 +24,7 @@ requires-python = ">=3.9"
|
|
|
23
24
|
dynamic = ["version"]
|
|
24
25
|
dependencies = [
|
|
25
26
|
"gitpython>3",
|
|
26
|
-
"dulwich>=0.
|
|
27
|
+
"dulwich>=0.24.0",
|
|
27
28
|
"pygit2>=1.14.0",
|
|
28
29
|
"pygtrie>=2.3.2",
|
|
29
30
|
"fsspec[tqdm]>=2024.2.0",
|
|
@@ -102,7 +103,7 @@ show_error_codes = true
|
|
|
102
103
|
show_error_context = true
|
|
103
104
|
show_traceback = true
|
|
104
105
|
pretty = true
|
|
105
|
-
check_untyped_defs =
|
|
106
|
+
check_untyped_defs = true
|
|
106
107
|
# Warnings
|
|
107
108
|
warn_no_return = true
|
|
108
109
|
warn_redundant_casts = true
|
|
@@ -192,8 +193,9 @@ select = [
|
|
|
192
193
|
|
|
193
194
|
[tool.ruff.lint.per-file-ignores]
|
|
194
195
|
"noxfile.py" = ["D", "PTH"]
|
|
195
|
-
"tests/**" = ["S", "ARG001", "ARG002", "ANN"]
|
|
196
|
+
"tests/**" = ["S", "ARG001", "ARG002", "ANN", "TID251", "TID253"]
|
|
196
197
|
"docs/**" = ["INP"]
|
|
198
|
+
"src/scmrepo/git/backend/gitpython.py" = ["TID251"]
|
|
197
199
|
|
|
198
200
|
[tool.ruff.lint.flake8-pytest-style]
|
|
199
201
|
fixture-parentheses = false
|
|
@@ -203,6 +205,14 @@ parametrize-names-type = "csv"
|
|
|
203
205
|
[tool.ruff.lint.flake8-type-checking]
|
|
204
206
|
strict = true
|
|
205
207
|
|
|
208
|
+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
209
|
+
"git".msg = "importing from 'git' is not allowed except inside `gitpython` backend"
|
|
210
|
+
|
|
211
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
212
|
+
# Ban certain modules from being imported at module level, instead requiring
|
|
213
|
+
# that they're imported lazily (e.g., within a function definition).
|
|
214
|
+
banned-module-level-imports = ["git"]
|
|
215
|
+
|
|
206
216
|
[tool.ruff.lint.isort]
|
|
207
217
|
known-first-party = ["scmrepo"]
|
|
208
218
|
|
|
@@ -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
|
|
@@ -203,9 +204,11 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
203
204
|
|
|
204
205
|
Submodule paths will be relative to Git repo root.
|
|
205
206
|
"""
|
|
207
|
+
|
|
206
208
|
from dulwich.config import ConfigFile, parse_submodules
|
|
207
209
|
|
|
208
210
|
submodules: dict[str, str] = {}
|
|
211
|
+
assert self.root_dir
|
|
209
212
|
config_path = os.path.join(self.root_dir, ".gitmodules")
|
|
210
213
|
if os.path.isfile(config_path):
|
|
211
214
|
config = ConfigFile.from_path(config_path)
|
|
@@ -217,7 +220,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
217
220
|
self.repo.close()
|
|
218
221
|
|
|
219
222
|
@property
|
|
220
|
-
def root_dir(self) -> str:
|
|
223
|
+
def root_dir(self) -> Optional[str]:
|
|
221
224
|
return self.repo.path
|
|
222
225
|
|
|
223
226
|
@classmethod
|
|
@@ -354,6 +357,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
354
357
|
# path relative to the submodule root.
|
|
355
358
|
fs_path = relpath(path, self.root_dir)
|
|
356
359
|
for sm_path in self._submodules.values():
|
|
360
|
+
assert self.root_dir
|
|
357
361
|
if fs_path.startswith(sm_path):
|
|
358
362
|
path = os.path.join(
|
|
359
363
|
self.root_dir,
|
|
@@ -378,7 +382,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
378
382
|
|
|
379
383
|
with reraise((Error, CommitError), SCMError("Git commit failed")):
|
|
380
384
|
try:
|
|
381
|
-
commit(self.
|
|
385
|
+
commit(self.repo, message=msg, no_verify=no_verify)
|
|
382
386
|
except InvalidUserIdentity as exc:
|
|
383
387
|
raise SCMError("Git username and email must be configured") from exc
|
|
384
388
|
except TimezoneFormatError as exc:
|
|
@@ -421,7 +425,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
421
425
|
from dulwich.porcelain import Error, branch_create
|
|
422
426
|
|
|
423
427
|
try:
|
|
424
|
-
branch_create(self.
|
|
428
|
+
branch_create(self.repo, branch)
|
|
425
429
|
except Error as exc:
|
|
426
430
|
raise SCMError(f"Failed to create branch '{branch}'") from exc
|
|
427
431
|
|
|
@@ -473,7 +477,25 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
473
477
|
return sorted(ref[len(base) :] for ref in self.iter_refs(base))
|
|
474
478
|
|
|
475
479
|
def list_all_commits(self) -> Iterable[str]:
|
|
476
|
-
|
|
480
|
+
from dulwich.objects import Tag
|
|
481
|
+
|
|
482
|
+
repo = self.repo
|
|
483
|
+
starting_points: list[bytes] = []
|
|
484
|
+
|
|
485
|
+
# HEAD
|
|
486
|
+
head_rev = self.get_ref("HEAD")
|
|
487
|
+
if head_rev:
|
|
488
|
+
starting_points.append(head_rev.encode("utf-8"))
|
|
489
|
+
|
|
490
|
+
# Branches and remotes
|
|
491
|
+
for ref in repo.refs:
|
|
492
|
+
if ref.startswith((b"refs/heads/", b"refs/remotes/", b"refs/tags/")):
|
|
493
|
+
if isinstance(repo.refs[ref], Tag):
|
|
494
|
+
ref = self.repo.get_peeled(repo.refs[ref])
|
|
495
|
+
starting_points.append(repo.refs[ref])
|
|
496
|
+
|
|
497
|
+
walker = self.repo.get_walker(include=starting_points, order=ORDER_DATE)
|
|
498
|
+
return [e.commit.id.decode() for e in walker]
|
|
477
499
|
|
|
478
500
|
def get_tree_obj(self, rev: str, **kwargs) -> DulwichObject:
|
|
479
501
|
from dulwich.objectspec import parse_tree
|
|
@@ -629,8 +651,13 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
629
651
|
def update_refs(refs):
|
|
630
652
|
from dulwich.objects import ZERO_SHA
|
|
631
653
|
|
|
654
|
+
_refspecs = (
|
|
655
|
+
os.fsencode(refspecs)
|
|
656
|
+
if isinstance(refspecs, str)
|
|
657
|
+
else [os.fsencode(refspec) for refspec in refspecs]
|
|
658
|
+
)
|
|
632
659
|
selected_refs.extend(
|
|
633
|
-
parse_reftuples(self.repo.refs, refs,
|
|
660
|
+
parse_reftuples(self.repo.refs, refs, _refspecs, force=force)
|
|
634
661
|
)
|
|
635
662
|
new_refs = {}
|
|
636
663
|
for lh, rh, _ in selected_refs:
|
|
@@ -882,7 +909,7 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
882
909
|
|
|
883
910
|
with reraise(Error, SCMError("Git status failed")):
|
|
884
911
|
staged, unstaged, untracked = git_status(
|
|
885
|
-
self.
|
|
912
|
+
self.repo, ignored=ignored, untracked_files=untracked_files
|
|
886
913
|
)
|
|
887
914
|
|
|
888
915
|
return (
|
|
@@ -161,8 +161,9 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
161
161
|
return bool(func(str(path)))
|
|
162
162
|
|
|
163
163
|
@property
|
|
164
|
-
def root_dir(self) -> str:
|
|
165
|
-
|
|
164
|
+
def root_dir(self) -> Optional[str]:
|
|
165
|
+
d = self.repo.working_tree_dir
|
|
166
|
+
return os.fspath(d) if d is not None else d
|
|
166
167
|
|
|
167
168
|
@staticmethod
|
|
168
169
|
@requires_git
|
|
@@ -241,7 +242,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
241
242
|
|
|
242
243
|
@property
|
|
243
244
|
def dir(self) -> str:
|
|
244
|
-
return self.repo.git_dir
|
|
245
|
+
return os.fspath(self.repo.git_dir)
|
|
245
246
|
|
|
246
247
|
def add(
|
|
247
248
|
self,
|
|
@@ -262,7 +263,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
262
263
|
paths = [path for path in paths if not self.is_ignored(path)]
|
|
263
264
|
self.git.add(*paths, **kwargs)
|
|
264
265
|
else:
|
|
265
|
-
self.repo.index.add(paths)
|
|
266
|
+
self.repo.index.add(paths if isinstance(paths, str) else list(paths))
|
|
266
267
|
except AssertionError as exc:
|
|
267
268
|
# NOTE: GitPython is not currently able to handle index version >= 3.
|
|
268
269
|
# See https://github.com/iterative/dvc/issues/610 for more details.
|
|
@@ -301,7 +302,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
301
302
|
kwargs["force"] = True
|
|
302
303
|
if unshallow:
|
|
303
304
|
kwargs["unshallow"] = True
|
|
304
|
-
infos = self.repo.remote(name=remote).fetch(**kwargs)
|
|
305
|
+
infos = self.repo.remote(name=remote).fetch(**kwargs) # type: ignore[arg-type]
|
|
305
306
|
for info in infos:
|
|
306
307
|
if info.flags & info.ERROR:
|
|
307
308
|
raise SCMError(f"fetch failed: {info.note}")
|
|
@@ -350,7 +351,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
350
351
|
|
|
351
352
|
def active_branch_remote(self) -> str:
|
|
352
353
|
try:
|
|
353
|
-
return self.repo.active_branch.tracking_branch()
|
|
354
|
+
return self.repo.active_branch.tracking_branch() # type: ignore[return-value]
|
|
354
355
|
except (TypeError, ValueError) as exc:
|
|
355
356
|
raise SCMError("No active branch tracking remote") from exc
|
|
356
357
|
|
|
@@ -428,15 +429,15 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
428
429
|
return GitCommit(
|
|
429
430
|
commit.hexsha,
|
|
430
431
|
commit.committed_date,
|
|
431
|
-
commit.committer_tz_offset,
|
|
432
|
-
commit.message,
|
|
432
|
+
commit.committer_tz_offset, # type: ignore[arg-type]
|
|
433
|
+
commit.message, # type: ignore[arg-type]
|
|
433
434
|
[str(parent) for parent in commit.parents],
|
|
434
|
-
commit.committer.name,
|
|
435
|
-
commit.committer.email,
|
|
436
|
-
commit.author.name,
|
|
437
|
-
commit.author.email,
|
|
435
|
+
commit.committer.name, # type: ignore[arg-type]
|
|
436
|
+
commit.committer.email, # type: ignore[arg-type]
|
|
437
|
+
commit.author.name, # type: ignore[arg-type]
|
|
438
|
+
commit.author.email, # type: ignore[arg-type]
|
|
438
439
|
commit.authored_date,
|
|
439
|
-
commit.author_tz_offset,
|
|
440
|
+
commit.author_tz_offset, # type: ignore[arg-type]
|
|
440
441
|
)
|
|
441
442
|
|
|
442
443
|
def set_ref(
|
|
@@ -744,6 +745,8 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
744
745
|
if not ref.tag:
|
|
745
746
|
return ref.commit.hexsha
|
|
746
747
|
tag = ref.tag
|
|
748
|
+
assert tag.tagger.email
|
|
749
|
+
assert tag.tagger.name
|
|
747
750
|
return GitTag(
|
|
748
751
|
tag.tag,
|
|
749
752
|
tag.hexsha,
|
|
@@ -171,7 +171,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
171
171
|
# NOTE: we want this init to be lazy so we do it on backend init.
|
|
172
172
|
# for subsequent backend instances, this call will error out since
|
|
173
173
|
# the filter is already registered
|
|
174
|
-
pygit2.filter_register("lfs", LFSFilter)
|
|
174
|
+
pygit2.filter_register("lfs", LFSFilter) # type: ignore[attr-defined]
|
|
175
175
|
except ValueError:
|
|
176
176
|
pass
|
|
177
177
|
|
|
@@ -181,7 +181,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
181
181
|
self.repo.free()
|
|
182
182
|
|
|
183
183
|
@property
|
|
184
|
-
def root_dir(self) -> str:
|
|
184
|
+
def root_dir(self) -> Optional[str]:
|
|
185
185
|
return self.repo.workdir
|
|
186
186
|
|
|
187
187
|
@cached_property
|
|
@@ -194,10 +194,10 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
194
194
|
from pygit2 import Tag
|
|
195
195
|
from pygit2.enums import ObjectType
|
|
196
196
|
|
|
197
|
-
commit, ref = self.repo.resolve_refish(refish)
|
|
197
|
+
commit, ref = self.repo.resolve_refish(refish) # type: ignore[attr-defined]
|
|
198
198
|
if isinstance(commit, Tag):
|
|
199
199
|
ref = commit
|
|
200
|
-
commit = commit.peel(ObjectType.COMMIT)
|
|
200
|
+
commit = commit.peel(ObjectType.COMMIT) # type: ignore[call-overload]
|
|
201
201
|
return commit, ref
|
|
202
202
|
|
|
203
203
|
@property
|
|
@@ -354,8 +354,8 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
354
354
|
with self.release_odb_handles():
|
|
355
355
|
if create_new:
|
|
356
356
|
commit = self.repo.revparse_single("HEAD")
|
|
357
|
-
new_branch = self.repo.branches.local.create(branch, commit)
|
|
358
|
-
self.repo.checkout(new_branch, strategy=strategy)
|
|
357
|
+
new_branch = self.repo.branches.local.create(branch, commit) # type: ignore[arg-type]
|
|
358
|
+
self.repo.checkout(new_branch, strategy=strategy) # type: ignore[attr-defined]
|
|
359
359
|
else:
|
|
360
360
|
if branch == "-":
|
|
361
361
|
branch = "@{-1}"
|
|
@@ -363,12 +363,12 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
363
363
|
commit, ref = self._resolve_refish(branch)
|
|
364
364
|
except (KeyError, GitError) as exc:
|
|
365
365
|
raise RevError(f"unknown Git revision '{branch}'") from exc
|
|
366
|
-
self.repo.checkout_tree(commit, strategy=strategy)
|
|
366
|
+
self.repo.checkout_tree(commit, strategy=strategy) # type: ignore[attr-defined]
|
|
367
367
|
detach = kwargs.get("detach", False)
|
|
368
368
|
if ref and not detach:
|
|
369
|
-
self.repo.set_head(ref.name)
|
|
369
|
+
self.repo.set_head(ref.name) # type: ignore[attr-defined]
|
|
370
370
|
else:
|
|
371
|
-
self.repo.set_head(commit.id)
|
|
371
|
+
self.repo.set_head(commit.id) # type: ignore[attr-defined]
|
|
372
372
|
|
|
373
373
|
def fetch(
|
|
374
374
|
self,
|
|
@@ -389,7 +389,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
389
389
|
|
|
390
390
|
try:
|
|
391
391
|
commit = self.repo[self.repo.head.target]
|
|
392
|
-
self.repo.create_branch(branch, commit)
|
|
392
|
+
self.repo.create_branch(branch, commit) # type: ignore[arg-type]
|
|
393
393
|
except GitError as exc:
|
|
394
394
|
raise SCMError(f"Failed to create branch '{branch}'") from exc
|
|
395
395
|
|
|
@@ -431,7 +431,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
431
431
|
# if HEAD points to a nonexistent branch we still return the
|
|
432
432
|
# branch name (without "refs/heads/" prefix) to match gitpython's
|
|
433
433
|
# behavior
|
|
434
|
-
return self.repo.references["HEAD"].target[11:]
|
|
434
|
+
return self.repo.references["HEAD"].target[11:] # type: ignore[index]
|
|
435
435
|
return self.repo.head.shorthand
|
|
436
436
|
|
|
437
437
|
def active_branch_remote(self) -> str:
|
|
@@ -452,10 +452,35 @@ 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 o in starting_points:
|
|
478
|
+
walker.push(o)
|
|
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
|
-
tree = self.repo[rev].tree
|
|
483
|
+
tree = self.repo[rev].tree # type: ignore[attr-defined]
|
|
459
484
|
return Pygit2Object(tree, backend=self)
|
|
460
485
|
|
|
461
486
|
def get_rev(self) -> str:
|
|
@@ -546,7 +571,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
546
571
|
try:
|
|
547
572
|
obj = self.repo[ref.target]
|
|
548
573
|
if isinstance(obj, Tag):
|
|
549
|
-
return str(obj.peel(ObjectType.COMMIT).id)
|
|
574
|
+
return str(obj.peel(ObjectType.COMMIT).id) # type: ignore[call-overload]
|
|
550
575
|
except ValueError:
|
|
551
576
|
pass
|
|
552
577
|
|
|
@@ -759,7 +784,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
759
784
|
from scmrepo.git import Stash
|
|
760
785
|
|
|
761
786
|
try:
|
|
762
|
-
oid = self.repo.stash(
|
|
787
|
+
oid = self.repo.stash( # type: ignore[attr-defined]
|
|
763
788
|
self.committer,
|
|
764
789
|
message=message,
|
|
765
790
|
include_untracked=include_untracked,
|
|
@@ -770,8 +795,8 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
770
795
|
commit = self.repo[oid]
|
|
771
796
|
|
|
772
797
|
if ref != Stash.DEFAULT_STASH:
|
|
773
|
-
self.set_ref(ref, commit.id, message=commit.message)
|
|
774
|
-
self.repo.stash_drop()
|
|
798
|
+
self.set_ref(ref, commit.id, message=commit.message) # type: ignore[attr-defined,arg-type]
|
|
799
|
+
self.repo.stash_drop() # type: ignore[attr-defined]
|
|
775
800
|
return str(oid), False
|
|
776
801
|
|
|
777
802
|
def _stash_apply(
|
|
@@ -788,11 +813,11 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
788
813
|
|
|
789
814
|
def _apply(index):
|
|
790
815
|
try:
|
|
791
|
-
self.repo.index.read(False)
|
|
816
|
+
self.repo.index.read(False) # type: ignore[attr-defined]
|
|
792
817
|
strategy = self._get_checkout_strategy()
|
|
793
818
|
if skip_conflicts:
|
|
794
819
|
strategy |= CheckoutStrategy.ALLOW_CONFLICTS
|
|
795
|
-
self.repo.stash_apply(
|
|
820
|
+
self.repo.stash_apply( # type: ignore[attr-defined]
|
|
796
821
|
index, strategy=strategy, reinstate_index=reinstate_index
|
|
797
822
|
)
|
|
798
823
|
except GitError as exc:
|
|
@@ -814,7 +839,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
814
839
|
try:
|
|
815
840
|
_apply(0)
|
|
816
841
|
finally:
|
|
817
|
-
self.repo.stash_drop()
|
|
842
|
+
self.repo.stash_drop() # type: ignore[attr-defined]
|
|
818
843
|
|
|
819
844
|
def _stash_drop(self, ref: str, index: int):
|
|
820
845
|
from scmrepo.git import Stash
|
|
@@ -822,7 +847,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
822
847
|
if ref != Stash.DEFAULT_STASH:
|
|
823
848
|
raise NotImplementedError
|
|
824
849
|
|
|
825
|
-
self.repo.stash_drop(index)
|
|
850
|
+
self.repo.stash_drop(index) # type: ignore[attr-defined]
|
|
826
851
|
|
|
827
852
|
def _describe(
|
|
828
853
|
self,
|
|
@@ -840,16 +865,16 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
840
865
|
from pygit2 import IndexEntry
|
|
841
866
|
from pygit2.enums import ResetMode
|
|
842
867
|
|
|
843
|
-
self.repo.index.read(False)
|
|
868
|
+
self.repo.index.read(False) # type: ignore[attr-defined]
|
|
844
869
|
if paths is not None:
|
|
845
|
-
tree = self.repo.revparse_single("HEAD").tree
|
|
870
|
+
tree = self.repo.revparse_single("HEAD").tree # type: ignore[attr-defined]
|
|
846
871
|
for path in paths:
|
|
847
872
|
rel = relpath(path, self.root_dir)
|
|
848
873
|
if os.name == "nt":
|
|
849
874
|
rel = rel.replace("\\", "/")
|
|
850
875
|
obj = tree[rel]
|
|
851
|
-
self.repo.index.add(IndexEntry(rel, obj.id, obj.filemode))
|
|
852
|
-
self.repo.index.write()
|
|
876
|
+
self.repo.index.add(IndexEntry(rel, obj.id, obj.filemode)) # type: ignore[attr-defined]
|
|
877
|
+
self.repo.index.write() # type: ignore[attr-defined]
|
|
853
878
|
elif hard:
|
|
854
879
|
self.repo.reset(self.repo.head.target, ResetMode.HARD)
|
|
855
880
|
else:
|
|
@@ -875,7 +900,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
875
900
|
strategy |= CheckoutStrategy.ALLOW_CONFLICTS
|
|
876
901
|
strategy = self._get_checkout_strategy(strategy)
|
|
877
902
|
|
|
878
|
-
index = self.repo.index
|
|
903
|
+
index = self.repo.index # type: ignore[attr-defined]
|
|
879
904
|
if paths:
|
|
880
905
|
path_list: Optional[list[str]] = [
|
|
881
906
|
relpath(path, self.root_dir) for path in paths
|
|
@@ -889,7 +914,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
889
914
|
path_list = None
|
|
890
915
|
|
|
891
916
|
with self.release_odb_handles():
|
|
892
|
-
self.repo.checkout_index(index=index, paths=path_list, strategy=strategy)
|
|
917
|
+
self.repo.checkout_index(index=index, paths=path_list, strategy=strategy) # type: ignore[attr-defined]
|
|
893
918
|
|
|
894
919
|
if index.conflicts and (ours or theirs):
|
|
895
920
|
for ancestor, ours_entry, theirs_entry in index.conflicts:
|
|
@@ -900,9 +925,10 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
900
925
|
index.add(ours_entry)
|
|
901
926
|
else:
|
|
902
927
|
entry = theirs_entry
|
|
928
|
+
assert self.root_dir
|
|
903
929
|
path = os.path.join(self.root_dir, entry.path)
|
|
904
930
|
with open(path, "wb") as fobj:
|
|
905
|
-
fobj.write(self.repo.get(entry.id).read_raw())
|
|
931
|
+
fobj.write(self.repo.get(entry.id).read_raw()) # type: ignore[attr-defined]
|
|
906
932
|
index.add(entry.path)
|
|
907
933
|
index.write()
|
|
908
934
|
|
|
@@ -965,8 +991,8 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
965
991
|
raise SCMError("Cannot merge with 'squash' and 'commit'")
|
|
966
992
|
|
|
967
993
|
with self.release_odb_handles():
|
|
968
|
-
self.repo.index.read(False)
|
|
969
|
-
obj, _ref = self.repo.resolve_refish(rev)
|
|
994
|
+
self.repo.index.read(False) # type: ignore[attr-defined]
|
|
995
|
+
obj, _ref = self.repo.resolve_refish(rev) # type: ignore[attr-defined]
|
|
970
996
|
try:
|
|
971
997
|
analysis, ff_pref = self.repo.merge_analysis(obj.id)
|
|
972
998
|
except GitError as exc:
|
|
@@ -978,12 +1004,12 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
978
1004
|
return None
|
|
979
1005
|
|
|
980
1006
|
try:
|
|
981
|
-
self.repo.merge(obj.id)
|
|
982
|
-
self.repo.index.write()
|
|
1007
|
+
self.repo.merge(obj.id) # type: ignore[attr-defined]
|
|
1008
|
+
self.repo.index.write() # type: ignore[attr-defined]
|
|
983
1009
|
except GitError as exc:
|
|
984
1010
|
raise SCMError("Merge failed") from exc
|
|
985
1011
|
|
|
986
|
-
if self.repo.index.conflicts:
|
|
1012
|
+
if self.repo.index.conflicts: # type: ignore[attr-defined]
|
|
987
1013
|
raise MergeConflictError("Merge contained conflicts")
|
|
988
1014
|
|
|
989
1015
|
try:
|
|
@@ -992,7 +1018,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
992
1018
|
return self._merge_ff(rev, obj)
|
|
993
1019
|
|
|
994
1020
|
if analysis & MergeAnalysis.UNBORN:
|
|
995
|
-
self.repo.set_head(obj.id)
|
|
1021
|
+
self.repo.set_head(obj.id) # type: ignore[attr-defined]
|
|
996
1022
|
return str(obj.id)
|
|
997
1023
|
|
|
998
1024
|
if ff_pref & MergePreference.FASTFORWARD_ONLY:
|
|
@@ -1005,12 +1031,12 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
1005
1031
|
# HEAD is not moved and merge changes stay in index
|
|
1006
1032
|
return None
|
|
1007
1033
|
finally:
|
|
1008
|
-
self.repo.state_cleanup()
|
|
1009
|
-
self.repo.index.write()
|
|
1034
|
+
self.repo.state_cleanup() # type: ignore[attr-defined]
|
|
1035
|
+
self.repo.index.write() # type: ignore[attr-defined]
|
|
1010
1036
|
|
|
1011
1037
|
def _merge_ff(self, rev: str, obj) -> str:
|
|
1012
1038
|
if self.repo.head_is_detached:
|
|
1013
|
-
self.repo.set_head(obj.id)
|
|
1039
|
+
self.repo.set_head(obj.id) # type: ignore[attr-defined]
|
|
1014
1040
|
else:
|
|
1015
1041
|
branch = self.get_ref("HEAD", follow=False)
|
|
1016
1042
|
assert branch
|
|
@@ -1024,7 +1050,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
1024
1050
|
def _merge_commit(self, msg: Optional[str], obj) -> str:
|
|
1025
1051
|
if not msg:
|
|
1026
1052
|
raise SCMError("Merge commit message is required")
|
|
1027
|
-
tree = self.repo.index.write_tree()
|
|
1053
|
+
tree = self.repo.index.write_tree() # type: ignore[attr-defined]
|
|
1028
1054
|
merge_commit = self.repo.create_commit(
|
|
1029
1055
|
"HEAD",
|
|
1030
1056
|
self.author,
|
|
@@ -1078,7 +1104,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
1078
1104
|
|
|
1079
1105
|
if path:
|
|
1080
1106
|
return Pygit2Config(_Pygit2Config(path))
|
|
1081
|
-
return Pygit2Config(self.repo.config)
|
|
1107
|
+
return Pygit2Config(self.repo.config) # type: ignore[attr-defined]
|
|
1082
1108
|
|
|
1083
1109
|
def check_attr(
|
|
1084
1110
|
self,
|
|
@@ -1098,7 +1124,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
1098
1124
|
except (KeyError, GitError) as exc:
|
|
1099
1125
|
raise SCMError(f"Invalid commit '{source}'") from exc
|
|
1100
1126
|
try:
|
|
1101
|
-
return self.repo.get_attr(
|
|
1127
|
+
return self.repo.get_attr( # type: ignore[attr-defined]
|
|
1102
1128
|
path, attr, flags=flags, commit=commit.id if commit else None
|
|
1103
1129
|
)
|
|
1104
1130
|
except GitError as exc:
|
|
@@ -18,22 +18,22 @@ class LFSFilter(Filter):
|
|
|
18
18
|
self._smudge_root: Optional[str] = None
|
|
19
19
|
|
|
20
20
|
def check(self, src: "FilterSource", attr_values: list[Optional[str]]):
|
|
21
|
-
if attr_values[0] == "lfs" and src.mode != GIT_FILTER_CLEAN:
|
|
21
|
+
if attr_values[0] == "lfs" and src.mode != GIT_FILTER_CLEAN: # type: ignore[attr-defined]
|
|
22
22
|
self._smudge_buf = io.BytesIO()
|
|
23
|
-
self._smudge_root = src.repo.workdir or src.repo.path
|
|
23
|
+
self._smudge_root = src.repo.workdir or src.repo.path # type: ignore[attr-defined]
|
|
24
24
|
return
|
|
25
25
|
raise Passthrough
|
|
26
26
|
|
|
27
27
|
def write(
|
|
28
28
|
self, data: bytes, src: "FilterSource", write_next: Callable[[bytes], None]
|
|
29
29
|
):
|
|
30
|
-
if src.mode == GIT_FILTER_CLEAN:
|
|
30
|
+
if src.mode == GIT_FILTER_CLEAN: # type: ignore[attr-defined]
|
|
31
31
|
write_next(data)
|
|
32
32
|
return
|
|
33
33
|
if self._smudge_buf is None:
|
|
34
34
|
self._smudge_buf = io.BytesIO()
|
|
35
35
|
if self._smudge_root is None:
|
|
36
|
-
self._smudge_root = src.repo.workdir or src.repo.path
|
|
36
|
+
self._smudge_root = src.repo.workdir or src.repo.path # type: ignore[attr-defined]
|
|
37
37
|
self._smudge_buf.write(data)
|
|
38
38
|
|
|
39
39
|
def close(self, write_next: Callable[[bytes], None]):
|
|
@@ -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,9 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from collections.abc import MutableMapping
|
|
2
|
+
from collections.abc import Iterator, MutableMapping
|
|
3
|
+
from typing import Callable, TypeVar, Union
|
|
3
4
|
|
|
5
|
+
K = TypeVar("K")
|
|
6
|
+
V = TypeVar("V")
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
class LazyDict(MutableMapping[K, V]):
|
|
10
|
+
def __init__(self, values: dict[K, Union[V, Callable[[], V]]]):
|
|
7
11
|
self._values = values
|
|
8
12
|
|
|
9
13
|
def __getitem__(self, item):
|
|
@@ -13,16 +17,16 @@ class LazyDict(MutableMapping):
|
|
|
13
17
|
self._values[item] = value
|
|
14
18
|
return value
|
|
15
19
|
|
|
16
|
-
def __setitem__(self, key, value):
|
|
20
|
+
def __setitem__(self, key: K, value: Union[V, Callable[[], V]]) -> None:
|
|
17
21
|
self._values[key] = value
|
|
18
22
|
|
|
19
|
-
def __delitem__(self, key):
|
|
23
|
+
def __delitem__(self, key: K) -> None:
|
|
20
24
|
del self._values[key]
|
|
21
25
|
|
|
22
|
-
def __iter__(self):
|
|
26
|
+
def __iter__(self) -> Iterator[K]:
|
|
23
27
|
return iter(self._values)
|
|
24
28
|
|
|
25
|
-
def __len__(self):
|
|
29
|
+
def __len__(self) -> int:
|
|
26
30
|
return len(self._values)
|
|
27
31
|
|
|
28
32
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
|
-
License: Apache-2.0
|
|
6
|
+
License-Expression: 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
|
|
@@ -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.24.0
|
|
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
|
|
@@ -73,7 +73,7 @@ def test_subprocess_strip_trailing_garbage_bytes(git_helper, mocker):
|
|
|
73
73
|
"subprocess.run",
|
|
74
74
|
# Simulate git-credential-osxkeychain (version 2.45), assuming initial 0-byte
|
|
75
75
|
return_value=mocker.Mock(
|
|
76
|
-
stdout=f"username=foo\npassword=bar{chr(0)}{random.randbytes(15)}"
|
|
76
|
+
stdout=f"username=foo\npassword=bar{chr(0)}{random.randbytes(15)}" # type: ignore[str-bytes-safe]
|
|
77
77
|
),
|
|
78
78
|
)
|
|
79
79
|
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
|
|
@@ -68,24 +68,24 @@ class Server(paramiko.ServerInterface):
|
|
|
68
68
|
|
|
69
69
|
def check_auth_interactive_response(self, responses):
|
|
70
70
|
if responses[0] == PASSWORD:
|
|
71
|
-
return paramiko.AUTH_SUCCESSFUL
|
|
72
|
-
return paramiko.AUTH_FAILED
|
|
71
|
+
return paramiko.AUTH_SUCCESSFUL # type: ignore[attr-defined]
|
|
72
|
+
return paramiko.AUTH_FAILED # type: ignore[attr-defined]
|
|
73
73
|
|
|
74
74
|
def check_auth_password(self, username, password):
|
|
75
75
|
if username == USER and password == PASSWORD:
|
|
76
|
-
return paramiko.AUTH_SUCCESSFUL
|
|
77
|
-
return paramiko.AUTH_FAILED
|
|
76
|
+
return paramiko.AUTH_SUCCESSFUL # type: ignore[attr-defined]
|
|
77
|
+
return paramiko.AUTH_FAILED # type: ignore[attr-defined]
|
|
78
78
|
|
|
79
79
|
def check_auth_publickey(self, username, key):
|
|
80
80
|
pubkey = paramiko.RSAKey.from_private_key(StringIO(CLIENT_KEY))
|
|
81
81
|
if username == USER and key == pubkey:
|
|
82
|
-
return paramiko.AUTH_SUCCESSFUL
|
|
83
|
-
return paramiko.AUTH_FAILED
|
|
82
|
+
return paramiko.AUTH_SUCCESSFUL # type: ignore[attr-defined]
|
|
83
|
+
return paramiko.AUTH_FAILED # type: ignore[attr-defined]
|
|
84
84
|
|
|
85
85
|
def check_channel_request(self, kind, chanid):
|
|
86
86
|
if kind == "session":
|
|
87
|
-
return paramiko.OPEN_SUCCEEDED
|
|
88
|
-
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
|
87
|
+
return paramiko.OPEN_SUCCEEDED # type: ignore[attr-defined]
|
|
88
|
+
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED # type: ignore[attr-defined]
|
|
89
89
|
|
|
90
90
|
def get_allowed_auths(self, username):
|
|
91
91
|
return self.allowed_auths
|
|
@@ -111,7 +111,7 @@ def ssh_conn(request: pytest.FixtureRequest) -> dict[str, Any]:
|
|
|
111
111
|
conn, _ = sock.accept()
|
|
112
112
|
except OSError:
|
|
113
113
|
return False
|
|
114
|
-
server.transport = transport = paramiko.Transport(conn)
|
|
114
|
+
server.transport = transport = paramiko.Transport(conn) # type: ignore[attr-defined]
|
|
115
115
|
request.addfinalizer(transport.close)
|
|
116
116
|
host_key = paramiko.RSAKey.from_private_key(StringIO(CLIENT_KEY))
|
|
117
117
|
transport.add_server_key(host_key)
|
|
@@ -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")
|
|
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"])
|
|
@@ -961,6 +1111,7 @@ def proxy_server():
|
|
|
961
1111
|
pass
|
|
962
1112
|
|
|
963
1113
|
_ProxyServer.setUpClass()
|
|
1114
|
+
assert _ProxyServer.PROXY
|
|
964
1115
|
yield f"http://{_ProxyServer.PROXY.flags.hostname}:{_ProxyServer.PROXY.flags.port}"
|
|
965
1116
|
_ProxyServer.tearDownClass()
|
|
966
1117
|
|
|
@@ -41,19 +41,19 @@ else:
|
|
|
41
41
|
|
|
42
42
|
def check_auth_password(self, username, password):
|
|
43
43
|
if username == USER and password == PASSWORD:
|
|
44
|
-
return paramiko.AUTH_SUCCESSFUL
|
|
45
|
-
return paramiko.AUTH_FAILED
|
|
44
|
+
return paramiko.AUTH_SUCCESSFUL # type: ignore[attr-defined]
|
|
45
|
+
return paramiko.AUTH_FAILED # type: ignore[attr-defined]
|
|
46
46
|
|
|
47
47
|
def check_auth_publickey(self, username, key):
|
|
48
48
|
pubkey = paramiko.RSAKey.from_private_key(StringIO(CLIENT_KEY))
|
|
49
49
|
if username == USER and key == pubkey:
|
|
50
|
-
return paramiko.AUTH_SUCCESSFUL
|
|
51
|
-
return paramiko.AUTH_FAILED
|
|
50
|
+
return paramiko.AUTH_SUCCESSFUL # type: ignore[attr-defined]
|
|
51
|
+
return paramiko.AUTH_FAILED # type: ignore[attr-defined]
|
|
52
52
|
|
|
53
53
|
def check_channel_request(self, kind, chanid):
|
|
54
54
|
if kind == "session":
|
|
55
|
-
return paramiko.OPEN_SUCCEEDED
|
|
56
|
-
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
|
55
|
+
return paramiko.OPEN_SUCCEEDED # type: ignore[attr-defined]
|
|
56
|
+
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED # type: ignore[attr-defined]
|
|
57
57
|
|
|
58
58
|
def get_allowed_auths(self, username):
|
|
59
59
|
return "password,publickey"
|
|
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
|