scmrepo 1.4.0__tar.gz → 2.0.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.

Files changed (72) hide show
  1. {scmrepo-1.4.0 → scmrepo-2.0.0}/.cruft.json +1 -1
  2. {scmrepo-1.4.0 → scmrepo-2.0.0}/.gitignore +2 -0
  3. {scmrepo-1.4.0 → scmrepo-2.0.0}/.pre-commit-config.yaml +6 -23
  4. {scmrepo-1.4.0/src/scmrepo.egg-info → scmrepo-2.0.0}/PKG-INFO +9 -21
  5. {scmrepo-1.4.0 → scmrepo-2.0.0}/noxfile.py +0 -1
  6. scmrepo-2.0.0/pyproject.toml +126 -0
  7. scmrepo-2.0.0/setup.cfg +4 -0
  8. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/base.py +5 -1
  9. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/fs.py +98 -103
  10. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/__init__.py +13 -0
  11. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/backend/base.py +43 -0
  12. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/backend/dulwich/__init__.py +67 -1
  13. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/backend/gitpython.py +56 -1
  14. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/backend/pygit2/__init__.py +201 -43
  15. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/backend/pygit2/callbacks.py +10 -1
  16. scmrepo-2.0.0/src/scmrepo/git/backend/pygit2/filter.py +65 -0
  17. scmrepo-2.0.0/src/scmrepo/git/config.py +35 -0
  18. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/credentials.py +2 -1
  19. scmrepo-2.0.0/src/scmrepo/git/lfs/__init__.py +8 -0
  20. scmrepo-2.0.0/src/scmrepo/git/lfs/client.py +218 -0
  21. scmrepo-2.0.0/src/scmrepo/git/lfs/exceptions.py +5 -0
  22. scmrepo-2.0.0/src/scmrepo/git/lfs/fetch.py +162 -0
  23. scmrepo-2.0.0/src/scmrepo/git/lfs/object.py +15 -0
  24. scmrepo-2.0.0/src/scmrepo/git/lfs/pointer.py +109 -0
  25. scmrepo-2.0.0/src/scmrepo/git/lfs/progress.py +61 -0
  26. scmrepo-2.0.0/src/scmrepo/git/lfs/smudge.py +51 -0
  27. scmrepo-2.0.0/src/scmrepo/git/lfs/storage.py +74 -0
  28. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/objects.py +3 -2
  29. {scmrepo-1.4.0 → scmrepo-2.0.0/src/scmrepo.egg-info}/PKG-INFO +9 -21
  30. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo.egg-info/SOURCES.txt +12 -2
  31. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo.egg-info/requires.txt +4 -18
  32. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/test_credentials.py +6 -12
  33. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/test_fs.py +9 -5
  34. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/test_git.py +29 -0
  35. scmrepo-2.0.0/tests/test_lfs.py +76 -0
  36. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/test_pygit2.py +1 -1
  37. scmrepo-1.4.0/pyproject.toml +0 -108
  38. scmrepo-1.4.0/setup.cfg +0 -80
  39. scmrepo-1.4.0/src/scmrepo.egg-info/not-zip-safe +0 -1
  40. {scmrepo-1.4.0 → scmrepo-2.0.0}/.coveragerc +0 -0
  41. {scmrepo-1.4.0 → scmrepo-2.0.0}/.gitattributes +0 -0
  42. {scmrepo-1.4.0 → scmrepo-2.0.0}/.github/dependabot.yml +0 -0
  43. {scmrepo-1.4.0 → scmrepo-2.0.0}/.github/workflows/release.yaml +0 -0
  44. {scmrepo-1.4.0 → scmrepo-2.0.0}/.github/workflows/tests.yaml +0 -0
  45. {scmrepo-1.4.0 → scmrepo-2.0.0}/.github/workflows/update-template.yaml +0 -0
  46. {scmrepo-1.4.0 → scmrepo-2.0.0}/CODE_OF_CONDUCT.rst +0 -0
  47. {scmrepo-1.4.0 → scmrepo-2.0.0}/CONTRIBUTING.rst +0 -0
  48. {scmrepo-1.4.0 → scmrepo-2.0.0}/LICENSE +0 -0
  49. {scmrepo-1.4.0 → scmrepo-2.0.0}/README.rst +0 -0
  50. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/__init__.py +0 -0
  51. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/asyn.py +0 -0
  52. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/exceptions.py +0 -0
  53. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/backend/__init__.py +0 -0
  54. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +0 -0
  55. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/backend/dulwich/client.py +0 -0
  56. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/git/stash.py +0 -0
  57. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/noscm.py +0 -0
  58. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/progress.py +0 -0
  59. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/py.typed +0 -0
  60. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo/utils.py +0 -0
  61. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo.egg-info/dependency_links.txt +0 -0
  62. {scmrepo-1.4.0 → scmrepo-2.0.0}/src/scmrepo.egg-info/top_level.txt +0 -0
  63. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/__init__.py +0 -0
  64. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/conftest.py +0 -0
  65. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/docker-compose.yml +0 -0
  66. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/git-init/git.sh +0 -0
  67. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/test_dulwich.py +0 -0
  68. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/test_noscm.py +0 -0
  69. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/test_scmrepo.py +0 -0
  70. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/test_stash.py +0 -0
  71. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/user.key +0 -0
  72. {scmrepo-1.4.0 → scmrepo-2.0.0}/tests/user.key.pub +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "template": "https://github.com/iterative/py-template",
