scmrepo 3.3.10__tar.gz → 3.3.11__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.10 → scmrepo-3.3.11}/.github/workflows/tests.yaml +2 -2
  2. {scmrepo-3.3.10 → scmrepo-3.3.11}/.pre-commit-config.yaml +2 -2
  3. {scmrepo-3.3.10/src/scmrepo.egg-info → scmrepo-3.3.11}/PKG-INFO +4 -3
  4. {scmrepo-3.3.10 → scmrepo-3.3.11}/pyproject.toml +1 -1
  5. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +19 -65
  6. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/pygit2/__init__.py +3 -1
  7. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/pygit2/callbacks.py +2 -0
  8. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/objects.py +1 -1
  9. {scmrepo-3.3.10 → scmrepo-3.3.11/src/scmrepo.egg-info}/PKG-INFO +4 -3
  10. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo.egg-info/requires.txt +1 -1
  11. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_dulwich.py +39 -26
  12. {scmrepo-3.3.10 → scmrepo-3.3.11}/.coveragerc +0 -0
  13. {scmrepo-3.3.10 → scmrepo-3.3.11}/.cruft.json +0 -0
  14. {scmrepo-3.3.10 → scmrepo-3.3.11}/.gitattributes +0 -0
  15. {scmrepo-3.3.10 → scmrepo-3.3.11}/.github/dependabot.yml +0 -0
  16. {scmrepo-3.3.10 → scmrepo-3.3.11}/.github/workflows/release.yaml +0 -0
  17. {scmrepo-3.3.10 → scmrepo-3.3.11}/.github/workflows/update-template.yaml +0 -0
  18. {scmrepo-3.3.10 → scmrepo-3.3.11}/.gitignore +0 -0
  19. {scmrepo-3.3.10 → scmrepo-3.3.11}/CODE_OF_CONDUCT.rst +0 -0
  20. {scmrepo-3.3.10 → scmrepo-3.3.11}/CONTRIBUTING.rst +0 -0
  21. {scmrepo-3.3.10 → scmrepo-3.3.11}/LICENSE +0 -0
  22. {scmrepo-3.3.10 → scmrepo-3.3.11}/README.rst +0 -0
  23. {scmrepo-3.3.10 → scmrepo-3.3.11}/noxfile.py +0 -0
  24. {scmrepo-3.3.10 → scmrepo-3.3.11}/setup.cfg +0 -0
  25. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/__init__.py +0 -0
  26. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/asyn.py +0 -0
  27. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/base.py +0 -0
  28. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/exceptions.py +0 -0
  29. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/fs.py +0 -0
  30. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/__init__.py +0 -0
  31. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/__init__.py +0 -0
  32. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/base.py +0 -0
  33. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/dulwich/__init__.py +0 -0
  34. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/dulwich/client.py +0 -0
  35. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/gitpython.py +0 -0
  36. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/pygit2/filter.py +0 -0
  37. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/config.py +0 -0
  38. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/credentials.py +0 -0
  39. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/__init__.py +0 -0
  40. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/client.py +0 -0
  41. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/exceptions.py +0 -0
  42. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/fetch.py +0 -0
  43. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/object.py +0 -0
  44. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/pointer.py +0 -0
  45. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/progress.py +0 -0
  46. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/smudge.py +0 -0
  47. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/storage.py +0 -0
  48. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/stash.py +0 -0
  49. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/noscm.py +0 -0
  50. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/progress.py +0 -0
  51. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/py.typed +0 -0
  52. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/urls.py +0 -0
  53. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/utils.py +0 -0
  54. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo.egg-info/SOURCES.txt +0 -0
  55. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo.egg-info/dependency_links.txt +0 -0
  56. {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo.egg-info/top_level.txt +0 -0
  57. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/__init__.py +0 -0
  58. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/conftest.py +0 -0
  59. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/docker-compose.yml +0 -0
  60. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/git-init/git.sh +0 -0
  61. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_credentials.py +0 -0
  62. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_fs.py +0 -0
  63. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_git.py +0 -0
  64. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_lfs.py +0 -0
  65. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_noscm.py +0 -0
  66. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_pygit2.py +0 -0
  67. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_scmrepo.py +0 -0
  68. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_stash.py +0 -0
  69. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_urls.py +0 -0
  70. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/user.key +0 -0
  71. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/user.key.pub +0 -0
  72. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/vendor/__init__.py +0 -0
  73. {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/vendor/test_paramiko_vendor.py +0 -0
@@ -20,7 +20,7 @@ jobs:
20
20
  strategy:
21
21
  fail-fast: false
22
22
  matrix:
23
- os: [ubuntu-20.04, windows-latest, macos-latest]
23
+ os: [ubuntu-latest, windows-latest, macos-latest]
24
24
  pyv: ['3.9', '3.10', '3.11', '3.12', '3.13']
25
25
 
26
26
  steps:
@@ -47,7 +47,7 @@ jobs:
47
47
  run: nox -s tests-${{ matrix.pyv }} -- --slow --cov-report=xml
48
48
 
49
49
  - name: Upload coverage report
50
- uses: codecov/codecov-action@v5.3.1
50
+ uses: codecov/codecov-action@v5
51
51
 
52
52
  - name: Build package
53
53
  run: nox -s build
@@ -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.9.3'
23
+ rev: 'v0.11.7'
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.4.0
29
+ rev: v2.4.1
30
30
  hooks:
31
31
  - id: codespell
32
32
  additional_dependencies: ["tomli"]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: scmrepo
3
- Version: 3.3.10
3
+ Version: 3.3.11
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -38,12 +38,13 @@ Requires-Dist: pytest-sugar; extra == "tests"
38
38
  Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
39
39
  Requires-Dist: proxy.py; extra == "tests"
40
40
  Provides-Extra: dev
41
- Requires-Dist: mypy==1.14.1; extra == "dev"
41
+ Requires-Dist: mypy==1.15.0; extra == "dev"
42
42
  Requires-Dist: scmrepo[tests]; extra == "dev"
43
43
  Requires-Dist: types-certifi; extra == "dev"
44
44
  Requires-Dist: types-mock; extra == "dev"
45
45
  Requires-Dist: types-paramiko; extra == "dev"
46
46
  Requires-Dist: types-tqdm; extra == "dev"
47
+ Dynamic: license-file
47
48
 
48
49
  scmrepo
49
50
  =======
@@ -52,7 +52,7 @@ tests = [
52
52
  "proxy.py",
53
53
  ]
54
54
  dev = [
55
- "mypy==1.14.1",
55
+ "mypy==1.15.0",
56
56
  "scmrepo[tests]",
57
57
  "types-certifi",
58
58
  "types-mock",
@@ -2,7 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  import os
5
- from collections.abc import Coroutine, Iterator, Sequence
5
+ from collections.abc import Coroutine, Iterator
6
6
  from typing import (
7
7
  TYPE_CHECKING,
8
8
  Any,
@@ -18,6 +18,7 @@ from scmrepo.asyn import BaseAsyncObject, sync_wrapper
18
18
  from scmrepo.exceptions import AuthError
19
19
 
20
20
  if TYPE_CHECKING:
21
+ from collections.abc import Sequence
21
22
  from pathlib import Path
22
23
 
23
24
  from asyncssh.auth import KbdIntPrompts, KbdIntResponse
@@ -40,6 +41,12 @@ async def _read_all(read: Callable[[int], Coroutine], n: Optional[int] = None) -
40
41
  return b"".join(result)
41
42
 
42
43
 
44
+ async def _getpass(*args, **kwargs) -> str:
45
+ from getpass import getpass
46
+
47
+ return await asyncio.to_thread(getpass, *args, **kwargs)
48
+
49
+
43
50
  class _StderrWrapper:
44
51
  def __init__(self, stderr: "SSHReader", loop: asyncio.AbstractEventLoop) -> None:
45
52
  self.stderr = stderr
@@ -97,45 +104,6 @@ class AsyncSSHWrapper(BaseAsyncObject):
97
104
  close = sync_wrapper(_close)
98
105
 
99
106
 
100
- # NOTE: Github's SSH server does not strictly comply with the SSH protocol.
101
- # When validating a public key using the rsa-sha2-256 or rsa-sha2-512
102
- # signature algorithms, RFC4252 + RFC8332 state that the server should respond
103
- # with the same algorithm in SSH_MSG_USERAUTH_PK_OK. Github's server always
104
- # returns "ssh-rsa" rather than the correct sha2 algorithm name (likely for
105
- # backwards compatibility with old SSH client reasons). This behavior causes
106
- # asyncssh to fail with a key-mismatch error (since asyncssh expects the server
107
- # to behave properly).
108
- #
109
- # See also:
110
- # https://www.ietf.org/rfc/rfc4252.txt
111
- # https://www.ietf.org/rfc/rfc8332.txt
112
- def _process_public_key_ok_gh(self, _pkttype, _pktid, packet):
113
- from asyncssh.misc import ProtocolError
114
-
115
- algorithm = packet.get_string()
116
- key_data = packet.get_string()
117
- packet.check_end()
118
-
119
- # pylint: disable=protected-access
120
- if (
121
- (
122
- algorithm == b"ssh-rsa"
123
- and self._keypair.algorithm
124
- not in (
125
- b"ssh-rsa",
126
- b"rsa-sha2-256",
127
- b"rsa-sha2-512",
128
- )
129
- )
130
- or (algorithm not in (b"ssh-rsa", self._keypair.algorithm))
131
- or key_data != self._keypair.public_data
132
- ):
133
- raise ProtocolError("Key mismatch")
134
-
135
- self.create_task(self._send_signed_request())
136
- return True
137
-
138
-
139
107
  class InteractiveSSHClient(SSHClient):
140
108
  _conn: Optional["SSHClientConnection"] = None
141
109
  _keys_to_try: Optional[list["FilePath"]] = None
@@ -171,7 +139,7 @@ class InteractiveSSHClient(SSHClient):
171
139
  self._keys_to_try = []
172
140
  options = self._conn._options # pylint: disable=protected-access
173
141
  config = options.config
174
- client_keys = cast(Sequence["FilePath"], config.get("IdentityFile", ()))
142
+ client_keys = cast("Sequence[FilePath]", config.get("IdentityFile", ()))
175
143
  if not client_keys:
176
144
  client_keys = [
177
145
  os.path.expanduser(os.path.join("~", ".ssh", path))
@@ -202,8 +170,6 @@ class InteractiveSSHClient(SSHClient):
202
170
  return None
203
171
 
204
172
  async def _read_private_key_interactive(self, path: "FilePath") -> "SSHKey":
205
- from getpass import getpass
206
-
207
173
  from asyncssh.public_key import (
208
174
  KeyEncryptionError,
209
175
  KeyImportError,
@@ -215,11 +181,8 @@ class InteractiveSSHClient(SSHClient):
215
181
  if passphrase:
216
182
  return read_private_key(path, passphrase=passphrase)
217
183
 
218
- loop = asyncio.get_running_loop()
219
184
  for _ in range(3):
220
- passphrase = await loop.run_in_executor(
221
- None, getpass, f"Enter passphrase for key '{path}': "
222
- )
185
+ passphrase = await _getpass(f"Enter passphrase for key {path!r}: ")
223
186
  if passphrase:
224
187
  try:
225
188
  key = read_private_key(path, passphrase=passphrase)
@@ -239,23 +202,20 @@ class InteractiveSSHClient(SSHClient):
239
202
  lang: str,
240
203
  prompts: "KbdIntPrompts",
241
204
  ) -> Optional["KbdIntResponse"]:
242
- from getpass import getpass
243
-
244
205
  if os.environ.get("GIT_TERMINAL_PROMPT") == "0":
245
206
  return None
246
207
 
247
- def _getpass(prompt: str) -> str:
248
- return getpass(prompt=prompt).rstrip()
249
-
250
208
  if instructions:
251
209
  pass
252
- loop = asyncio.get_running_loop()
253
- return [
254
- await loop.run_in_executor(
255
- None, _getpass, f"({name}) {prompt}" if name else prompt
256
- )
257
- for prompt, _ in prompts
258
- ]
210
+
211
+ response: list[str] = []
212
+ for prompt, _echo in prompts:
213
+ p = await _getpass(f"({name}) {prompt}" if name else prompt)
214
+ response.append(p.rstrip())
215
+ return response
216
+
217
+ async def password_auth_requested(self) -> str:
218
+ return await _getpass()
259
219
 
260
220
 
261
221
  class AsyncSSHVendor(BaseAsyncObject, SSHVendor):
@@ -286,12 +246,6 @@ class AsyncSSHVendor(BaseAsyncObject, SSHVendor):
286
246
  key_filename: Optional path to private keyfile
287
247
  """
288
248
  import asyncssh
289
- from asyncssh.auth import MSG_USERAUTH_PK_OK, _ClientPublicKeyAuth
290
-
291
- # pylint: disable=protected-access
292
- _ClientPublicKeyAuth._packet_handlers[MSG_USERAUTH_PK_OK] = (
293
- _process_public_key_ok_gh
294
- )
295
249
 
296
250
  try:
297
251
  conn = await asyncssh.connect(
@@ -289,7 +289,9 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
289
289
  bare = True
290
290
  try:
291
291
  with RemoteCallbacks(progress=progress) as cb:
292
- repo = clone_repository(url, to_path, callbacks=cb, bare=bare)
292
+ repo = clone_repository(
293
+ url, os.fspath(to_path), callbacks=cb, bare=bare
294
+ )
293
295
  if mirror:
294
296
  cls._set_mirror(repo, progress=progress)
295
297
  except GitError as exc:
@@ -66,6 +66,8 @@ class RemoteCallbacks(_RemoteCallbacks, AbstractContextManager):
66
66
  else:
67
67
  creds = Credential(username=username_from_url, url=url).fill()
68
68
  self._store_credentials = creds
69
+ assert creds.username is not None
70
+ assert creds.password is not None
69
71
  return UserPass(creds.username, creds.password)
70
72
  except CredentialNotFoundError:
71
73
  pass
@@ -160,7 +160,7 @@ class GitTrie:
160
160
  }
161
161
  )
162
162
 
163
- return cast(dict, ret)
163
+ return cast("dict", ret)
164
164
 
165
165
 
166
166
  @dataclass
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: scmrepo
3
- Version: 3.3.10
3
+ Version: 3.3.11
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -38,12 +38,13 @@ Requires-Dist: pytest-sugar; extra == "tests"
38
38
  Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
39
39
  Requires-Dist: proxy.py; extra == "tests"
40
40
  Provides-Extra: dev
41
- Requires-Dist: mypy==1.14.1; extra == "dev"
41
+ Requires-Dist: mypy==1.15.0; extra == "dev"
42
42
  Requires-Dist: scmrepo[tests]; extra == "dev"
43
43
  Requires-Dist: types-certifi; extra == "dev"
44
44
  Requires-Dist: types-mock; extra == "dev"
45
45
  Requires-Dist: types-paramiko; extra == "dev"
46
46
  Requires-Dist: types-tqdm; extra == "dev"
47
+ Dynamic: license-file
47
48
 
48
49
  scmrepo
49
50
  =======
@@ -10,7 +10,7 @@ aiohttp-retry>=2.5.0
10
10
  tqdm
11
11
 
12
12
  [dev]
13
- mypy==1.14.1
13
+ mypy==1.15.0
14
14
  scmrepo[tests]
15
15
  types-certifi
16
16
  types-mock
@@ -8,6 +8,7 @@ from unittest.mock import AsyncMock
8
8
  import asyncssh
9
9
  import paramiko
10
10
  import pytest
11
+ from paramiko.server import InteractiveQuery
11
12
  from pytest_mock import MockerFixture
12
13
  from pytest_test_utils.waiters import wait_until
13
14
 
@@ -52,13 +53,24 @@ class Server(paramiko.ServerInterface):
52
53
  """http://docs.paramiko.org/en/2.4/api/server.html."""
53
54
 
54
55
  def __init__(self, commands, *args, **kwargs) -> None:
55
- super().__init__(*args, **kwargs)
56
+ super().__init__()
56
57
  self.commands = commands
58
+ self.allowed_auths = kwargs.get("allowed_auths", "publickey,password")
57
59
 
58
60
  def check_channel_exec_request(self, channel, command):
59
61
  self.commands.append(command)
60
62
  return True
61
63
 
64
+ def check_auth_interactive(self, username: str, submethods: str):
65
+ return InteractiveQuery(
66
+ "Password", "Enter the password", f"Password for user {USER}:"
67
+ )
68
+
69
+ def check_auth_interactive_response(self, responses):
70
+ if responses[0] == PASSWORD:
71
+ return paramiko.AUTH_SUCCESSFUL
72
+ return paramiko.AUTH_FAILED
73
+
62
74
  def check_auth_password(self, username, password):
63
75
  if username == USER and password == PASSWORD:
64
76
  return paramiko.AUTH_SUCCESSFUL
@@ -76,12 +88,12 @@ class Server(paramiko.ServerInterface):
76
88
  return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
77
89
 
78
90
  def get_allowed_auths(self, username):
79
- return "password,publickey"
91
+ return self.allowed_auths
80
92
 
81
93
 
82
94
  @pytest.fixture
83
95
  def ssh_conn(request: pytest.FixtureRequest) -> dict[str, Any]:
84
- server = Server([])
96
+ server = Server([], **getattr(request, "param", {}))
85
97
 
86
98
  socket.setdefaulttimeout(10)
87
99
  request.addfinalizer(lambda: socket.setdefaulttimeout(None))
@@ -133,7 +145,8 @@ def test_run_command_password(server: Server, ssh_port: int):
133
145
  assert b"test_run_command_password" in server.commands
134
146
 
135
147
 
136
- def test_run_command_no_password(server: Server, ssh_port: int):
148
+ @pytest.mark.parametrize("ssh_conn", [{"allowed_auths": "publickey"}], indirect=True)
149
+ def test_run_command_no_password(ssh_port: int):
137
150
  vendor = AsyncSSHVendor()
138
151
  with pytest.raises(AuthError):
139
152
  vendor.run_command(
@@ -145,6 +158,28 @@ def test_run_command_no_password(server: Server, ssh_port: int):
145
158
  )
146
159
 
147
160
 
161
+ @pytest.mark.parametrize(
162
+ "ssh_conn",
163
+ [{"allowed_auths": "password"}, {"allowed_auths": "keyboard-interactive"}],
164
+ indirect=True,
165
+ ids=["password", "interactive"],
166
+ )
167
+ def test_should_prompt_for_password_when_no_password_passed(
168
+ mocker: MockerFixture, server: Server, ssh_port: int
169
+ ):
170
+ mocked_getpass = mocker.patch("getpass.getpass", return_value=PASSWORD)
171
+ vendor = AsyncSSHVendor()
172
+ vendor.run_command(
173
+ "127.0.0.1",
174
+ "test_run_command_password",
175
+ username=USER,
176
+ port=ssh_port,
177
+ password=None,
178
+ )
179
+ assert server.commands == [b"test_run_command_password"]
180
+ mocked_getpass.asssert_called_once()
181
+
182
+
148
183
  def test_run_command_with_privkey(server: Server, ssh_port: int):
149
184
  key = asyncssh.import_private_key(CLIENT_KEY)
150
185
 
@@ -212,28 +247,6 @@ def test_run_command_partial_transfer(ssh_port: int, mocker: MockerFixture):
212
247
  assert mock_stderr.call_count == 3
213
248
 
214
249
 
215
- @pytest.mark.parametrize("algorithm", [b"ssh-rsa", b"rsa-sha2-256", b"rsa-sha2-512"])
216
- def test_dulwich_github_compat(mocker: MockerFixture, algorithm: bytes):
217
- from asyncssh.misc import ProtocolError
218
-
219
- from scmrepo.git.backend.dulwich.asyncssh_vendor import _process_public_key_ok_gh
220
-
221
- key_data = b"foo"
222
- auth = mocker.Mock(
223
- _keypair=mocker.Mock(algorithm=algorithm, public_data=key_data),
224
- )
225
- packet = mocker.Mock()
226
-
227
- strings = iter((b"ed21556", key_data))
228
- packet.get_string = lambda: next(strings)
229
- with pytest.raises(ProtocolError):
230
- _process_public_key_ok_gh(auth, None, None, packet)
231
-
232
- strings = iter((b"ssh-rsa", key_data))
233
- packet.get_string = lambda: next(strings)
234
- _process_public_key_ok_gh(auth, None, None, packet)
235
-
236
-
237
250
  @pytest.mark.skipif(os.name != "nt", reason="Windows only")
238
251
  def test_git_bash_ssh_vendor(mocker):
239
252
  from dulwich.client import SubprocessSSHVendor
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