scmrepo 3.3.4__tar.gz → 3.3.6__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 (73) hide show
  1. {scmrepo-3.3.4 → scmrepo-3.3.6}/.github/workflows/tests.yaml +3 -4
  2. {scmrepo-3.3.4 → scmrepo-3.3.6}/.pre-commit-config.yaml +2 -2
  3. {scmrepo-3.3.4/src/scmrepo.egg-info → scmrepo-3.3.6}/PKG-INFO +2 -2
  4. {scmrepo-3.3.4 → scmrepo-3.3.6}/noxfile.py +0 -8
  5. {scmrepo-3.3.4 → scmrepo-3.3.6}/pyproject.toml +8 -5
  6. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +5 -5
  7. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/dulwich/client.py +1 -1
  8. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/gitpython.py +1 -1
  9. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/pygit2/__init__.py +55 -78
  10. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/pygit2/callbacks.py +1 -1
  11. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/credentials.py +12 -12
  12. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/fetch.py +2 -2
  13. {scmrepo-3.3.4 → scmrepo-3.3.6/src/scmrepo.egg-info}/PKG-INFO +2 -2
  14. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo.egg-info/requires.txt +1 -1
  15. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/docker-compose.yml +2 -2
  16. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_dulwich.py +75 -7
  17. {scmrepo-3.3.4 → scmrepo-3.3.6}/.coveragerc +0 -0
  18. {scmrepo-3.3.4 → scmrepo-3.3.6}/.cruft.json +0 -0
  19. {scmrepo-3.3.4 → scmrepo-3.3.6}/.gitattributes +0 -0
  20. {scmrepo-3.3.4 → scmrepo-3.3.6}/.github/dependabot.yml +0 -0
  21. {scmrepo-3.3.4 → scmrepo-3.3.6}/.github/workflows/release.yaml +0 -0
  22. {scmrepo-3.3.4 → scmrepo-3.3.6}/.github/workflows/update-template.yaml +0 -0
  23. {scmrepo-3.3.4 → scmrepo-3.3.6}/.gitignore +0 -0
  24. {scmrepo-3.3.4 → scmrepo-3.3.6}/CODE_OF_CONDUCT.rst +0 -0
  25. {scmrepo-3.3.4 → scmrepo-3.3.6}/CONTRIBUTING.rst +0 -0
  26. {scmrepo-3.3.4 → scmrepo-3.3.6}/LICENSE +0 -0
  27. {scmrepo-3.3.4 → scmrepo-3.3.6}/README.rst +0 -0
  28. {scmrepo-3.3.4 → scmrepo-3.3.6}/setup.cfg +0 -0
  29. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/__init__.py +0 -0
  30. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/asyn.py +0 -0
  31. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/base.py +0 -0
  32. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/exceptions.py +0 -0
  33. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/fs.py +0 -0
  34. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/__init__.py +0 -0
  35. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/__init__.py +0 -0
  36. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/base.py +0 -0
  37. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/dulwich/__init__.py +0 -0
  38. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/backend/pygit2/filter.py +0 -0
  39. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/config.py +0 -0
  40. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/__init__.py +0 -0
  41. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/client.py +0 -0
  42. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/exceptions.py +0 -0
  43. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/object.py +0 -0
  44. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/pointer.py +0 -0
  45. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/progress.py +0 -0
  46. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/smudge.py +0 -0
  47. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/lfs/storage.py +0 -0
  48. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/objects.py +0 -0
  49. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/git/stash.py +0 -0
  50. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/noscm.py +0 -0
  51. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/progress.py +0 -0
  52. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/py.typed +0 -0
  53. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/urls.py +0 -0
  54. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo/utils.py +0 -0
  55. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo.egg-info/SOURCES.txt +0 -0
  56. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo.egg-info/dependency_links.txt +0 -0
  57. {scmrepo-3.3.4 → scmrepo-3.3.6}/src/scmrepo.egg-info/top_level.txt +0 -0
  58. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/__init__.py +0 -0
  59. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/conftest.py +0 -0
  60. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/git-init/git.sh +0 -0
  61. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_credentials.py +0 -0
  62. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_fs.py +0 -0
  63. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_git.py +0 -0
  64. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_lfs.py +0 -0
  65. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_noscm.py +0 -0
  66. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_pygit2.py +0 -0
  67. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_scmrepo.py +0 -0
  68. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_stash.py +0 -0
  69. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/test_urls.py +0 -0
  70. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/user.key +0 -0
  71. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/user.key.pub +0 -0
  72. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/vendor/__init__.py +0 -0
  73. {scmrepo-3.3.4 → scmrepo-3.3.6}/tests/vendor/test_paramiko_vendor.py +0 -0
@@ -40,12 +40,11 @@ jobs:
40
40
  pip --version
41
41
  nox --version
42
42
 