3
- "commit": "0aac03d10cbc7b2f7e144e728de5043b7a3458cb",
3
+ "commit": "15ee26df315020399731c6291d61bef81a3fc5d3",
4
4
  "context": {
5
5
  "cookiecutter": {
6
6
  "project_name": "scmrepo",
@@ -136,3 +136,5 @@ dmypy.json
136
136
 
137
137
  # Cython debug symbols
138
138
  cython_debug/
139
+
140
+ .DS_Store
@@ -1,10 +1,6 @@
1
1
  default_language_version:
2
2
  python: python3
3
3
  repos:
4
- - repo: https://github.com/psf/black
5
- rev: 23.9.1
6
- hooks:
7
- - id: black
8
4
  - repo: https://github.com/pre-commit/pre-commit-hooks
9
5
  rev: v4.4.0
10
6
  hooks:
@@ -24,6 +20,12 @@ repos:
24
20
  args: ['--fix=lf']
25
21
  - id: sort-simple-yaml
26
22
  - id: trailing-whitespace
23
+ - repo: https://github.com/astral-sh/ruff-pre-commit
24
+ rev: 'v0.1.5'
25
+ hooks:
26
+ - id: ruff
27
+ args: [--fix, --exit-non-zero-on-fix]
28
+ - id: ruff-format
27
29
  - repo: https://github.com/codespell-project/codespell
28
30
  rev: v2.2.5
29
31
  hooks:
@@ -34,22 +36,3 @@ repos:
34
36
  hooks:
35
37
  - id: pyupgrade
36
38
  args: [--py38-plus]
37
- - repo: https://github.com/PyCQA/isort
38
- rev: 5.12.0
39
- hooks:
40
- - id: isort
41
- - repo: https://github.com/pycqa/flake8
42
- rev: 6.1.0
43
- hooks:
44
- - id: flake8
45
- additional_dependencies:
46
- - flake8-bugbear==23.7.10
47
- - flake8-comprehensions==3.14.0
48
- - flake8-debugger==4.1.2
49
- - flake8-string-format==0.3.0
50
- - repo: https://github.com/pycqa/bandit
51
- rev: 1.7.5
52
- hooks:
53
- - id: bandit
54
- args: ["-c", "pyproject.toml"]
55
- additional_dependencies: [".[toml]"]
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scmrepo
3
- Version: 1.4.0
4
- Summary: SCM wrapper and fsspec filesystem for Git for use in DVC
5
- Home-page: https://github.com/iterative/scmrepo
6
- Maintainer-email: support@dvc.org
3
+ Version: 2.0.0
4
+ Summary: scmrepo
5
+ Author-email: Iterative <support@dvc.org>
7
6
  License: Apache-2.0
8
- Platform: any
7
+ Project-URL: Issues, https://github.com/iterative/scmrepo/issues
8
+ Project-URL: Source, https://github.com/iterative/scmrepo
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Programming Language :: Python :: 3.8
11
11
  Classifier: Programming Language :: Python :: 3.9
@@ -17,19 +17,20 @@ Description-Content-Type: text/x-rst
17
17
  License-File: LICENSE
18
18
  Requires-Dist: gitpython>3
19
19
  Requires-Dist: dulwich>=0.21.6
20
- Requires-Dist: pygit2>=1.13.0
20
+ Requires-Dist: pygit2>=1.13.3
21
21
  Requires-Dist: pygtrie>=2.3.2
22
22
  Requires-Dist: fsspec>=2021.7.0
23
23
  Requires-Dist: pathspec>=0.9.0
24
24
  Requires-Dist: asyncssh<3,>=2.13.1
25
25
  Requires-Dist: funcy>=1.14
26
26
  Requires-Dist: shortuuid>=0.5.0
27
+ Requires-Dist: dvc-objects<4,>=3
28
+ Requires-Dist: dvc-http>=2.29.0
27
29
  Provides-Extra: tests
28
30
  Requires-Dist: pytest==7.2.0; extra == "tests"
29
31
  Requires-Dist: pytest-sugar==0.9.5; extra == "tests"
30
32
  Requires-Dist: pytest-cov==3.0.0; extra == "tests"
31
33
  Requires-Dist: pytest-mock==3.8.2; extra == "tests"
32
- Requires-Dist: pylint==2.15.0; extra == "tests"
33
34
  Requires-Dist: mypy==0.971; extra == "tests"
34
35
  Requires-Dist: pytest-test-utils==0.0.8; extra == "tests"
35
36
  Requires-Dist: pytest-asyncio==0.18.3; extra == "tests"
@@ -40,20 +41,7 @@ Requires-Dist: types-certifi==2021.10.8.3; extra == "tests"
40
41
  Requires-Dist: types-mock==5.1.0.2; extra == "tests"
41
42
  Requires-Dist: types-paramiko==3.3.0.0; extra == "tests"
42
43
  Provides-Extra: dev
43
- Requires-Dist: pytest==7.2.0; extra == "dev"
44
- Requires-Dist: pytest-sugar==0.9.5; extra == "dev"
45
- Requires-Dist: pytest-cov==3.0.0; extra == "dev"
46
- Requires-Dist: pytest-mock==3.8.2; extra == "dev"
47
- Requires-Dist: pylint==2.15.0; extra == "dev"
48
- Requires-Dist: mypy==0.971; extra == "dev"
49
- Requires-Dist: pytest-test-utils==0.0.8; extra == "dev"
50
- Requires-Dist: pytest-asyncio==0.18.3; extra == "dev"
51
- Requires-Dist: pytest-docker==0.12.0; (python_version < "3.10" and implementation_name != "pypy") and extra == "dev"
52
- Requires-Dist: mock==5.1.0; extra == "dev"
53
- Requires-Dist: paramiko==3.3.1; extra == "dev"
54
- Requires-Dist: types-certifi==2021.10.8.3; extra == "dev"
55
- Requires-Dist: types-mock==5.1.0.2; extra == "dev"
56
- Requires-Dist: types-paramiko==3.3.0.0; extra == "dev"
44
+ Requires-Dist: scmrepo[tests]; extra == "dev"
57
45
 
58
46
  scmrepo
59
47
  =======
@@ -29,7 +29,6 @@ def lint(session: nox.Session) -> None:
29
29
  args = *(session.posargs or ("--show-diff-on-failure",)), "--all-files"
30
30
  session.run("pre-commit", "run", *args)
31
31
  session.run("python", "-m", "mypy")
32
- session.run("python", "-m", "pylint", *locations)
33
32
 
34
33
 
35
34
  @nox.session
@@ -0,0 +1,126 @@
1
+ [build-system]
2
+ requires = ["setuptools>=48", "setuptools_scm[toml]>=6.3.1"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools_scm]
6
+
7
+ [project]
8
+ name = "scmrepo"
9
+ description = "scmrepo"
10
+ readme = "README.rst"
11
+ license = {text = "Apache-2.0"}
12
+ authors = [{ name = "Iterative", email = "support@dvc.org" }]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.8",
16
+ "Programming Language :: Python :: 3.9",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Development Status :: 4 - Beta",
20
+ ]
21
+ requires-python = ">=3.8"
22
+ dynamic = ["version"]
23
+ dependencies = [
24
+ "gitpython>3",
25
+ "dulwich>=0.21.6",
26
+ "pygit2>=1.13.3",
27
+ "pygtrie>=2.3.2",
28
+ "fsspec>=2021.7.0",
29
+ "pathspec>=0.9.0",
30
+ "asyncssh>=2.13.1,<3",
31
+ "funcy>=1.14",
32
+ "shortuuid>=0.5.0",
33
+ "dvc-objects>=3,<4",
34
+ "dvc-http>=2.29.0",
35
+ ]
36
+
37
+ [project.urls]
38
+ Issues = "https://github.com/iterative/scmrepo/issues"
39
+ Source = "https://github.com/iterative/scmrepo"
40
+
41
+ [project.optional-dependencies]
42
+ tests = [
43
+ "pytest==7.2.0",
44
+ "pytest-sugar==0.9.5",
45
+ "pytest-cov==3.0.0",
46
+ "pytest-mock==3.8.2",
47
+ "mypy==0.971",
48
+ "pytest-test-utils==0.0.8",
49
+ "pytest-asyncio==0.18.3",
50
+ # https://github.com/docker/docker-py/issues/2902
51
+ "pytest-docker==0.12.0; python_version < '3.10' and implementation_name != 'pypy'",
52
+ "mock==5.1.0",
53
+ "paramiko==3.3.1",
54
+ "types-certifi==2021.10.8.3",
55
+ "types-mock==5.1.0.2",
56
+ "types-paramiko==3.3.0.0",
57
+ ]
58
+ dev = [
59
+ "scmrepo[tests]",
60
+ ]
61
+
62
+ [tool.setuptools.package-data]
63
+ dvc_objects = ["py.typed"]
64
+
65
+ [tool.setuptools.packages.find]
66
+ where = ["src"]
67
+ namespaces = false
68
+
69
+ [tool.pytest.ini_options]
70
+ addopts = "-ra"
71
+ markers = [
72
+ "skip_git_backend: skip tests for given backend",
73
+ "slow: mark test as slow to run",
74
+ ]
75
+
76
+ [tool.coverage.run]
77
+ branch = true
78
+ source = ["scmrepo", "tests"]
79
+
80
+ [tool.coverage.paths]
81
+ source = ["src", "*/site-packages"]
82
+
83
+ [tool.coverage.report]
84
+ show_missing = true
85
+ exclude_lines = [
86
+ "pragma: no cover",
87
+ "if __name__ == .__main__.:",
88
+ "if typing.TYPE_CHECKING:",
89
+ "if TYPE_CHECKING:",
90
+ "raise NotImplementedError",
91
+ "raise AssertionError",
92
+ "@overload",
93
+ ]
94
+
95
+ [tool.mypy]
96
+ # Error output
97
+ show_column_numbers = true
98
+ show_error_codes = true
99
+ show_error_context = true
100
+ show_traceback = true
101
+ pretty = true
102
+ check_untyped_defs = false
103
+ # Warnings
104
+ warn_no_return = true
105
+ warn_redundant_casts = true
106
+ warn_unreachable = true
107
+ files = ["src", "tests"]
108
+
109
+ [[tool.mypy.overrides]]
110
+ module = [
111
+ "pygtrie",
112
+ "dvc_http.*",
113
+ "funcy",
114
+ "git",
115
+ "gitdb.*",
116
+ "fsspec.*",
117
+ "pathspec.patterns",
118
+ "asyncssh.*",
119
+ "pygit2.*",
120
+ "pytest_docker.plugin",
121
+ "urllib3.*",
122
+ ]
123
+ ignore_missing_imports = true
124
+
125
+ [tool.codespell]
126
+ ignore-words-list = "cachable, keypair"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -1,7 +1,8 @@
1
1
  """Manages source control systems (e.g. Git) in DVC."""
2
+ from contextlib import AbstractContextManager
2
3
 
3
4
 
4
- class Base:
5
+ class Base(AbstractContextManager):
5
6
  """Base class for source control management driver implementations."""
6
7
 
7
8
  def __init__(self, root_dir=None):
@@ -18,6 +19,9 @@ class Base:
18
19
  class_name=type(self).__name__, directory=self.dir
19
20
  )
