scmrepo 3.3.8__tar.gz → 3.3.10__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.
Files changed (73) hide show
  1. {scmrepo-3.3.8 → scmrepo-3.3.10}/.github/workflows/tests.yaml +2 -2
  2. {scmrepo-3.3.8 → scmrepo-3.3.10}/.pre-commit-config.yaml +3 -3
  3. {scmrepo-3.3.8/src/scmrepo.egg-info → scmrepo-3.3.10}/PKG-INFO +4 -3
  4. {scmrepo-3.3.8 → scmrepo-3.3.10}/noxfile.py +1 -1
  5. {scmrepo-3.3.8 → scmrepo-3.3.10}/pyproject.toml +2 -1
  6. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/fs.py +0 -31
  7. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/__init__.py +2 -2
  8. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/gitpython.py +1 -1
  9. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/pygit2/__init__.py +1 -1
  10. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/pygit2/filter.py +3 -3
  11. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/credentials.py +34 -3
  12. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/client.py +1 -1
  13. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/fetch.py +1 -1
  14. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/storage.py +2 -2
  15. {scmrepo-3.3.8 → scmrepo-3.3.10/src/scmrepo.egg-info}/PKG-INFO +4 -3
  16. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo.egg-info/requires.txt +1 -1
  17. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/conftest.py +1 -1
  18. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_credentials.py +40 -0
  19. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_lfs.py +1 -1
  20. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_pygit2.py +1 -1
  21. {scmrepo-3.3.8 → scmrepo-3.3.10}/.coveragerc +0 -0
  22. {scmrepo-3.3.8 → scmrepo-3.3.10}/.cruft.json +0 -0
  23. {scmrepo-3.3.8 → scmrepo-3.3.10}/.gitattributes +0 -0
  24. {scmrepo-3.3.8 → scmrepo-3.3.10}/.github/dependabot.yml +0 -0
  25. {scmrepo-3.3.8 → scmrepo-3.3.10}/.github/workflows/release.yaml +0 -0
  26. {scmrepo-3.3.8 → scmrepo-3.3.10}/.github/workflows/update-template.yaml +0 -0
  27. {scmrepo-3.3.8 → scmrepo-3.3.10}/.gitignore +0 -0
  28. {scmrepo-3.3.8 → scmrepo-3.3.10}/CODE_OF_CONDUCT.rst +0 -0
  29. {scmrepo-3.3.8 → scmrepo-3.3.10}/CONTRIBUTING.rst +0 -0
  30. {scmrepo-3.3.8 → scmrepo-3.3.10}/LICENSE +0 -0
  31. {scmrepo-3.3.8 → scmrepo-3.3.10}/README.rst +0 -0
  32. {scmrepo-3.3.8 → scmrepo-3.3.10}/setup.cfg +0 -0
  33. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/__init__.py +0 -0
  34. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/asyn.py +0 -0
  35. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/base.py +0 -0
  36. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/exceptions.py +0 -0
  37. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/__init__.py +0 -0
  38. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/base.py +0 -0
  39. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/dulwich/__init__.py +0 -0
  40. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +0 -0
  41. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/dulwich/client.py +0 -0
  42. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/backend/pygit2/callbacks.py +0 -0
  43. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/config.py +0 -0
  44. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/__init__.py +0 -0
  45. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/exceptions.py +0 -0
  46. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/object.py +0 -0
  47. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/pointer.py +0 -0
  48. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/progress.py +0 -0
  49. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/lfs/smudge.py +0 -0
  50. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/objects.py +0 -0
  51. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/git/stash.py +0 -0
  52. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/noscm.py +0 -0
  53. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/progress.py +0 -0
  54. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/py.typed +0 -0
  55. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/urls.py +0 -0
  56. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo/utils.py +0 -0
  57. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo.egg-info/SOURCES.txt +0 -0
  58. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo.egg-info/dependency_links.txt +0 -0
  59. {scmrepo-3.3.8 → scmrepo-3.3.10}/src/scmrepo.egg-info/top_level.txt +0 -0
  60. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/__init__.py +0 -0
  61. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/docker-compose.yml +0 -0
  62. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/git-init/git.sh +0 -0
  63. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_dulwich.py +0 -0
  64. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_fs.py +0 -0
  65. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_git.py +0 -0
  66. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_noscm.py +0 -0
  67. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_scmrepo.py +0 -0
  68. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_stash.py +0 -0
  69. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/test_urls.py +0 -0
  70. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/user.key +0 -0
  71. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/user.key.pub +0 -0
  72. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/vendor/__init__.py +0 -0
  73. {scmrepo-3.3.8 → scmrepo-3.3.10}/tests/vendor/test_paramiko_vendor.py +0 -0
