Skip to content

Commit 49024b9

Browse files
committed
git: reuse most recent backend when possible
1 parent 8016977 commit 49024b9

File tree

1 file changed

+28
-3
lines changed

1 file changed

+28
-3
lines changed

scmrepo/git/__init__.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import os
55
import re
6+
from collections import OrderedDict
67
from collections.abc import Mapping
78
from contextlib import contextmanager
89
from functools import partialmethod
@@ -30,6 +31,9 @@
3031
BackendCls = Type[BaseGitBackend]
3132

3233

34+
_LOW_PRIO_BACKENDS = ("gitpython",)
35+
36+
3337
class GitBackends(Mapping):
3438
DEFAULT: Dict[str, BackendCls] = {
3539
"dulwich": DulwichBackend,
@@ -50,7 +54,9 @@ def __init__(
5054
self, selected: Optional[Iterable[str]], *args, **kwargs
5155
) -> None:
5256
selected = selected or list(self.DEFAULT)
53-
self.backends = {key: self.DEFAULT[key] for key in selected}
57+
self.backends = OrderedDict(
58+
((key, self.DEFAULT[key]) for key in selected)
59+
)
5460

5561
self.initialized: Dict[str, BaseGitBackend] = {}
5662

@@ -71,6 +77,10 @@ def reset_all(self) -> None:
7177
for backend in self.initialized.values():
7278
backend._reset() # pylint: disable=protected-access
7379

80+
def move_to_end(self, key: str, last: bool = True):
81+
if key not in _LOW_PRIO_BACKENDS:
82+
self.backends.move_to_end(key, last=last)
83+
7484

7585
class Git(Base):
7686
"""Class for managing Git."""
@@ -87,6 +97,7 @@ def __init__(
8797
self.backends = GitBackends(backends, *args, **kwargs)
8898
first_ = first(self.backends.values())
8999
super().__init__(first_.root_dir)
100+
self._last_backend: Optional[str] = None
90101

91102
@property
92103
def dir(self):
@@ -255,11 +266,25 @@ def close(self):
255266
def no_commits(self):
256267
return not bool(self.get_ref("HEAD"))
257268

269+
# Prefer re-using the most recently used backend when possible. When
270+
# changing backends (due to unimplemented calls), we close the previous
271+
# backend to release any open git files/contexts that may cause conflicts
272+
# with the new backend.
273+
#
274+
# See:
275+
# https://github.com/iterative/dvc/issues/5641
276+
# https://github.com/iterative/dvc/issues/7458
258277
def _backend_func(self, name, *args, **kwargs):
259-
for backend in self.backends.values():
278+
for key, backend in self.backends.items():
279+
if self._last_backend is not None and key != self._last_backend:
280+
self.backends[self._last_backend].close()
281+
self._last_backend = None
260282
try:
261283
func = getattr(backend, name)
262-
return func(*args, **kwargs)
284+
result = func(*args, **kwargs)
285+
self._last_backend = key
286+
self.backends.move_to_end(key, last=False)
287+
return result
263288
except NotImplementedError:
264289
pass
265290
raise NoGitBackendError(name)

0 commit comments

Comments
 (0)