43
- - name: Lint code and check dependencies
44
- continue-on-error: ${{ matrix.nox_pyv == '3.11' }}
45
- run: nox -s lint safety --verbose
43
+ - name: Lint code
44
+ run: nox -s lint
46
45
 
47
46
  - name: Run tests
48
- run: nox -s tests-${{ matrix.nox_pyv || matrix.pyv }} -- --slow --cov-report=xml
47
+ run: nox -s tests-${{ matrix.pyv }} -- --slow --cov-report=xml
49
48
 
50
49
  - name: Upload coverage report
51
50
  uses: codecov/codecov-action@v3.1.0
@@ -20,13 +20,13 @@ 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.4.3'
23
+ rev: 'v0.5.0'
24
24
  hooks:
25
25
  - id: ruff
26
26
  args: [--fix, --exit-non-zero-on-fix]
27
27
  - id: ruff-format
28
28
  - repo: https://github.com/codespell-project/codespell
29
- rev: v2.2.6
29
+ rev: v2.3.0
30
30
  hooks:
31
31
  - id: codespell
32
32
  additional_dependencies: ["tomli"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scmrepo
3
- Version: 3.3.4
3
+ Version: 3.3.6
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -36,7 +36,7 @@ Requires-Dist: pytest-mock; extra == "tests"
36
36
  Requires-Dist: pytest-sugar; extra == "tests"
37
37
  Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
38
38
  Provides-Extra: dev
39
- Requires-Dist: mypy==1.10.0; extra == "dev"
39
+ Requires-Dist: mypy==1.10.1; extra == "dev"
40
40
  Requires-Dist: scmrepo[tests]; extra == "dev"
41
41
  Requires-Dist: types-certifi; extra == "dev"
42
42
  Requires-Dist: types-mock; extra == "dev"
@@ -32,14 +32,6 @@ def lint(session: nox.Session) -> None:
32
32
  session.run("python", "-m", "mypy")
33
33
 
34
34
 
35
- @nox.session
36
- def safety(session: nox.Session) -> None:
37
- """Scan dependencies for insecure packages."""
38
- session.install(".[dev]")
39
- session.install("safety")
40
- session.run("safety", "check", "--full-report", "--ignore=67599")
41
-
42
-
43
35
  @nox.session
44
36
  def build(session: nox.Session) -> None:
45
37
  session.install("build", "setuptools", "twine")
@@ -50,7 +50,7 @@ tests = [
50
50
  "pytest-test-utils>=0.1.0,<0.2",
51
51
  ]
52
52
  dev = [
53
- "mypy==1.10.0",
53
+ "mypy==1.10.1",
54
54
  "scmrepo[tests]",
55
55
  "types-certifi",
56
56
  "types-mock",
@@ -123,8 +123,13 @@ ignore_missing_imports = true
123
123
 
124
124
  [tool.codespell]
125
125
  ignore-words-list = "cachable, keypair"
126
+ skip = "CODE_OF_CONDUCT.rst"
126
127
 
127
128
  [tool.ruff]
129
+ output-format = "full"
130
+ show-fixes = true
131
+
132
+ [tool.ruff.lint]
128
133
  ignore = [
129
134
  "S101", # assert
130
135
  "PLR2004", # magic-value-comparison
@@ -180,10 +185,8 @@ select = [
180
185
  "W", # pycodestyle - Warning
181
186
  "YTT", # flake8-2020
182
187
  ]
183
- show-source = true
184
- show-fixes = true
185
188
 
186
- [tool.ruff.per-file-ignores]
189
+ [tool.ruff.lint.per-file-ignores]
187
190
  "noxfile.py" = ["D", "PTH"]
188
191
  "tests/**" = ["S", "ARG001", "ARG002", "ANN"]
189
192
  "docs/**" = ["INP"]
@@ -199,5 +202,5 @@ strict = true
199
202
  [tool.ruff.lint.isort]
200
203
  known-first-party = ["scmrepo"]
201
204
 
202
- [tool.ruff.pylint]
205
+ [tool.ruff.lint.pylint]
203
206
  max-args = 10
@@ -66,8 +66,8 @@ class _StderrWrapper:
66
66
  class AsyncSSHWrapper(BaseAsyncObject):
67
67
  def __init__(self, conn: "SSHClientConnection", proc: "SSHClientProcess", **kwargs):
68
68
  super().__init__(**kwargs)
69
- self.conn: "SSHClientConnection" = conn
70
- self.proc: "SSHClientProcess" = proc
69
+ self.conn: SSHClientConnection = conn
70
+ self.proc: SSHClientProcess = proc
71
71
  self.stderr = _StderrWrapper(proc.stderr, self.loop)
72
72
 
73
73
  def can_read(self) -> bool:
@@ -152,7 +152,7 @@ class InteractiveSSHClient(SSHClient):
152
152
  def connection_lost(self, exc: Optional[Exception]) -> None:
153
153
  self._conn = None
154
154
 
155
- async def public_key_auth_requested( # noqa: C901, PLR0912
155
+ async def public_key_auth_requested( # noqa: C901
156
156
  self,
157
157
  ) -> Optional["KeyPairListArg"]:
158
158
  from asyncssh.public_key import (
@@ -298,7 +298,7 @@ class AsyncSSHVendor(BaseAsyncObject, SSHVendor):
298
298
  host,
299
299
  port=port if port is not None else (),
300
300
  username=username if username is not None else (),
301
- password=password if password is not None else (),
301
+ password=password,
302
302
  client_keys=[key_filename] if key_filename else (),
303
303
  ignore_encrypted=not key_filename,
304
304
  known_hosts=None,
@@ -320,7 +320,7 @@ def get_unsupported_opts(config_paths: "ConfigPaths") -> Iterator[str]:
320
320
 
321
321
  if config_paths:
322
322
  if isinstance(config_paths, (str, PurePath)):
323
- paths: Sequence["FilePath"] = [config_paths]
323
+ paths: Sequence[FilePath] = [config_paths]
324
324
  else:
325
325
  paths = config_paths
326
326
 
@@ -22,7 +22,7 @@ class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstrac
22
22
  config=config,
23
23
  **kwargs,
24
24
  )
25
- self._store_credentials: Optional["Credential"] = None
25
+ self._store_credentials: Optional[Credential] = None
26
26
 
27
27
  def _http_request(
28
28
  self,
@@ -468,7 +468,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
468
468
  except GitCommandError as exc:
469
469
  raise SCMError(f"Failed to set ref '{name}'") from exc
470
470
 
471
- def get_ref(self, name: str, follow: bool = True) -> Optional[str]: # noqa: C901, PLR0911, PLR0912
471
+ def get_ref(self, name: str, follow: bool = True) -> Optional[str]: # noqa: C901, PLR0911
472
472
  from git.exc import GitCommandError
473
473
 
474
474
  if name == "HEAD":
@@ -34,6 +34,7 @@ logger = logging.getLogger(__name__)
34
34
  if TYPE_CHECKING:
35
35
  from pygit2 import Commit, Oid, Signature
36
36
  from pygit2.config import Config as _Pygit2Config
37
+ from pygit2.enums import CheckoutStrategy
37
38
  from pygit2.remotes import Remote
38
39
  from pygit2.repository import Repository
39
40
 
@@ -246,17 +247,15 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
246
247
  )
247
248
 
248
249
  @staticmethod
249
- def _get_checkout_strategy(strategy: Optional[int] = None):
250
- from pygit2 import (
251
- GIT_CHECKOUT_RECREATE_MISSING,
252
- GIT_CHECKOUT_SAFE,
253
- GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES,
254
- )
250
+ def _get_checkout_strategy(
251
+ strategy: Optional["CheckoutStrategy"] = None,
252
+ ) -> "CheckoutStrategy":
253
+ from pygit2.enums import CheckoutStrategy
255
254
 
256
255
  if strategy is None:
257
- strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING
256
+ strategy = CheckoutStrategy.SAFE | CheckoutStrategy.RECREATE_MISSING
258
257
  if os.name == "nt":
259
- strategy |= GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES
258
+ strategy |= CheckoutStrategy.SKIP_LOCKED_DIRECTORIES
260
259
  return strategy
261
260
 
262
261
  # Workaround to force git_backend_odb_pack to release open file handles
@@ -343,9 +342,12 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
343
342
  force: bool = False,
344
343
  **kwargs,
345
344
  ):
346
- from pygit2 import GIT_CHECKOUT_FORCE, GitError
345
+ from pygit2 import GitError
346
+ from pygit2.enums import CheckoutStrategy
347
347
 
348
- strategy = self._get_checkout_strategy(GIT_CHECKOUT_FORCE if force else None)
348
+ strategy = self._get_checkout_strategy(
349
+ CheckoutStrategy.FORCE if force else None
350
+ )
349
351
 
350
352
  with self.release_odb_handles():
351
353
  if create_new:
@@ -613,7 +615,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
613
615
  force: bool = False,
614
616
  on_diverged: Optional[Callable[[str, str], bool]] = None,
615
617
  ) -> SyncStatus:
616
- import pygit2
618
+ from pygit2.enums import MergeAnalysis
617
619
 
618
620
  rh_rev = self.resolve_rev(rh)
619
621
 
@@ -627,16 +629,16 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
627
629
  self.set_ref(lh, rh_rev)
628
630
  return SyncStatus.SUCCESS
629
631
 
630
- if merge_result & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE:
632
+ if merge_result & MergeAnalysis.UP_TO_DATE:
631
633
  return SyncStatus.UP_TO_DATE
632
- if merge_result & pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD:
634
+ if merge_result & MergeAnalysis.FASTFORWARD:
633
635
  self.set_ref(lh, rh_rev)
634
636
  return SyncStatus.SUCCESS
635
- if merge_result & pygit2.GIT_MERGE_ANALYSIS_NORMAL:
637
+ if merge_result & MergeAnalysis.NORMAL:
636
638
  if on_diverged and on_diverged(lh, rh_rev):
637
639
  return SyncStatus.SUCCESS
638
640
  return SyncStatus.DIVERGED
639
- logger.debug("Unexpected merge result: %s", pygit2.GIT_MERGE_ANALYSIS_NORMAL)
641
+ logger.debug("Unexpected merge result: %s", MergeAnalysis.NORMAL)
640
642
  raise SCMError("Unknown merge analysis result")
641
643
 
642
644
  @contextmanager
@@ -696,7 +698,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
696
698
  SCMError(f"Git failed to fetch ref from '{url}'"),
697
699
  ):
698
700
  with RemoteCallbacks(progress=progress) as cb:
699
- remote_refs: dict[str, "Oid"] = (
701
+ remote_refs: dict[str, Oid] = (
700
702
  {
701
703
  head["name"]: head["oid"]
702
704
  for head in remote.ls_remotes(callbacks=cb)
@@ -710,7 +712,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
710
712
  message="fetch",
711
713
  )
712
714
 
713
- result: dict[str, "SyncStatus"] = {}
715
+ result: dict[str, SyncStatus] = {}
714
716
  for refspec in refspecs:
715
717
  lh, rh = refspec.split(":")
716
718
  if lh.endswith("*"):
@@ -779,7 +781,8 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
779
781
  skip_conflicts: bool = False,
780
782
  **kwargs,
781
783
  ):
782
- from pygit2 import GIT_CHECKOUT_ALLOW_CONFLICTS, GitError
784
+ from pygit2 import GitError
785
+ from pygit2.enums import CheckoutStrategy
783
786
 
784
787
  from scmrepo.git import Stash
785
788
 
@@ -788,7 +791,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
788
791
  self.repo.index.read(False)
789
792
  strategy = self._get_checkout_strategy()
790
793
  if skip_conflicts:
791
- strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS
794
+ strategy |= CheckoutStrategy.ALLOW_CONFLICTS
792
795
  self.repo.stash_apply(
793
796
  index, strategy=strategy, reinstate_index=reinstate_index
794
797
  )
@@ -834,7 +837,8 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
834
837
  raise NotImplementedError
835
838
 
836
839
  def reset(self, hard: bool = False, paths: Optional[Iterable[str]] = None):
837
- from pygit2 import GIT_RESET_HARD, GIT_RESET_MIXED, IndexEntry
840
+ from pygit2 import IndexEntry
841
+ from pygit2.enums import ResetMode
838
842
 
839
843
  self.repo.index.read(False)
840
844
  if paths is not None:
@@ -847,9 +851,9 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
847
851
  self.repo.index.add(IndexEntry(rel, obj.id, obj.filemode))
848
852
  self.repo.index.write()
849
853
  elif hard:
850
- self.repo.reset(self.repo.head.target, GIT_RESET_HARD)
854
+ self.repo.reset(self.repo.head.target, ResetMode.HARD)
851
855
  else:
852
- self.repo.reset(self.repo.head.target, GIT_RESET_MIXED)
856
+ self.repo.reset(self.repo.head.target, ResetMode.MIXED)
853
857
 
854
858
  def checkout_index(
855
859
  self,
@@ -858,22 +862,17 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
858
862
  ours: bool = False,
859
863
  theirs: bool = False,
860
864
  ):
861
- from pygit2 import (
862
- GIT_CHECKOUT_ALLOW_CONFLICTS,
863
- GIT_CHECKOUT_FORCE,
864
- GIT_CHECKOUT_RECREATE_MISSING,
865
- GIT_CHECKOUT_SAFE,
866
- )
865
+ from pygit2.enums import CheckoutStrategy
867
866
 
868
867
  assert not (ours and theirs)
869
- strategy = GIT_CHECKOUT_RECREATE_MISSING
868
+ strategy = CheckoutStrategy.RECREATE_MISSING
870
869
  if force or ours or theirs:
871
- strategy |= GIT_CHECKOUT_FORCE
870
+ strategy |= CheckoutStrategy.FORCE
872
871
  else:
873
- strategy |= GIT_CHECKOUT_SAFE
872
+ strategy |= CheckoutStrategy.SAFE
874
873
 
875
874
  if ours or theirs:
876
- strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS
875
+ strategy |= CheckoutStrategy.ALLOW_CONFLICTS
877
876
  strategy = self._get_checkout_strategy(strategy)
878
877
 
879
878
  index = self.repo.index
@@ -910,18 +909,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
910
909
  def status(
911
910
  self, ignored: bool = False, untracked_files: str = "all"
912
911
  ) -> tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
913
- from pygit2 import (
914
- GIT_STATUS_IGNORED,
915
- GIT_STATUS_INDEX_DELETED,
916
- GIT_STATUS_INDEX_MODIFIED,
917
- GIT_STATUS_INDEX_NEW,
918
- GIT_STATUS_WT_DELETED,
919
- GIT_STATUS_WT_MODIFIED,
920
- GIT_STATUS_WT_NEW,
921
- GIT_STATUS_WT_RENAMED,
922
- GIT_STATUS_WT_TYPECHANGE,
923
- GIT_STATUS_WT_UNREADABLE,
924
- )
912
+ from pygit2.enums import FileStatus
925
913
 
926
914
  staged: Mapping[str, list[str]] = {
927
915
  "add": [],
@@ -932,19 +920,19 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
932
920
  untracked: list[str] = []
933
921
 
934
922
  states = {
935
- GIT_STATUS_WT_NEW: untracked,
936
- GIT_STATUS_WT_MODIFIED: unstaged,
937
- GIT_STATUS_WT_TYPECHANGE: staged["modify"],
938
- GIT_STATUS_WT_DELETED: staged["modify"],
939
- GIT_STATUS_WT_RENAMED: staged["modify"],
940
- GIT_STATUS_INDEX_NEW: staged["add"],
941
- GIT_STATUS_INDEX_MODIFIED: staged["modify"],
942
- GIT_STATUS_INDEX_DELETED: staged["delete"],
943
- GIT_STATUS_WT_UNREADABLE: untracked,
923
+ FileStatus.WT_NEW: untracked,
924
+ FileStatus.WT_MODIFIED: unstaged,
925
+ FileStatus.WT_TYPECHANGE: staged["modify"],
926
+ FileStatus.WT_DELETED: staged["modify"],
927
+ FileStatus.WT_RENAMED: staged["modify"],
928
+ FileStatus.INDEX_NEW: staged["add"],
929
+ FileStatus.INDEX_MODIFIED: staged["modify"],
930
+ FileStatus.INDEX_DELETED: staged["delete"],
931
+ FileStatus.WT_UNREADABLE: untracked,
944
932
  }
945
933
 
946
934
  if untracked_files != "no" and ignored:
947
- states[GIT_STATUS_IGNORED] = untracked
935
+ states[FileStatus.IGNORED] = untracked
948
936
 
949
937
  for file, state in self.repo.status(
950
938
  untracked_files=untracked_files, ignored=ignored
@@ -970,15 +958,8 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
970
958
  msg: Optional[str] = None,
971
959
  squash: bool = False,
972
960
  ) -> Optional[str]:
973
- from pygit2 import (
974
- GIT_MERGE_ANALYSIS_FASTFORWARD,
975
- GIT_MERGE_ANALYSIS_NONE,
976
- GIT_MERGE_ANALYSIS_UNBORN,
977
- GIT_MERGE_ANALYSIS_UP_TO_DATE,
978
- GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY,
979
- GIT_MERGE_PREFERENCE_NO_FASTFORWARD,
980
- GitError,
981
- )
961
+ from pygit2 import GitError
962
+ from pygit2.enums import MergeAnalysis, MergePreference
982
963
 
983
964
  if commit and squash:
984
965
  raise SCMError("Cannot merge with 'squash' and 'commit'")
@@ -991,9 +972,9 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
991
972
  except GitError as exc:
992
973
  raise SCMError("Merge analysis failed") from exc
993
974
 
994
- if analysis == GIT_MERGE_ANALYSIS_NONE:
975
+ if analysis == MergeAnalysis.NONE:
995
976
  raise SCMError(f"'{rev}' cannot be merged into HEAD")
996
- if analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE:
977
+ if analysis & MergeAnalysis.UP_TO_DATE:
997
978
  return None
998
979
 
999
980
  try:
@@ -1006,15 +987,15 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
1006
987
  raise MergeConflictError("Merge contained conflicts")
1007
988
 
1008
989
  try:
1009
- if not (squash or ff_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD):
1010
- if analysis & GIT_MERGE_ANALYSIS_FASTFORWARD:
990
+ if not (squash or ff_pref & MergePreference.NO_FASTFORWARD):
991
+ if analysis & MergeAnalysis.FASTFORWARD:
1011
992
  return self._merge_ff(rev, obj)
1012
993
 
1013
- if analysis & GIT_MERGE_ANALYSIS_UNBORN:
994
+ if analysis & MergeAnalysis.UNBORN:
1014
995
  self.repo.set_head(obj.id)
1015
996
  return str(obj.id)
1016
997
 
1017
- if ff_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY:
998
+ if ff_pref & MergePreference.FASTFORWARD_ONLY:
1018
999
  raise SCMError(f"Cannot fast-forward HEAD to '{rev}'")
1019
1000
 
1020
1001
  if commit:
@@ -1105,19 +1086,15 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
1105
1086
  attr: str,
1106
1087
  source: Optional[str] = None,
1107
1088
  ) -> Optional[Union[bool, str]]:
1108
- from pygit2 import (
1109
- GIT_ATTR_CHECK_FILE_THEN_INDEX,
1110
- GIT_ATTR_CHECK_INCLUDE_COMMIT,
1111
- GIT_ATTR_CHECK_INDEX_ONLY,
1112
- GitError,
1113
- )
1089
+ from pygit2 import GitError
1090
+ from pygit2.enums import AttrCheck
1114
1091
 
1115
- commit: Optional["Commit"] = None
1116
- flags = GIT_ATTR_CHECK_FILE_THEN_INDEX
1092
+ commit: Optional[Commit] = None
1093
+ flags = AttrCheck.FILE_THEN_INDEX
1117
1094
  if source:
1118
1095
  try:
1119
1096
  commit, _ref = self._resolve_refish(source)
1120
- flags = GIT_ATTR_CHECK_INDEX_ONLY | GIT_ATTR_CHECK_INCLUDE_COMMIT
1097
+ flags = AttrCheck.INDEX_ONLY | AttrCheck.INCLUDE_COMMIT
1121
1098
  except (KeyError, GitError) as exc:
1122
1099
  raise SCMError(f"Invalid commit '{source}'") from exc
1123
1100
  try:
@@ -28,7 +28,7 @@ class RemoteCallbacks(_RemoteCallbacks, AbstractContextManager):
28
28
  ):
29
29
  super().__init__(*args, **kwargs)
30
30
  self.progress = GitProgressReporter(progress) if progress else None
31
- self._store_credentials: Optional["Credential"] = None
31
+ self._store_credentials: Optional[Credential] = None
32
32
  self._tried_credentials = False
33
33
  self.result: dict[str, SyncStatus] = {}
34
34
 
@@ -136,8 +136,8 @@ class GitCredentialHelper(CredentialHelper):
136
136
  if not shutil.which(executable) and shutil.which("git"):
137
137
  # If the helper cannot be found in PATH, it might be
138
138
  # a C git helper in GIT_EXEC_PATH
139
- git_exec_path = subprocess.check_output(
140
- ("git", "--exec-path"), # noqa: S603
139
+ git_exec_path = subprocess.check_output( # noqa: S603
140
+ ("git", "--exec-path"),
141
141
  text=True,
142
142
  ).strip()
143
143
  if shutil.which(executable, path=git_exec_path):
@@ -158,8 +158,8 @@ class GitCredentialHelper(CredentialHelper):
158
158
  helper_input.append("")
159
159
 
160
160
  try:
161
- res = subprocess.run(
162
- cmd, # noqa: S603
161
+ res = subprocess.run( # noqa: S603
162
+ cmd,
163
163
  check=True,
164
164
  capture_output=True,
165
165
  input="\n".join(helper_input),
@@ -199,8 +199,8 @@ class GitCredentialHelper(CredentialHelper):
199
199
  helper_input.append("")
200
200
 
201
201
  try:
202
- res = subprocess.run(
203
- cmd, # noqa: S603
202
+ res = subprocess.run( # noqa: S603
203
+ cmd,
204
204
  capture_output=True,
205
205
  input="\n".join(helper_input),
206
206
  encoding=self._encoding,
@@ -224,8 +224,8 @@ class GitCredentialHelper(CredentialHelper):
224
224
  helper_input.append("")
225
225
 
226
226
  try:
227
- res = subprocess.run(
228
- cmd, # noqa: S603
227
+ res = subprocess.run( # noqa: S603
228
+ cmd,
229
229
  capture_output=True,
230
230
  input="\n".join(helper_input),
231
231
  encoding=self._encoding,
@@ -243,7 +243,7 @@ class GitCredentialHelper(CredentialHelper):
243
243
  ) -> Iterator[tuple[str, bool]]:
244
244
  config = config or StackedConfig.default()
245
245
  if isinstance(config, StackedConfig):
246
- backends: Iterable["ConfigDict"] = config.backends
246
+ backends: Iterable[ConfigDict] = config.backends
247
247
  else:
248
248
  backends = [config]
249
249
 
@@ -324,7 +324,7 @@ class MemoryCredentialHelper(CredentialHelper):
324
324
 
325
325
  def __init__(self):
326
326
  super().__init__()
327
- self._credentials: dict["_CredentialKey", "Credential"] = {}
327
+ self._credentials: dict[_CredentialKey, Credential] = {}
328
328
 
329
329
  def __getitem__(self, key: object) -> "Credential":
330
330
  if isinstance(key, _CredentialKey):
@@ -448,8 +448,8 @@ class _AskpassCommand:
448
448
  def input(self, prompt: str) -> str:
449
449
  argv = [self.command, prompt]
450
450
  try:
451
- res = subprocess.run(
452
- argv, # noqa: S603
451
+ res = subprocess.run( # noqa: S603
452
+ argv,
453
453
  check=True,
454
454
  capture_output=True,
455
455
  encoding=locale.getpreferredencoding(),
@@ -46,7 +46,7 @@ def fetch(
46
46
  scm.lfs_storage.fetch(url, objects, progress=progress)
47
47
 
48
48
 
49
- def get_fetch_url(scm: "Git", remote: Optional[str] = None): # noqa: C901,PLR0912
49
+ def get_fetch_url(scm: "Git", remote: Optional[str] = None): # noqa: C901
50
50
  """Return LFS fetch URL for the specified repository."""
51
51
  git_config = scm.get_config()
52
52
 
@@ -56,7 +56,7 @@ def get_fetch_url(scm: "Git", remote: Optional[str] = None): # noqa: C901,PLR09
56
56
  except KeyError:
57
57
  pass
58
58
  try:
59
- lfs_config: Optional["Config"] = scm.get_config(
59
+ lfs_config: Optional[Config] = scm.get_config(
60
60
  os.path.join(scm.root_dir, ".lfsconfig")
61
61
  )
62
62
  except FileNotFoundError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scmrepo
3
- Version: 3.3.4
3
+ Version: 3.3.6
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -36,7 +36,7 @@ Requires-Dist: pytest-mock; extra == "tests"
36
36
  Requires-Dist: pytest-sugar; extra == "tests"
37
37
  Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
38
38
  Provides-Extra: dev
39
- Requires-Dist: mypy==1.10.0; extra == "dev"
39
+ Requires-Dist: mypy==1.10.1; extra == "dev"
40
40
  Requires-Dist: scmrepo[tests]; extra == "dev"
41
41
  Requires-Dist: types-certifi; extra == "dev"
42
42
  Requires-Dist: types-mock; extra == "dev"
@@ -10,7 +10,7 @@ aiohttp-retry>=2.5.0
10
10
  tqdm
11
11
 
12
12
  [dev]
13
- mypy==1.10.0
13
+ mypy==1.10.1
14
14
  scmrepo[tests]
15
15
  types-certifi
16
16
  types-mock
@@ -2,7 +2,7 @@
2
2
  version: '3.2'
3
3
  services:
4
4
  git-server:
5
- image: ghcr.io/linuxserver/openssh-server:9.0_p1-r2-ls100
5
+ image: ghcr.io/linuxserver/openssh-server
6
6
  environment:
7
7
  - USER_NAME=user
8
8
  - PUBLIC_KEY_FILE=/tmp/key
@@ -10,4 +10,4 @@ services:
10
10
  - 2222
11
11
  volumes:
12
12
  - ./user.key.pub:/tmp/key
13
- - ./git-init:/config/custom-cont-init.d
13
+ - ./git-init:/custom-cont-init.d
@@ -11,18 +11,74 @@ import pytest
11
11
  from pytest_mock import MockerFixture
12
12
  from pytest_test_utils.waiters import wait_until
13
13
 
14
+ from scmrepo.exceptions import AuthError
14
15
  from scmrepo.git.backend.dulwich.asyncssh_vendor import AsyncSSHVendor
15
16
 
16
- from .vendor.test_paramiko_vendor import (
17
- CLIENT_KEY,
18
- PASSWORD,
19
- USER,
20
- Server,
21
- )
22
-
23
17
  # pylint: disable=redefined-outer-name
24
18
 
25
19
 
20
+ USER = "testuser"
21
+ PASSWORD = "test"
22
+ CLIENT_KEY = """-----BEGIN RSA PRIVATE KEY-----
23
+ MIIEpAIBAAKCAQEAxvREKSElPOm/0z/nPO+j5rk2tjdgGcGc7We1QZ6TRXYLu7nN
24
+ GeEFIL4p8N1i6dmB+Eydt7xqCU79MWD6Yy4prFe1+/K1wCDUxIbFMxqQcX5zjJzd
25
+ i8j8PbcaUlVhP/OkjtkSxrXaGDO1BzfdV4iEBtTV/2l3zmLKJlt3jnOHLczP24CB
26
+ DTQKp3rKshbRefzot9Y+wnaK692RsYgsyo9YEP0GyWKG9topCHk13r46J6vGLeuj
27
+ ryUKqmbLJkzbJbIcEqwTDo5iHaCVqaMr5Hrb8BdMucSseqZQJsXSd+9tdRcIblUQ
28
+ 38kZjmFMm4SFbruJcpZCNM2wNSZPIRX+3eiwNwIDAQABAoIBAHSacOBSJsr+jIi5
29
+ KUOTh9IPtzswVUiDKwARCjB9Sf8p4lKR4N1L/n9kNJyQhApeikgGT2GCMftmqgoo
30
+ tlculQoHFgemBlOmak0MV8NNzF5YKEy/GzF0CDH7gJfEpoyetVFrdA+2QS5yD6U9
31
+ XqKQxiBi2VEqdScmyyeT8AwzNYTnPeH/DOEcnbdRjqiy/CD79F49CQ1lX1Fuqm0K
32
+ I7BivBH1xo/rVnUP4F+IzocDqoga+Pjdj0LTXIgJlHQDSbhsQqWujWQDDuKb+MAw
33
+ sNK4Zf8ErV3j1PyA7f/M5LLq6zgstkW4qikDHo4SpZX8kFOO8tjqb7kujj7XqeaB
34
+ CxqrOTECgYEA73uWkrohcmDJ4KqbuL3tbExSCOUiaIV+sT1eGPNi7GCmXD4eW5Z4
35
+ 75v2IHymW83lORSu/DrQ6sKr1nkuRpqr2iBzRmQpl/H+wahIhBXlnJ25uUjDsuPO
36
+ 1Pq2LcmyD+jTxVnmbSe/q7O09gZQw3I6H4+BMHmpbf8tC97lqimzpJ0CgYEA1K0W
37
+ ZL70Xtn9quyHvbtae/BW07NZnxvUg4UaVIAL9Zu34JyplJzyzbIjrmlDbv6aRogH
38
+ /KtuG9tfbf55K/jjqNORiuRtzt1hUN1ye4dyW7tHx2/7lXdlqtyK40rQl8P0kqf8
39
+ zaS6BqjnobgSdSpg32rWoL/pcBHPdJCJEgQ8zeMCgYEA0/PK8TOhNIzrP1dgGSKn
40
+ hkkJ9etuB5nW5mEM7gJDFDf6JPupfJ/xiwe6z0fjKK9S57EhqgUYMB55XYnE5iIw
41
+ ZQ6BV9SAZ4V7VsRs4dJLdNC3tn/rDGHJBgCaym2PlbsX6rvFT+h1IC8dwv0V79Ui
42
+ Ehq9WTzkMoE8yhvNokvkPZUCgYEAgBAFxv5xGdh79ftdtXLmhnDvZ6S8l6Fjcxqo
43
+ Ay/jg66Tp43OU226iv/0mmZKM8Dd1xC8dnon4GBVc19jSYYiWBulrRPlx0Xo/o+K
44
+ CzZBN1lrXH1i6dqufpc0jq8TMf/N+q1q/c1uMupsKCY1/xVYpc+ok71b7J7c49zQ
45
+ nOeuUW8CgYA9Infooy65FTgbzca0c9kbCUBmcAPQ2ItH3JcPKWPQTDuV62HcT00o
46
+ fZdIV47Nez1W5Clk191RMy8TXuqI54kocciUWpThc6j44hz49oUueb8U4bLcEHzA
47
+ WxtWBWHwxfSmqgTXilEA3ALJp0kNolLnEttnhENwJpZHlqtes0ZA4w==
48
+ -----END RSA PRIVATE KEY-----"""
49
+
50
+
51
+ class Server(paramiko.ServerInterface):
52
+ """http://docs.paramiko.org/en/2.4/api/server.html."""
53
+
54
+ def __init__(self, commands, *args, **kwargs) -> None:
55
+ super().__init__(*args, **kwargs)
56
+ self.commands = commands
57
+
58
+ def check_channel_exec_request(self, channel, command):
59
+ self.commands.append(command)
60
+ return True
61
+
62
+ def check_auth_password(self, username, password):
63
+ if username == USER and password == PASSWORD:
64
+ return paramiko.AUTH_SUCCESSFUL
65
+ return paramiko.AUTH_FAILED
66
+
67
+ def check_auth_publickey(self, username, key):
68
+ pubkey = paramiko.RSAKey.from_private_key(StringIO(CLIENT_KEY))
69
+ if username == USER and key == pubkey:
70
+ return paramiko.AUTH_SUCCESSFUL
71
+ return paramiko.AUTH_FAILED
72
+
73
+ def check_channel_request(self, kind, chanid):
74
+ if kind == "session":
75
+ return paramiko.OPEN_SUCCEEDED
76
+ return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
77
+
78
+ def get_allowed_auths(self, username):
79
+ return "password,publickey"
80
+
81
+
26
82
  @pytest.fixture
27
83
  def ssh_conn(request: pytest.FixtureRequest) -> dict[str, Any]:
28
84
  server = Server([])
@@ -77,6 +133,18 @@ def test_run_command_password(server: Server, ssh_port: int):
77
133
  assert b"test_run_command_password" in server.commands
78
134
 
79
135
 
136
+ def test_run_command_no_password(server: Server, ssh_port: int):
137
+ vendor = AsyncSSHVendor()
138
+ with pytest.raises(AuthError):
139
+ vendor.run_command(
140
+ "127.0.0.1",
141
+ "test_run_command_password",
142
+ username=USER,
143
+ port=ssh_port,
144
+ password=None,
145
+ )
146
+
147
+
80
148
  def test_run_command_with_privkey(server: Server, ssh_port: int):
81
149
  key = asyncssh.import_private_key(CLIENT_KEY)
82
150
 
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