@@ -21,7 +21,7 @@ jobs:
21
21
  fail-fast: false
22
22
  matrix:
23
23
  os: [ubuntu-20.04, windows-latest, macos-latest]
24
- pyv: ['3.9', '3.10', '3.11', '3.12']
24
+ pyv: ['3.9', '3.10', '3.11', '3.12', '3.13']
25
25
 
26
26
  steps:
27
27
  - name: Check out the repository
@@ -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@v3.1.0
50
+ uses: codecov/codecov-action@v5.3.1
51
51
 
52
52
  - name: Build package
53
53
  run: nox -s build
@@ -2,7 +2,7 @@ default_language_version:
2
2
  python: python3
3
3
  repos:
4
4
  - repo: https://github.com/pre-commit/pre-commit-hooks
5
- rev: v4.6.0
5
+ rev: v5.0.0
6
6
  hooks:
7
7
  - id: check-added-large-files
8
8
  - id: check-case-conflict
@@ -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.6.7'
23
+ rev: 'v0.9.3'
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.3.0
29
+ rev: v2.4.0
30
30
  hooks:
31
31
  - id: codespell
32
32
  additional_dependencies: ["tomli"]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: scmrepo
3
- Version: 3.3.8
3
+ Version: 3.3.10
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
14
15
  Classifier: Development Status :: 4 - Beta
15
16
  Requires-Python: >=3.9
16
17
  Description-Content-Type: text/x-rst
@@ -37,7 +38,7 @@ Requires-Dist: pytest-sugar; extra == "tests"
37
38
  Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
38
39
  Requires-Dist: proxy.py; extra == "tests"
39
40
  Provides-Extra: dev
40
- Requires-Dist: mypy==1.11.2; extra == "dev"
41
+ Requires-Dist: mypy==1.14.1; extra == "dev"
41
42
  Requires-Dist: scmrepo[tests]; extra == "dev"
42
43
  Requires-Dist: types-certifi; extra == "dev"
43
44
  Requires-Dist: types-mock; extra == "dev"
@@ -10,7 +10,7 @@ nox.options.sessions = "lint", "tests"
10
10
  locations = "src", "tests"
11
11
 
12
12
 
13
- @nox.session(python=["3.9", "3.10", "3.11", "3.12"])
13
+ @nox.session(python=["3.9", "3.10", "3.11", "3.12", "3.13"])
14
14
  def tests(session: nox.Session) -> None:
15
15
  session.install(".[tests]")
