scmrepo 1.4.0__tar.gz → 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of scmrepo might be problematic. Click here for more details.
- {scmrepo-1.4.0 → scmrepo-1.5.0}/PKG-INFO +4 -2
- {scmrepo-1.4.0 → scmrepo-1.5.0}/pyproject.toml +1 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/setup.cfg +3 -1
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/base.py +5 -1
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/fs.py +4 -3
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/__init__.py +13 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/backend/base.py +43 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/backend/dulwich/__init__.py +67 -1
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/backend/gitpython.py +56 -1
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/backend/pygit2/__init__.py +201 -43
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/backend/pygit2/callbacks.py +10 -1
- scmrepo-1.5.0/src/scmrepo/git/backend/pygit2/filter.py +65 -0
- scmrepo-1.5.0/src/scmrepo/git/config.py +35 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/__init__.py +8 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/client.py +223 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/exceptions.py +5 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/fetch.py +162 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/object.py +15 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/pointer.py +109 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/progress.py +61 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/smudge.py +51 -0
- scmrepo-1.5.0/src/scmrepo/git/lfs/storage.py +74 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/objects.py +3 -2
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo.egg-info/PKG-INFO +4 -2
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo.egg-info/SOURCES.txt +12 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo.egg-info/requires.txt +3 -1
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/test_fs.py +9 -5
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/test_git.py +29 -0
- scmrepo-1.5.0/tests/test_lfs.py +76 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/test_pygit2.py +1 -1
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.coveragerc +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.cruft.json +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.gitattributes +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.github/dependabot.yml +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.github/workflows/release.yaml +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.github/workflows/tests.yaml +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.github/workflows/update-template.yaml +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.gitignore +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/.pre-commit-config.yaml +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/CODE_OF_CONDUCT.rst +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/CONTRIBUTING.rst +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/LICENSE +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/README.rst +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/noxfile.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/__init__.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/asyn.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/exceptions.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/backend/__init__.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/backend/dulwich/client.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/credentials.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/git/stash.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/noscm.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/progress.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/py.typed +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo/utils.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo.egg-info/dependency_links.txt +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo.egg-info/not-zip-safe +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/src/scmrepo.egg-info/top_level.txt +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/__init__.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/conftest.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/docker-compose.yml +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/git-init/git.sh +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/test_credentials.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/test_dulwich.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/test_noscm.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/test_scmrepo.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/test_stash.py +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/user.key +0 -0
- {scmrepo-1.4.0 → scmrepo-1.5.0}/tests/user.key.pub +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: SCM wrapper and fsspec filesystem for Git for use in DVC
|
|
5
5
|
Home-page: https://github.com/iterative/scmrepo
|
|
6
6
|
Maintainer-email: support@dvc.org
|
|
@@ -17,13 +17,15 @@ 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.
|
|
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<2,>=1.0.1
|
|
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"
|
|
@@ -26,13 +26,15 @@ packages = find:
|
|
|
26
26
|
install_requires =
|
|
27
27
|
gitpython>3
|
|
28
28
|
dulwich>=0.21.6
|
|
29
|
-
pygit2>=1.13.
|
|
29
|
+
pygit2>=1.13.3
|
|
30
30
|
pygtrie>=2.3.2
|
|
31
31
|
fsspec>=2021.7.0
|
|
32
32
|
pathspec>=0.9.0
|
|
33
33
|
asyncssh>=2.13.1,<3
|
|
34
34
|
funcy>=1.14
|
|
35
35
|
shortuuid>=0.5.0
|
|
36
|
+
dvc-objects>=1.0.1,<2
|
|
37
|
+
dvc-http>=2.29.0
|
|
36
38
|
|
|
37
39
|
[options.extras_require]
|
|
38
40
|
tests =
|
|
@@ -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."""
|
|
@@ -187,9 +187,10 @@ class GitFileSystem(AbstractFileSystem):
|
|
|
187
187
|
self,
|
|
188
188
|
path: str,
|
|
189
189
|
mode: str = "rb",
|
|
190
|
-
block_size: int = None,
|
|
190
|
+
block_size: Optional[int] = None,
|
|
191
191
|
autocommit: bool = True,
|
|
192
|
-
cache_options: Dict = None,
|
|
192
|
+
cache_options: Optional[Dict] = None,
|
|
193
|
+
raw: bool = False,
|
|
193
194
|
**kwargs: Any,
|
|
194
195
|
) -> BinaryIO:
|
|
195
196
|
if mode != "rb":
|
|
@@ -197,7 +198,7 @@ class GitFileSystem(AbstractFileSystem):
|
|
|
197
198
|
|
|
198
199
|
key = self._get_key(path)
|
|
199
200
|
try:
|
|
200
|
-
obj = self.trie.open(key, mode=mode)
|
|
201
|
+
obj = self.trie.open(key, mode=mode, raw=raw)
|
|
201
202
|
obj.size = bytesio_len(obj)
|
|
202
203
|
return obj
|
|
203
204
|
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
|
+
"""
|
|
@@ -27,11 +27,13 @@ from scmrepo.exceptions import AuthError, CloneError, InvalidRemote, RevError, S
|
|
|
27
27
|
from scmrepo.progress import GitProgressReporter
|
|
28
28
|
from scmrepo.utils import relpath
|
|
29
29
|
|
|
30
|
+
from ...config import Config
|
|
30
31
|
from ...objects import GitObject, GitTag
|
|
31
32
|
from ..base import BaseGitBackend, SyncStatus
|
|
32
33
|
|
|
33
34
|
if TYPE_CHECKING:
|
|
34
35
|
from dulwich.client import SSHVendor
|
|
36
|
+
from dulwich.config import ConfigFile, StackedConfig
|
|
35
37
|
from dulwich.repo import Repo
|
|
36
38
|
|
|
37
39
|
from scmrepo.progress import GitProgressEvent
|
|
@@ -49,7 +51,16 @@ class DulwichObject(GitObject):
|
|
|
49
51
|
self._mode = mode
|
|
50
52
|
self._sha = sha
|
|
51
53
|
|
|
52
|
-
def open(
|
|
54
|
+
def open( # pylint: disable=unused-argument
|
|
55
|
+
self,
|
|
56
|
+
mode: str = "r",
|
|
57
|
+
encoding: Optional[str] = None,
|
|
58
|
+
raw: bool = True,
|
|
59
|
+
rev: Optional[str] = None,
|
|
60
|
+
**kwargs,
|
|
61
|
+
):
|
|
62
|
+
if not raw:
|
|
63
|
+
raise NotImplementedError
|
|
53
64
|
if not encoding:
|
|
54
65
|
encoding = locale.getpreferredencoding(False)
|
|
55
66
|
# NOTE: we didn't load the object before as Dulwich will also try to
|
|
@@ -130,6 +141,35 @@ def _get_ssh_vendor() -> "SSHVendor":
|
|
|
130
141
|
return AsyncSSHVendor()
|
|
131
142
|
|
|
132
143
|
|
|
144
|
+
class DulwichConfig(Config):
|
|
145
|
+
def __init__(self, config: Union["ConfigFile", "StackedConfig"]):
|
|
146
|
+
self._config = config
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def encoding(self) -> str:
|
|
150
|
+
from dulwich.config import ConfigFile
|
|
151
|
+
|
|
152
|
+
if isinstance(self._config, ConfigFile):
|
|
153
|
+
return self._config.encoding
|
|
154
|
+
return self._config.backends[0].encoding
|
|
155
|
+
|
|
156
|
+
def get(self, section: Tuple[str, ...], name: str) -> str:
|
|
157
|
+
"""Return the specified setting as a string."""
|
|
158
|
+
return self._config.get(section, name).decode(self.encoding)
|
|
159
|
+
|
|
160
|
+
def get_bool(self, section: Tuple[str, ...], name: str) -> bool:
|
|
161
|
+
"""Return the specified setting as a boolean."""
|
|
162
|
+
value = self._config.get_boolean(section, name)
|
|
163
|
+
if value is None:
|
|
164
|
+
raise ValueError("setting is not a valid boolean")
|
|
165
|
+
return value
|
|
166
|
+
|
|
167
|
+
def get_multivar(self, section: Tuple[str, ...], name: str) -> Iterator[str]:
|
|
168
|
+
"""Iterate over string values in the specified multivar setting."""
|
|
169
|
+
for value in self._config.get_multivar(section, name):
|
|
170
|
+
yield value.decode(self.encoding)
|
|
171
|
+
|
|
172
|
+
|
|
133
173
|
class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
134
174
|
"""Dulwich Git backend."""
|
|
135
175
|
|
|
@@ -430,6 +470,9 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
430
470
|
def active_branch(self) -> str:
|
|
431
471
|
raise NotImplementedError
|
|
432
472
|
|
|
473
|
+
def active_branch_remote(self) -> str:
|
|
474
|
+
raise NotImplementedError
|
|
475
|
+
|
|
433
476
|
def list_branches(self) -> Iterable[str]:
|
|
434
477
|
base = "refs/heads/"
|
|
435
478
|
return sorted(ref[len(base) :] for ref in self.iter_refs(base))
|
|
@@ -884,6 +927,14 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
884
927
|
):
|
|
885
928
|
raise InvalidRemote(url)
|
|
886
929
|
|
|
930
|
+
def get_remote_url(self, remote: str) -> str:
|
|
931
|
+
from dulwich.porcelain import get_remote_repo
|
|
932
|
+
|
|
933
|
+
remote_name, location = get_remote_repo(self.repo, remote)
|
|
934
|
+
if not remote_name:
|
|
935
|
+
raise InvalidRemote(remote)
|
|
936
|
+
return location
|
|
937
|
+
|
|
887
938
|
def check_ref_format(self, refname: str) -> bool:
|
|
888
939
|
from dulwich.refs import check_ref_format
|
|
889
940
|
|
|
@@ -913,6 +964,21 @@ class DulwichBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
913
964
|
)
|
|
914
965
|
return os.fsdecode(ref)
|
|
915
966
|
|
|
967
|
+
def get_config(self, path: Optional[str] = None) -> "Config":
|
|
968
|
+
from dulwich.config import ConfigFile
|
|
969
|
+
|
|
970
|
+
if path:
|
|
971
|
+
return DulwichConfig(ConfigFile.from_path(path))
|
|
972
|
+
return DulwichConfig(self.repo.get_config_stack())
|
|
973
|
+
|
|
974
|
+
def check_attr(
|
|
975
|
+
self,
|
|
976
|
+
path: str,
|
|
977
|
+
attr: str,
|
|
978
|
+
source: Optional[str] = None,
|
|
979
|
+
) -> Optional[Union[bool, str]]:
|
|
980
|
+
raise NotImplementedError
|
|
981
|
+
|
|
916
982
|
|
|
917
983
|
_IDENTITY_RE = re.compile(r"(?P<name>.+)\s+<(?P<email>.+)>")
|
|
918
984
|
|
|
@@ -2,6 +2,7 @@ import io
|
|
|
2
2
|
import locale
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
import sys
|
|
6
7
|
from functools import partial, wraps
|
|
7
8
|
from typing import (
|
|
@@ -21,6 +22,7 @@ from funcy import ignore
|
|
|
21
22
|
|
|
22
23
|
from scmrepo.exceptions import (
|
|
23
24
|
CloneError,
|
|
25
|
+
InvalidRemote,
|
|
24
26
|
MergeConflictError,
|
|
25
27
|
RevError,
|
|
26
28
|
SCMError,
|
|
@@ -34,6 +36,8 @@ from .base import BaseGitBackend, SyncStatus
|
|
|
34
36
|
if TYPE_CHECKING:
|
|
35
37
|
from scmrepo.progress import GitProgressEvent
|
|
36
38
|
|
|
39
|
+
from ..config import Config
|
|
40
|
+
|
|
37
41
|
|
|
38
42
|
logger = logging.getLogger(__name__)
|
|
39
43
|
|
|
@@ -80,7 +84,15 @@ class GitPythonObject(GitObject):
|
|
|
80
84
|
def __init__(self, obj):
|
|
81
85
|
self.obj = obj
|
|
82
86
|
|
|
83
|
-
def open(
|
|
87
|
+
def open(
|
|
88
|
+
self,
|
|
89
|
+
mode: str = "r",
|
|
90
|
+
encoding: str = None,
|
|
91
|
+
raw: bool = True,
|
|
92
|
+
**kwargs,
|
|
93
|
+
):
|
|
94
|
+
if not raw:
|
|
95
|
+
raise NotImplementedError
|
|
84
96
|
if not encoding:
|
|
85
97
|
encoding = locale.getpreferredencoding(False)
|
|
86
98
|
# GitPython's obj.data_stream is a fragile thing, it is better to
|
|
@@ -341,6 +353,12 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
341
353
|
except TypeError as exc:
|
|
342
354
|
raise SCMError("No active branch") from exc
|
|
343
355
|
|
|
356
|
+
def active_branch_remote(self) -> str:
|
|
357
|
+
try:
|
|
358
|
+
return self.repo.active_branch.tracking_branch()
|
|
359
|
+
except (TypeError, ValueError) as exc:
|
|
360
|
+
raise SCMError("No active branch tracking remote") from exc
|
|
361
|
+
|
|
344
362
|
def list_branches(self):
|
|
345
363
|
return [h.name for h in self.repo.heads]
|
|
346
364
|
|
|
@@ -714,6 +732,14 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
714
732
|
def validate_git_remote(self, url: str, **kwargs):
|
|
715
733
|
raise NotImplementedError
|
|
716
734
|
|
|
735
|
+
def get_remote_url(self, remote: str) -> str:
|
|
736
|
+
from git.exc import GitCommandError
|
|
737
|
+
|
|
738
|
+
try:
|
|
739
|
+
return self.repo.remotes[remote].url
|
|
740
|
+
except (KeyError, GitCommandError) as exc:
|
|
741
|
+
raise InvalidRemote(remote) from exc
|
|
742
|
+
|
|
717
743
|
def check_ref_format(self, refname: str):
|
|
718
744
|
raise NotImplementedError
|
|
719
745
|
|
|
@@ -736,3 +762,32 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
736
762
|
except IndexError:
|
|
737
763
|
pass
|
|
738
764
|
return None
|
|
765
|
+
|
|
766
|
+
def get_config(self, path: Optional[str] = None) -> "Config":
|
|
767
|
+
raise NotImplementedError
|
|
768
|
+
|
|
769
|
+
def check_attr(
|
|
770
|
+
self,
|
|
771
|
+
path: str,
|
|
772
|
+
attr: str,
|
|
773
|
+
source: Optional[str] = None,
|
|
774
|
+
) -> Optional[Union[bool, str]]:
|
|
775
|
+
from git.exc import GitCommandError
|
|
776
|
+
|
|
777
|
+
try:
|
|
778
|
+
result = self.git.check_attr(attr, "--", path, source=source)
|
|
779
|
+
except GitCommandError as exc:
|
|
780
|
+
raise SCMError("Failed to check attribute") from exc
|
|
781
|
+
escaped_path = re.escape(path)
|
|
782
|
+
escaped_attr = re.escape(attr)
|
|
783
|
+
m = re.match(f"{escaped_path}: {escaped_attr}: (?P<info>.*)", result)
|
|
784
|
+
if not m or not m.group("info"):
|
|
785
|
+
raise SCMError("Failed to check attribute")
|
|
786
|
+
info = m.group("info")
|
|
787
|
+
if info == "unspecified":
|
|
788
|
+
return None
|
|
789
|
+
if info == "set":
|
|
790
|
+
return True
|
|
791
|
+
if info == "unset":
|
|
792
|
+
return False
|
|
793
|
+
return info
|