20
21
 
22
+ def __exit__(self, exc_type, exc_value, traceback):
23
+ self.close()
24
+
21
25
  @property
22
26
  def dir(self):
23
27
  """Path to a directory with SCM specific information."""
@@ -14,49 +14,79 @@ if TYPE_CHECKING:
14
14
  from scmrepo.git.objects import GitTrie
15
15
 
16
16
 
17
- class Path:
18
- def __init__(self, sep, getcwd=None, realpath=None):
19
- def _getcwd():
20
- return ""
17
+ def bytesio_len(obj: "BytesIO") -> Optional[int]:
18
+ try:
19
+ offset = obj.tell()
20
+ length = obj.seek(0, os.SEEK_END)
21
+ obj.seek(offset)
22
+ except (AttributeError, OSError):
23
+ return None
24
+ return length
21
25
 
22
- self.getcwd = getcwd or _getcwd
23
- self.realpath = realpath or self.abspath
24
26
 
25
- assert sep == posixpath.sep
26
- self.flavour = posixpath
27
+ class GitFileSystem(AbstractFileSystem):
28
+ # pylint: disable=abstract-method
29
+ cachable = False
30
+ root_marker = "/"
27
31
 
28
- def chdir(self, path):
29
- def _getcwd():
30
- return path
32
+ def __init__(
33
+ self,
34
+ path: str = None,
35
+ rev: str = None,
36
+ scm: "Git" = None,
37
+ trie: "GitTrie" = None,
38
+ rev_resolver: Callable[["Git", str], str] = None,
39
+ **kwargs,
40
+ ):
41
+ from scmrepo.git import Git
42
+ from scmrepo.git.objects import GitTrie
43
+
44
+ super().__init__(**kwargs)
45
+ if not trie:
46
+ scm = scm or Git(path)
47
+ resolver = rev_resolver or Git.resolve_rev
48
+ resolved = resolver(scm, rev or "HEAD")
49
+ tree_obj = scm.pygit2.get_tree_obj(rev=resolved)
50
+ trie = GitTrie(tree_obj, resolved)
51
+
52
+ self.trie = trie
53
+ self.rev = self.trie.rev
31
54
 