16
16
  session.run(
@@ -16,6 +16,7 @@ classifiers = [
16
16
  "Programming Language :: Python :: 3.10",
17
17
  "Programming Language :: Python :: 3.11",
18
18
  "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
19
20
  "Development Status :: 4 - Beta",
20
21
  ]
21
22
  requires-python = ">=3.9"
@@ -51,7 +52,7 @@ tests = [
51
52
  "proxy.py",
52
53
  ]
53
54
  dev = [
54
- "mypy==1.11.2",
55
+ "mypy==1.14.1",
55
56
  "scmrepo[tests]",
56
57
  "types-certifi",
57
58
  "types-mock",
@@ -3,9 +3,7 @@ import os
3
3
  import posixpath
4
4
  from typing import TYPE_CHECKING, Any, BinaryIO, Callable, Optional
5
5
 
6
- from fsspec.callbacks import _DEFAULT_CALLBACK
7
6
  from fsspec.spec import AbstractFileSystem
8
- from fsspec.utils import isfilelike
9
7
 
10
8
  if TYPE_CHECKING:
11
9
  from io import BytesIO
@@ -242,32 +240,3 @@ class GitFileSystem(AbstractFileSystem):
242
240
  return paths
243
241
 
244
242
  return [self.info(_path) for _path in paths]
245
-
246
- def get_file(
247
- self, rpath, lpath, callback=_DEFAULT_CALLBACK, outfile=None, **kwargs
248
- ):
249
- # NOTE: temporary workaround while waiting for
250
- # https://github.com/fsspec/filesystem_spec/pull/1191
251
-
252
- if isfilelike(lpath):
253
- outfile = lpath
254
- elif self.isdir(rpath):
255
- os.makedirs(lpath, exist_ok=True)
256
- return None
257
-
258
- with self.open(rpath, "rb", **kwargs) as f1:
259
- if outfile is None:
260
- outfile = open(lpath, "wb") # noqa: SIM115
261
-
262
- try:
263
- callback.set_size(getattr(f1, "size", None))
264
- data = True
265
- while data:
266
- data = f1.read(self.blocksize)
267
- segment_len = outfile.write(data)
268
- if segment_len is None:
269
- segment_len = len(data)
270
- callback.relative_update(segment_len)
271
- finally:
272
- if not isfilelike(lpath):
273
- outfile.close()
@@ -258,7 +258,7 @@ class Git(Base):
258
258
  self.hooks_dir.mkdir(exist_ok=True)
259
259
  hook = self.hooks_dir / name
260
260
 
261
- directive = f"#!{shutil.which(interpreter) or '/bin/sh' }"
261
+ directive = f"#!{shutil.which(interpreter) or '/bin/sh'}"
262
262
  hook.write_text(f"{directive}\n{script}\n", encoding="utf-8")
263
263
  hook.chmod(0o777)
264
264
 
@@ -287,7 +287,7 @@ class Git(Base):
287
287
  def no_commits(self):
288
288
  return not bool(self.get_ref("HEAD"))
289
289
 
290
- # Prefer re-using the most recently used backend when possible. When
290
+ # Prefer reusing the most recently used backend when possible. When
291
291
  # changing backends (due to unimplemented calls), we close the previous
292
292
  # backend to release any open git files/contexts that may cause conflicts
293
293
  # with the new backend.
@@ -184,7 +184,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
184
184
  # In fix_env, we delete LD_LIBRARY_PATH key if it was empty before
185
185
  # PyInstaller modified it. GitPython, in git.Repo.clone_from, uses
186
186
  # env to update its own internal state. When there is no key in
187
- # env, this value is not updated and GitPython re-uses
187
+ # env, this value is not updated and GitPython reuses
188
188
  # LD_LIBRARY_PATH that has been set by PyInstaller.
189
189
  # See [1] for more info.
190
190
  # [1] https://github.com/gitpython-developers/GitPython/issues/924
@@ -720,7 +720,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
720
720
  for refname in remote_refs:
721
721
  if fnmatch.fnmatch(refname, lh):
722
722
  src = refname
723
- dst = f"{rh_prefix}{refname[len(lh_prefix):]}"
723
+ dst = f"{rh_prefix}{refname[len(lh_prefix) :]}"
724
724
  result[dst] = cb.result.get(
725
725
  src, _default_status(src, dst, remote_refs)
726
726
  )
@@ -2,10 +2,10 @@ import io
2
2
  import logging
3
3
  from typing import TYPE_CHECKING, Callable, Optional
4
4
 
5
- from pygit2 import GIT_FILTER_CLEAN, Filter, Passthrough
5
+ from pygit2 import GIT_FILTER_CLEAN, Filter, Passthrough # type: ignore[attr-defined]
6
6
 
7
7
  if TYPE_CHECKING:
8
- from pygit2 import FilterSource
8
+ from pygit2 import FilterSource # type: ignore[attr-defined]
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
@@ -17,7 +17,7 @@ class LFSFilter(Filter):
17
17
  self._smudge_buf: Optional[io.BytesIO] = None
18
18
  self._smudge_root: Optional[str] = None
19
19
 
20
- def check(self, src: "FilterSource", attr_values: list[str]):
20
+ def check(self, src: "FilterSource", attr_values: list[Optional[str]]):
21
21
  if attr_values[0] == "lfs" and src.mode != GIT_FILTER_CLEAN:
22
22
  self._smudge_buf = io.BytesIO()
23
23
  self._smudge_root = src.repo.workdir or src.repo.path
@@ -173,11 +173,29 @@ class GitCredentialHelper(CredentialHelper):
173
173
  if res.stderr:
174
174
  logger.debug(res.stderr)
175
175
 
176
- credentials = {}
176
+ credentials: dict[str, Any] = {}
177
177
  for line in res.stdout.splitlines():
178
178
  try:
179
179
  key, value = line.split("=", maxsplit=1)
180
- credentials[key] = value
180
+ # Only include credential values that are used in the Credential
181
+ # constructor.
182
+ # Other values may be returned by the subprocess, but they must be
183
+ # ignored.
184
+ # e.g. osxkeychain credential helper >= 2.46.0 can return
185
+ # `capability[]` and `state`)
186
+ if key in [
187
+ "protocol",
188
+ "host",
189
+ "path",
190
+ "username",
191
+ "password",
192
+ "password_expiry_utc",
193
+ "url",
194
+ ]:
195
+ # Garbage bytes were output from git-credential-osxkeychain from
196
+ # 2.45.0 to 2.47.0:
197
+ # https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69
198
+ credentials[key] = _strip_garbage_bytes(value)
181
199
  except ValueError:
182
200
  continue
183
201
  if not credentials:
@@ -265,6 +283,19 @@ class GitCredentialHelper(CredentialHelper):
265
283
  )
266
284
 
267
285
 
286
+ def _strip_garbage_bytes(s: str) -> str:
287
+ """
288
+ Garbage (random) bytes were output from git-credential-osxkeychain from
289
+ 2.45.0 to 2.47.0 so must be removed.
290
+ https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69
291
+ :param s: string that might contain garbage/random bytes
292
+ :return str: The string with the garbage bytes removed
293
+ """
294
+ # Assume that any garbage bytes begin with a 0-byte
295
+ zero = s.find(chr(0))
296
+ return s[0:zero] if zero >= 0 else s
297
+
298
+
268
299
  class _CredentialKey(NamedTuple):
269
300
  protocol: str
270
301
  host: Optional[str]
@@ -293,7 +324,7 @@ def _input_tty(prompt: str = "Username: ") -> str:
293
324
  try:
294
325
  fd = os.open(
295
326
  "/dev/tty",
296
- os.O_RDWR | os.O_NOCTTY, # pylint: disable=no-member
327
+ os.O_RDWR | os.O_NOCTTY, # type: ignore[attr-defined]
297
328
  )
298
329
  tty = io.FileIO(fd, "w+")
299
330
  stack.enter_context(tty)
@@ -281,7 +281,7 @@ def _as_atomic(to_info: str, create_parents: bool = False) -> Iterator[str]:
281
281
  if create_parents:
282
282
  os.makedirs(parent, exist_ok=True)
283
283
 
284
- tmp_file = NamedTemporaryFile(dir=parent, delete=False)
284
+ tmp_file = NamedTemporaryFile(dir=parent, delete=False) # noqa: SIM115
285
285
  tmp_file.close()
286
286
  try:
287
287
  yield tmp_file.name
@@ -122,7 +122,7 @@ def _collect_objects(
122
122
  and (result := _ROOT_PATH_PREFIX_REGEX.match(path := include[0]))
123
123
  ):
124
124
  root = result.group("prefix")
125
- if path in {root, f'{root.rstrip("/")}/**'}:
125
+ if path in {root, f"{root.rstrip('/')}/**"}:
126
126
  include = []
127
127
  else:
128
128
  root = "/"
@@ -47,7 +47,7 @@ class LFSStorage:
47
47
  oid = obj if isinstance(obj, str) else obj.oid
48
48
  path = self.oid_to_path(oid)
49
49
  try:
50
- return open(path, **kwargs) # noqa: SIM115
50
+ return open(path, **kwargs)
51
51
  except FileNotFoundError:
52
52
  if not fetch_url or not isinstance(obj, Pointer):
53
53
  raise
@@ -57,7 +57,7 @@ class LFSStorage:
57
57
  raise FileNotFoundError(
58
58
  errno.ENOENT, os.strerror(errno.ENOENT), path
59
59
  ) from exc
60
- return open(path, **kwargs) # noqa: SIM115
60
+ return open(path, **kwargs)
61
61
 
62
62
  def close(self):
63
63
  pass
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: scmrepo
3
- Version: 3.3.8
3
+ Version: 3.3.10
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
14
15
  Classifier: Development Status :: 4 - Beta
15
16
  Requires-Python: >=3.9
16
17
  Description-Content-Type: text/x-rst
@@ -37,7 +38,7 @@ Requires-Dist: pytest-sugar; extra == "tests"
37
38
  Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
38
39
  Requires-Dist: proxy.py; extra == "tests"
39
40
  Provides-Extra: dev
40
- Requires-Dist: mypy==1.11.2; extra == "dev"
41
+ Requires-Dist: mypy==1.14.1; extra == "dev"
41
42
  Requires-Dist: scmrepo[tests]; extra == "dev"
42
43
  Requires-Dist: types-certifi; extra == "dev"
43
44
  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.11.2
13
+ mypy==1.14.1
14
14
  scmrepo[tests]
15
15
  types-certifi
16
16
  types-mock
@@ -58,7 +58,7 @@ email=dvctester@example.com
58
58
  defaultBranch=master
59
59
  """
60
60
  (home_dir / ".gitconfig").write_bytes(contents)
61
- pygit2.settings.search_path[pygit2.GIT_CONFIG_LEVEL_GLOBAL] = str(home_dir)
61
+ pygit2.settings.search_path[pygit2.GIT_CONFIG_LEVEL_GLOBAL] = str(home_dir) # type: ignore[attr-defined]
62
62
 
63
63
 
64
64
  @pytest.fixture
@@ -1,5 +1,6 @@
1
1
  import io
2
2
  import os
3
+ import random
3
4
 
4
5
  import pytest
5
6
 
@@ -45,6 +46,45 @@ def test_subprocess_get_use_http_path(git_helper, mocker):
45
46
  assert creds == Credential(username="foo", password="bar")
46
47
 
47
48
 
49
+ def test_subprocess_ignore_unexpected_credential_keys(git_helper, mocker):
50
+ git_helper.use_http_path = True
51
+ run = mocker.patch(
52
+ "subprocess.run",
53
+ # Simulate git-credential-osxkeychain (version >=2.45)
54
+ return_value=mocker.Mock(
55
+ stdout="username=foo\npassword=bar\ncapability[]=state\nstate[]=osxkeychain:seen=1"
56
+ ),
57
+ )
58
+ creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
59
+ assert run.call_args.args[0] == ["git-credential-foo", "get"]
60
+ assert (
61
+ run.call_args.kwargs.get("input")
62
+ == "protocol=https\nhost=foo.com\npath=foo.git\n"
63
+ )
64
+ assert creds == Credential(username="foo", password="bar")
65
+
66
+
67
+ def test_subprocess_strip_trailing_garbage_bytes(git_helper, mocker):
68
+ """Garbage bytes were output from git-credential-osxkeychain from 2.45.0 to 2.47.0
69
+ so must be removed
70
+ https://github.com/git/git/commit/6c3c451fb6e1c3ca83f74e63079d4d0af01b2d69"""
71
+ git_helper.use_http_path = True
72
+ run = mocker.patch(
73
+ "subprocess.run",
74
+ # Simulate git-credential-osxkeychain (version 2.45), assuming initial 0-byte
75
+ return_value=mocker.Mock(
76
+ stdout=f"username=foo\npassword=bar{chr(0)}{random.randbytes(15)}"
77
+ ),
78
+ )
79
+ creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
80
+ assert run.call_args.args[0] == ["git-credential-foo", "get"]
81
+ assert (
82
+ run.call_args.kwargs.get("input")
83
+ == "protocol=https\nhost=foo.com\npath=foo.git\n"
84
+ )
85
+ assert creds == Credential(username="foo", password="bar")
86
+
87
+
48
88
  def test_subprocess_get_failed(git_helper, mocker):
49
89
  from subprocess import CalledProcessError
50
90
 
@@ -32,7 +32,7 @@ def storage(tmp_dir_factory: TempDirFactory) -> LFSStorage:
32
32
 
33
33
 
34
34
  @pytest.fixture
35
- def lfs(tmp_dir: TmpDir, scm: Git) -> None: # noqa: PT004
35
+ def lfs(tmp_dir: TmpDir, scm: Git) -> None:
36
36
  tmp_dir.gen(".gitattributes", "*.lfs filter=lfs diff=lfs merge=lfs -text")
37
37
  scm.add([".gitattributes"])
38
38
  scm.commit("init lfs attributes")
@@ -39,7 +39,7 @@ def test_pygit_resolve_refish(tmp_dir: TmpDir, scm: Git, use_sha: str):
39
39
  def test_pygit_stash_apply_conflicts(
40
40
  tmp_dir: TmpDir, scm: Git, skip_conflicts: bool, mocker: MockerFixture
41
41
  ):
42
- from pygit2 import GIT_CHECKOUT_ALLOW_CONFLICTS
42
+ from pygit2 import GIT_CHECKOUT_ALLOW_CONFLICTS # type: ignore[attr-defined]
43
43
 
44
44
  tmp_dir.gen("foo", "foo")
45
45
  scm.add_commit("foo", message="foo")
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