32
- self.getcwd = _getcwd
55
+ def getcwd(self):
56
+ return self.root_marker
33
57
 
34
- def join(self, *parts):
35
- return self.flavour.join(*parts)
58
+ def chdir(self, path):
59
+ raise NotImplementedError
60
+
61
+ @classmethod
62
+ def join(cls, *parts):
63
+ return posixpath.join(*parts)
36
64
 
37
- def split(self, path):
38
- return self.flavour.split(path)
65
+ @classmethod
66
+ def split(cls, path):
67
+ return posixpath.split(path)
39
68
 
40
69
  def normpath(self, path):
41
- return self.flavour.normpath(path)
70
+ return posixpath.normpath(path)
42
71
 
43
- def isabs(self, path):
44
- return self.flavour.isabs(path)
72
+ @classmethod
73
+ def isabs(cls, path):
74
+ return posixpath.isabs(path)
45
75
 
46
76
  def abspath(self, path):
47
77
  if not self.isabs(path):
48
78
  path = self.join(self.getcwd(), path)
49
79
  return self.normpath(path)
50
80
 
51
- def commonprefix(self, path):
52
- return self.flavour.commonprefix(path)
53
-
54
- def parts(self, path):
55
- drive, path = self.flavour.splitdrive(path.rstrip(self.flavour.sep))
81
+ @classmethod
82
+ def commonprefix(cls, path):
83
+ return posixpath.commonprefix(path)
56
84
 
85
+ @classmethod
86
+ def parts(cls, path):
57
87
  ret = []
58
88
  while True:
59
- path, part = self.flavour.split(path)
89
+ path, part = cls.split(path)
60
90
 
61
91
  if part:
62
92
  ret.append(part)
@@ -69,113 +99,77 @@ class Path:
69
99
 
70
100
  ret.reverse()
71
101
 
72
- if drive:
73
- ret = [drive] + ret
74
-
75
102
  return tuple(ret)
76
103
 
77
- def parent(self, path):
78
- return self.flavour.dirname(path)
104
+ @classmethod
105
+ def parent(cls, path):
106
+ return posixpath.dirname(path)
79
107
 
80
- def dirname(self, path):
81
- return self.parent(path)
108
+ @classmethod
109
+ def dirname(cls, path):
110
+ return cls.parent(path)
82
111
 
83
- def parents(self, path):
84
- parts = self.parts(path)
112
+ @classmethod
113
+ def parents(cls, path):
114
+ parts = cls.parts(path)
85
115
  return tuple(
86
- self.join(*parts[:length]) for length in range(len(parts) - 1, 0, -1)
116
+ cls.join(*parts[:length]) for length in range(len(parts) - 1, 0, -1)
87
117
  )
88
118
 
89
- def name(self, path):
90
- return self.parts(path)[-1]
119
+ @classmethod
120
+ def name(cls, path):
121
+ return cls.parts(path)[-1]
91
122
 
92
- def suffix(self, path):
93
- name = self.name(path)
123
+ @classmethod
124
+ def suffix(cls, path):
125
+ name = cls.name(path)
94
126
  _, dot, suffix = name.partition(".")
95
127
  return dot + suffix
96
128
 
97
- def with_name(self, path, name):
98
- parts = list(self.parts(path))
129
+ @classmethod
130
+ def with_name(cls, path, name):
131
+ parts = list(cls.parts(path))
99
132
  parts[-1] = name
100
- return self.join(*parts)
133
+ return cls.join(*parts)
101
134
 
102
- def with_suffix(self, path, suffix):
103
- parts = list(self.parts(path))
135
+ @classmethod
136
+ def with_suffix(cls, path, suffix):
137
+ parts = list(cls.parts(path))
104
138
  real_path, _, _ = parts[-1].partition(".")
105
139
  parts[-1] = real_path + suffix
106
- return self.join(*parts)
140
+ return cls.join(*parts)
107
141
 
108
- def isin(self, left, right):
109
- left_parts = self.parts(left)
110
- right_parts = self.parts(right)
142
+ @classmethod
143
+ def isin(cls, left, right):
144
+ left_parts = cls.parts(left)
145
+ right_parts = cls.parts(right)
111
146
  left_len = len(left_parts)
112
147
  right_len = len(right_parts)
113
148
  return left_len > right_len and left_parts[:right_len] == right_parts
114
149
 
115
- def isin_or_eq(self, left, right):
116
- return left == right or self.isin(left, right)
150
+ @classmethod
151
+ def isin_or_eq(cls, left, right):
152
+ return left == right or cls.isin(left, right)
117
153
 
118
- def overlaps(self, left, right):
154
+ @classmethod
155
+ def overlaps(cls, left, right):
119
156
  # pylint: disable=arguments-out-of-order
120
- return self.isin_or_eq(left, right) or self.isin(right, left)
157
+ return cls.isin_or_eq(left, right) or cls.isin(right, left)
121
158
 
122
159
  def relpath(self, path, start=None):
123
160
  if start is None:
124
161
  start = "."
125
- return self.flavour.relpath(self.abspath(path), start=self.abspath(start))
162
+ return self.relpath(self.abspath(path), start=self.abspath(start))
126
163
 
127
164
  def relparts(self, path, start=None):
128
165
  return self.parts(self.relpath(path, start=start))
129
166
 
130
- def as_posix(self, path):
131
- return path.replace(self.flavour.sep, posixpath.sep)
132
-
133
-
134
- def bytesio_len(obj: "BytesIO") -> Optional[int]:
135
- try:
136
- offset = obj.tell()
137
- length = obj.seek(0, os.SEEK_END)
138
- obj.seek(offset)
139
- except (AttributeError, OSError):
140
- return None
141
- return length
142
-
143
-
144
- class GitFileSystem(AbstractFileSystem):
145
- # pylint: disable=abstract-method
146
- cachable = False
147
- root_marker = "/"
148
-
149
- def __init__(
150
- self,
151
- path: str = None,
152
- rev: str = None,
153
- scm: "Git" = None,
154
- trie: "GitTrie" = None,
155
- rev_resolver: Callable[["Git", str], str] = None,
156
- **kwargs,
157
- ):
158
- from scmrepo.git import Git
159
- from scmrepo.git.objects import GitTrie
160
-
161
- super().__init__(**kwargs)
162
- if not trie:
163
- scm = scm or Git(path)
164
- resolver = rev_resolver or Git.resolve_rev
165
- resolved = resolver(scm, rev or "HEAD")
166
- tree_obj = scm.pygit2.get_tree_obj(rev=resolved)
167
- trie = GitTrie(tree_obj, resolved)
168
-
169
- self.trie = trie
170
- self.rev = self.trie.rev
171
-
172
- def _getcwd():
173
- return self.root_marker
174
-
175
- self.path = Path(self.sep, getcwd=_getcwd)
167
+ @classmethod
168
+ def as_posix(cls, path):
169
+ return path
176
170
 
177
171
  def _get_key(self, path: str) -> Tuple[str, ...]:
178
- path = self.path.abspath(path)
172
+ path = self.abspath(path)
179
173
  if path == self.root_marker:
180
174
  return ()
181
175
  relparts = path.split(self.sep)
@@ -187,9 +181,10 @@ class GitFileSystem(AbstractFileSystem):
187
181
  self,
188
182
  path: str,
189
183
  mode: str = "rb",
190
- block_size: int = None,
184
+ block_size: Optional[int] = None,
191
185
  autocommit: bool = True,
192
- cache_options: Dict = None,
186
+ cache_options: Optional[Dict] = None,
187
+ raw: bool = False,
193
188
  **kwargs: Any,
194
189
  ) -> BinaryIO:
195
190
  if mode != "rb":
@@ -197,7 +192,7 @@ class GitFileSystem(AbstractFileSystem):
197
192
 
198
193
  key = self._get_key(path)
199
194
  try:
200
- obj = self.trie.open(key, mode=mode)
195
+ obj = self.trie.open(key, mode=mode, raw=raw)
201
196
  obj.size = bytesio_len(obj)
202
197
  return obj
203
198
  except KeyError as exc:
@@ -171,6 +171,13 @@ class Git(Base):
171
171
  def ignore_file(self):
172
172
  return self.GITIGNORE
173
173
 
174
+ @cached_property
175
+ def lfs_storage(self):
176
+ from .lfs import LFSStorage
177
+ from .lfs.storage import get_storage_path
178
+
179
+ return LFSStorage(get_storage_path(self))
180
+
174
181
  def _get_gitignore(self, path):
175
182
  ignore_file_dir = os.path.dirname(path)
176
183
 
@@ -267,6 +274,8 @@ class Git(Base):
267
274
 
268
275
  def close(self):
269
276
  self.backends.close_initialized()
277
+ if "lfs_storage" in self.__dict__:
278
+ self.lfs_storage.close()
270
279
 
271
280
  @property
272
281
  def no_commits(self):
@@ -358,6 +367,7 @@ class Git(Base):
358
367
  is_tracked = partialmethod(_backend_func, "is_tracked")
359
368
  is_dirty = partialmethod(_backend_func, "is_dirty")
360
369
  active_branch = partialmethod(_backend_func, "active_branch")
370
+ active_branch_remote = partialmethod(_backend_func, "active_branch_remote")
361
371
  list_branches = partialmethod(_backend_func, "list_branches")
362
372
  list_tags = partialmethod(_backend_func, "list_tags")
363
373
  list_all_commits = partialmethod(_backend_func, "list_all_commits")
@@ -383,8 +393,11 @@ class Git(Base):
383
393
  status = partialmethod(_backend_func, "status")
384
394
  merge = partialmethod(_backend_func, "merge")
385
395
  validate_git_remote = partialmethod(_backend_func, "validate_git_remote")
396
+ get_remote_url = partialmethod(_backend_func, "get_remote_url")
386
397
  check_ref_format = partialmethod(_backend_func, "check_ref_format")
387
398
  get_tag = partialmethod(_backend_func, "get_tag")
399
+ get_config = partialmethod(_backend_func, "get_config")
400
+ check_attr = partialmethod(_backend_func, "check_attr")
388
401
 
389
402
  get_tree_obj = partialmethod(_backend_func, "get_tree_obj")
390
403
 
@@ -10,6 +10,7 @@ from ..objects import GitObject
10
10
  if TYPE_CHECKING:
11
11
  from scmrepo.progress import GitProgressEvent
12
12
 
13
+ from ..config import Config
13
14
  from ..objects import GitCommit, GitTag
14
15
 
15
16
 
@@ -135,6 +136,10 @@ class BaseGitBackend(ABC):
135
136
  def active_branch(self) -> str:
136
137
  pass
137
138
 
139
+ @abstractmethod
140
+ def active_branch_remote(self) -> str:
141
+ """Return the fetch remote name for the current branch."""
142
+
138
143
  @abstractmethod
139
144
  def list_branches(self) -> Iterable[str]:
140
145
  pass
@@ -263,6 +268,9 @@ class BaseGitBackend(ABC):
263
268
  returns True the local ref will be overwritten.
264
269
  Callback will be of the form:
265
270
  on_diverged(local_refname, remote_sha)
271
+
272
+ Returns:
273
+ Mapping of local_refname to sync status.
266
274
  """
267
275
 
268
276
  @abstractmethod
@@ -394,6 +402,10 @@ class BaseGitBackend(ABC):
394
402
  def validate_git_remote(self, url: str, **kwargs):
395
403
  """Verify that url is a valid git URL or remote name."""
396
404
 
405
+ @abstractmethod
406
+ def get_remote_url(self, remote: str) -> str:
407
+ """Return URL for the specified remote."""
408
+
397
409
  @abstractmethod
398
410
  def check_ref_format(self, refname: str) -> bool:
399
411
  """Check if a reference name is well formed."""
@@ -410,3 +422,34 @@ class BaseGitBackend(ABC):
410
422
  String SHA for the target object if the tag is a lightweight tag.
411
423
  GitTag object if the tag is an annotated tag.
412
424
  """
425
+
426
+ @abstractmethod
427
+ def get_config(self, path: Optional[str] = None) -> "Config":
428
+ """Return a Git config object.
429
+
430
+ Args:
431
+ path: If set, a config object for the specified config file will be
432
+ returned. By default, the standard Git system/global/repo config
433
+ stack object will be returned.
434
+ """
435
+
436
+ @abstractmethod
437
+ def check_attr(
438
+ self,
439
+ path: str,
440
+ attr: str,
441
+ source: Optional[str] = None,
442
+ ) -> Optional[Union[bool, str]]:
443
+ """Return the value of the specified attribute for a pathname.
444
+
445
+ Args:
446
+ path: Pathname to check.
447
+ attr: Attribute to check.
448
+ source: Optional tree-ish source to check.
449
+
450
+ Returns:
451
+ None when the attribute is not defined for the path (unspecified).
452
+ True when the attribute is defined as true (set).
453
+ False when the attribute is defined as false (unset).
454
+ The value of the attribute when a value has been assigned.
455
+ """