diff --git a/dvc/analytics.py b/dvc/analytics.py index 479c1ef91..81d1699d1 100644 --- a/dvc/analytics.py +++ b/dvc/analytics.py @@ -82,10 +82,9 @@ def send(path): def _scm_in_use(): - from scmrepo.noscm import NoSCM - from dvc.exceptions import NotDvcRepoError from dvc.repo import Repo + from dvc.scm import NoSCM from .scm import SCM, SCMError diff --git a/dvc/commands/experiments/show.py b/dvc/commands/experiments/show.py index 4bb7a44f6..b97aacbc9 100644 --- a/dvc/commands/experiments/show.py +++ b/dvc/commands/experiments/show.py @@ -77,7 +77,7 @@ def _collect_rows( # noqa: C901 fill_value=FILL_VALUE, iso=False, ): - from scmrepo.git import Git + from dvc.scm import Git if sort_by: sort_path, sort_name, sort_type = _sort_column( diff --git a/dvc/external_repo.py b/dvc/external_repo.py index bf71eba57..2c5f7821c 100644 --- a/dvc/external_repo.py +++ b/dvc/external_repo.py @@ -20,10 +20,10 @@ from dvc.scm import CloneError, map_scm_exception from dvc.utils import relpath if TYPE_CHECKING: - from scmrepo import Git - from dvc.types import StrPath + from .scm import Git + logger = logging.getLogger(__name__) @@ -37,10 +37,9 @@ def external_repo( cache_types: Optional[Collection[str]] = None, **kwargs, ) -> Iterator["Repo"]: - from scmrepo.git import Git - from dvc.config import NoRemoteError from dvc.fs import GitFileSystem + from dvc.scm import Git logger.debug("Creating external repo %s@%s", url, rev) path = _cached_clone(url, rev, for_write=for_write) @@ -197,7 +196,7 @@ def _clone_default_branch(url, rev, for_write=False): # noqa: C901 The cloned is reactualized with git pull unless rev is a known sha. """ - from scmrepo.git import Git + from dvc.scm import Git clone_path, shallow = CLONES.get(url) or (None, False) @@ -274,7 +273,7 @@ def _merge_upstream(git: "Git"): def _git_checkout(repo_path, rev): - from scmrepo.git import Git + from dvc.scm import Git logger.debug("erepo: git checkout %s@%s", repo_path, rev) git = Git(repo_path) diff --git a/dvc/fs/git.py b/dvc/fs/git.py index 383e7529b..fc8715ff9 100644 --- a/dvc/fs/git.py +++ b/dvc/fs/git.py @@ -5,9 +5,10 @@ from . import FileSystem if TYPE_CHECKING: from scmrepo.fs import GitFileSystem as FsspecGitFileSystem - from scmrepo.git import Git from scmrepo.git.objects import GitTrie + from dvc.scm import Git + class GitFileSystem(FileSystem): # pylint:disable=abstract-method """Proxies the repo file access methods to Git objects""" diff --git a/dvc/repo/__init__.py b/dvc/repo/__init__.py index 27baa6110..8290f49fe 100644 --- a/dvc/repo/__init__.py +++ b/dvc/repo/__init__.py @@ -28,7 +28,7 @@ if TYPE_CHECKING: from dvc.fs.dvc import DVCFileSystem from dvc.lock import LockBase from dvc.machine import MachineManager - from dvc.scm import Base, Git, NoSCM + from dvc.scm import Git, NoSCM from dvc.stage import Stage from dvc.types import DictStrAny from dvc_data.hashfile.state import StateBase @@ -171,7 +171,7 @@ class Repo: config: Optional["DictStrAny"] = None, url: Optional[str] = None, repo_factory: Optional[Callable] = None, - scm: Optional["Base"] = None, + scm: Optional[Union["Git", "NoSCM"]] = None, ): from dvc.config import Config from dvc.data_cloud import DataCloud diff --git a/dvc/repo/experiments/apply.py b/dvc/repo/experiments/apply.py index feea6e620..3823ab44c 100644 --- a/dvc/repo/experiments/apply.py +++ b/dvc/repo/experiments/apply.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Optional from dvc.repo import locked from dvc.repo.scm_context import scm_context +from dvc.scm import Git from dvc.utils.fs import remove from .exceptions import ( @@ -16,8 +17,6 @@ from .executor.base import BaseExecutor from .refs import EXEC_APPLY if TYPE_CHECKING: - from scmrepo.git import Git - from dvc.repo import Repo from dvc.repo.experiments import Experiments @@ -36,6 +35,7 @@ def apply(repo: "Repo", rev: str, force: bool = True, **kwargs): # noqa: C901 is_stash: bool = False + assert isinstance(repo.scm, Git) try: exp_rev = resolve_rev(repo.scm, rev) exps.check_baseline(exp_rev) @@ -90,6 +90,7 @@ def _apply_workspace(repo: "Repo", rev: str, force: bool): from scmrepo.exceptions import MergeConflictError from scmrepo.exceptions import SCMError as _SCMError + assert isinstance(repo.scm, Git) if repo.scm.is_dirty(untracked_files=True): logger.debug("Stashing workspace") stash_rev: Optional[str] = repo.scm.stash.push(include_untracked=True) diff --git a/dvc/repo/experiments/executor/base.py b/dvc/repo/experiments/executor/base.py index 94c19d78c..6c0864638 100644 --- a/dvc/repo/experiments/executor/base.py +++ b/dvc/repo/experiments/executor/base.py @@ -43,10 +43,9 @@ from dvc.utils.fs import remove if TYPE_CHECKING: from queue import Queue - from scmrepo.git import Git - from dvc.repo import Repo from dvc.repo.experiments.stash import ExpStashEntry + from dvc.scm import Git from dvc.stage import PipelineStage logger = logging.getLogger(__name__) diff --git a/dvc/repo/experiments/executor/local.py b/dvc/repo/experiments/executor/local.py index 36bf71a47..7b2386089 100644 --- a/dvc/repo/experiments/executor/local.py +++ b/dvc/repo/experiments/executor/local.py @@ -20,14 +20,13 @@ from dvc.repo.experiments.refs import ( EXPS_TEMP, ) from dvc.repo.experiments.utils import EXEC_TMP_DIR, get_exp_rwlock -from dvc.scm import SCM, GitMergeError +from dvc.scm import SCM, Git, GitMergeError from dvc.utils.fs import remove from dvc.utils.objects import cached_property from .base import BaseExecutor, TaskStatus if TYPE_CHECKING: - from scmrepo.git import Git from dvc.repo import Repo from dvc.repo.experiments.refs import ExpRefInfo @@ -131,6 +130,7 @@ class TempDirExecutor(BaseLocalExecutor): # checkout EXEC_HEAD and apply EXEC_MERGE on top of it without # committing + assert isinstance(self.scm, Git) head = EXEC_BRANCH if branch else EXEC_HEAD self.scm.checkout(head, detach=True) merge_rev = self.scm.get_ref(EXEC_MERGE) @@ -216,6 +216,8 @@ class WorkspaceExecutor(BaseLocalExecutor): if infofile: self.info.dump_json(infofile) + assert isinstance(self.scm, Git) + with get_exp_rwlock(repo, writes=[EXEC_NAMESPACE]): scm.set_ref(EXEC_HEAD, entry.head_rev) scm.set_ref(EXEC_MERGE, stash_rev) diff --git a/dvc/repo/experiments/executor/ssh.py b/dvc/repo/experiments/executor/ssh.py index 2f247dc8a..100e6b6f4 100644 --- a/dvc/repo/experiments/executor/ssh.py +++ b/dvc/repo/experiments/executor/ssh.py @@ -22,11 +22,10 @@ from .base import BaseExecutor, ExecutorInfo, ExecutorResult, TaskStatus if TYPE_CHECKING: from queue import Queue - from scmrepo.git import Git - from dvc.repo import Repo from dvc.repo.experiments.refs import ExpRefInfo from dvc.repo.experiments.stash import ExpStashEntry + from dvc.scm import Git logger = logging.getLogger(__name__) diff --git a/dvc/repo/experiments/queue/base.py b/dvc/repo/experiments/queue/base.py index 666853edc..9f84b8642 100644 --- a/dvc/repo/experiments/queue/base.py +++ b/dvc/repo/experiments/queue/base.py @@ -44,11 +44,10 @@ from dvc.utils.objects import cached_property from .utils import get_remote_executor_refs if TYPE_CHECKING: - from scmrepo.git import Git - from dvc.repo import Repo from dvc.repo.experiments import Experiments from dvc.repo.experiments.executor.base import ExecutorResult + from dvc.scm import Git logger = logging.getLogger(__name__) @@ -119,6 +118,9 @@ class BaseStashQueue(ABC): @property def scm(self) -> "Git": + from dvc.scm import Git + + assert isinstance(self.repo.scm, Git) return self.repo.scm @cached_property @@ -433,6 +435,7 @@ class BaseStashQueue(ABC): name=name, ) stash_rev = self.stash.push(message=msg) + assert stash_rev logger.debug( ( "Stashed experiment '%s' with baseline '%s' " diff --git a/dvc/repo/experiments/queue/utils.py b/dvc/repo/experiments/queue/utils.py index b19c97d46..e1c9b8b3d 100644 --- a/dvc/repo/experiments/queue/utils.py +++ b/dvc/repo/experiments/queue/utils.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - from scmrepo.git import Git + from dvc.scm import Git from .base import BaseStashQueue diff --git a/dvc/repo/experiments/remove.py b/dvc/repo/experiments/remove.py index 46068df99..8ba6925ec 100644 --- a/dvc/repo/experiments/remove.py +++ b/dvc/repo/experiments/remove.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Union from dvc.repo import locked from dvc.repo.scm_context import scm_context -from dvc.scm import iter_revs +from dvc.scm import Git, iter_revs from .exceptions import UnresolvedExpNamesError from .utils import exp_refs, exp_refs_by_baseline, push_refspec @@ -11,7 +11,6 @@ from .utils import exp_refs, exp_refs_by_baseline, push_refspec if TYPE_CHECKING: from dvc.repo import Repo from dvc.repo.experiments.queue.celery import LocalCeleryQueue - from dvc.scm import Git from .queue.base import ExpRefAndQueueEntry, QueueEntry from .refs import ExpRefInfo @@ -38,6 +37,8 @@ def remove( # noqa: C901 if queue: removed.extend(celery_queue.clear(queued=True)) + + assert isinstance(repo.scm, Git) if all_commits: removed.extend( _remove_commited_exps( diff --git a/dvc/repo/experiments/save.py b/dvc/repo/experiments/save.py index 1e3f17ff3..c89443989 100644 --- a/dvc/repo/experiments/save.py +++ b/dvc/repo/experiments/save.py @@ -2,6 +2,8 @@ import logging import os from typing import TYPE_CHECKING, List, Optional +from dvc.scm import Git + from .exceptions import ExperimentExistsError from .refs import ExpRefInfo from .utils import check_ref_format, get_random_exp_name @@ -29,6 +31,8 @@ def _save_experiment( if repo.scm.get_ref(ref) and not force: raise ExperimentExistsError(ref_info.name, command="save") + assert isinstance(repo.scm, Git) + repo.scm.add([], update=True) if include_untracked: repo.scm.add(include_untracked) @@ -51,6 +55,8 @@ def save( """ logger.debug("Saving workspace in %s", os.getcwd()) + assert isinstance(repo.scm, Git) + _, _, untracked = repo.scm.status() if include_untracked: untracked = [ diff --git a/dvc/repo/experiments/show.py b/dvc/repo/experiments/show.py index 0fb88f6aa..286e10b8d 100644 --- a/dvc/repo/experiments/show.py +++ b/dvc/repo/experiments/show.py @@ -20,13 +20,12 @@ from scmrepo.exceptions import SCMError as InnerScmError from dvc.repo.metrics.show import _gather_metrics from dvc.repo.params.show import _gather_params -from dvc.scm import SCMError, iter_revs, resolve_rev +from dvc.scm import Git, SCMError, iter_revs, resolve_rev from dvc.utils import error_handler, onerror_collect, relpath from .refs import ExpRefInfo if TYPE_CHECKING: - from scmrepo.git import Git from scmrepo.git.objects import GitCommit from dvc.repo import Repo @@ -190,6 +189,7 @@ def _collect_branch( ref_info = ExpRefInfo(baseline_sha=baseline) commits: List[Tuple[str, "GitCommit", str, List[str]]] = [] + assert isinstance(repo.scm, Git) for ref in repo.scm.iter_refs(base=str(ref_info)): try: commit = repo.scm.resolve_commit(ref) @@ -414,6 +414,8 @@ def show( if isinstance(revs, str): revs = [revs] + assert isinstance(repo.scm, Git) + found_revs: Dict[str, List[str]] = {"workspace": []} found_revs.update( iter_revs(repo.scm, revs, num, all_branches, all_tags, all_commits) diff --git a/dvc/repo/experiments/utils.py b/dvc/repo/experiments/utils.py index bc79c5ebb..ec040b8a5 100644 --- a/dvc/repo/experiments/utils.py +++ b/dvc/repo/experiments/utils.py @@ -17,11 +17,10 @@ from typing import ( Union, ) -from scmrepo.git import Git - from dvc.exceptions import InvalidArgumentError from dvc.repo.experiments.exceptions import AmbiguousExpRefInfo from dvc.rwlock import rwlock +from dvc.scm import Git from .refs import ( EXEC_APPLY, diff --git a/dvc/repo/install.py b/dvc/repo/install.py index 617309e1d..a583e2f40 100644 --- a/dvc/repo/install.py +++ b/dvc/repo/install.py @@ -3,9 +3,8 @@ from typing import TYPE_CHECKING from dvc.exceptions import DvcException if TYPE_CHECKING: - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git def pre_commit_install(scm: "Git") -> None: @@ -68,7 +67,7 @@ def install(self: "Repo", use_pre_commit_tool: bool = False) -> None: If use_pre_commit_tool is set and pre-commit is installed it will be used to install the hooks. """ - from scmrepo.git import Git + from dvc.scm import Git scm = self.scm if not isinstance(scm, Git): diff --git a/dvc/repo/scm_context.py b/dvc/repo/scm_context.py index 888fc38c5..82db4068d 100644 --- a/dvc/repo/scm_context.py +++ b/dvc/repo/scm_context.py @@ -18,9 +18,8 @@ from dvc.utils import relpath from dvc.utils.collections import ensure_list if TYPE_CHECKING: - from scmrepo.base import Base - from dvc.repo import Repo + from dvc.scm import Base logger = logging.getLogger(__name__) diff --git a/dvc/scm.py b/dvc/scm.py index af24280b2..865ff909b 100644 --- a/dvc/scm.py +++ b/dvc/scm.py @@ -2,7 +2,16 @@ import os from contextlib import contextmanager from functools import partial -from typing import TYPE_CHECKING, Iterator, List, Mapping, Optional +from typing import ( + TYPE_CHECKING, + Iterator, + List, + Literal, + Mapping, + Optional, + Union, + overload, +) from funcy import group_by from scmrepo.base import Base # noqa: F401, pylint: disable=unused-import @@ -81,8 +90,38 @@ def map_scm_exception(with_cause: bool = False) -> Iterator[None]: raise into +@overload def SCM( - root_dir, search_parent_directories=True, no_scm=False + root_dir: str, + *, + search_parent_directories: bool = ..., + no_scm: Literal[False] = ..., +) -> "Git": + ... + + +@overload +def SCM( + root_dir: str, + *, + search_parent_directories: bool = ..., + no_scm: Literal[True], +) -> "NoSCM": + ... + + +@overload +def SCM( + root_dir: str, + *, + search_parent_directories: bool = ..., + no_scm: bool = ..., +) -> Union["Git", "NoSCM"]: + ... + + +def SCM( + root_dir, *, search_parent_directories=True, no_scm=False ): # pylint: disable=invalid-name """Returns SCM instance that corresponds to a repo at the specified path. diff --git a/dvc/testing/fixtures.py b/dvc/testing/fixtures.py index db202831b..6bd4fff97 100644 --- a/dvc/testing/fixtures.py +++ b/dvc/testing/fixtures.py @@ -42,9 +42,8 @@ def make_tmp_dir(tmp_path_factory, request, worker_id): ): # pylint: disable=W0621 from shutil import copytree, ignore_patterns - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git from .tmp_dir import TmpDir diff --git a/dvc/testing/tmp_dir.py b/dvc/testing/tmp_dir.py index 490c5abe3..76fa80478 100644 --- a/dvc/testing/tmp_dir.py +++ b/dvc/testing/tmp_dir.py @@ -87,9 +87,8 @@ class TmpDir(pathlib.Path): return self def init(self, *, scm=False, dvc=False, subdir=False): - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git assert not scm or not hasattr(self, "scm") assert not dvc or not hasattr(self, "dvc") @@ -158,7 +157,7 @@ class TmpDir(pathlib.Path): def commit(self, output_paths, msg): def to_gitignore(stage_path): - from scmrepo.git import Git + from dvc.scm import Git return os.path.join(os.path.dirname(stage_path), Git.GITIGNORE) diff --git a/pyproject.toml b/pyproject.toml index d1c4cc094..8f34adb77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "rich>=10.13.0", "pyparsing>=2.4.7", "typing-extensions>=3.7.4", - "scmrepo==0.1.6", + "scmrepo==0.1.7", "dvc-render==0.0.17", "dvc-task==0.1.11", "dvclive>=1.2.2", @@ -259,7 +259,6 @@ module = [ "pytest_docker.plugin", "ruamel.*", "ruamel.yaml.*", - "scmrepo.*", "tpi.*", "viztracer", "voluptuous", diff --git a/tests/func/experiments/test_diff.py b/tests/func/experiments/test_diff.py index 581b1aa46..f0618caae 100644 --- a/tests/func/experiments/test_diff.py +++ b/tests/func/experiments/test_diff.py @@ -2,9 +2,8 @@ from funcy import first def test_diff_no_commits(tmp_dir): - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git git = Git.init(tmp_dir.fs_path) assert git.no_commits diff --git a/tests/func/experiments/test_show.py b/tests/func/experiments/test_show.py index cec63742b..787a89d5c 100644 --- a/tests/func/experiments/test_show.py +++ b/tests/func/experiments/test_show.py @@ -50,9 +50,8 @@ def make_executor_info(**kwargs): @pytest.mark.vscode def test_show_no_commits(tmp_dir): - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git git = Git.init(tmp_dir.fs_path) assert git.no_commits diff --git a/tests/func/metrics/test_diff.py b/tests/func/metrics/test_diff.py index 1749b9476..eff5e77a3 100644 --- a/tests/func/metrics/test_diff.py +++ b/tests/func/metrics/test_diff.py @@ -149,9 +149,8 @@ def test_metrics_diff_with_unchanged(tmp_dir, scm, dvc, run_copy_metrics): def test_no_commits(tmp_dir): - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git git = Git.init(tmp_dir.fs_path) assert git.no_commits diff --git a/tests/func/params/test_diff.py b/tests/func/params/test_diff.py index a910468e5..00b20311d 100644 --- a/tests/func/params/test_diff.py +++ b/tests/func/params/test_diff.py @@ -130,9 +130,8 @@ def test_pipeline_tracked_params(tmp_dir, scm, dvc, run_copy): def test_no_commits(tmp_dir): - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git git = Git.init(tmp_dir.fs_path) assert git.no_commits diff --git a/tests/func/plots/test_diff.py b/tests/func/plots/test_diff.py index 56cc332bc..ec163bf7c 100644 --- a/tests/func/plots/test_diff.py +++ b/tests/func/plots/test_diff.py @@ -52,9 +52,8 @@ def test_diff_dirty(tmp_dir, scm, dvc, run_copy_metrics): @pytest.mark.vscode def test_no_commits(tmp_dir): - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git git = Git.init(tmp_dir.fs_path) assert git.no_commits diff --git a/tests/func/test_diff.py b/tests/func/test_diff.py index 5b93815fc..fc1c10023 100644 --- a/tests/func/test_diff.py +++ b/tests/func/test_diff.py @@ -321,9 +321,8 @@ def test_no_changes(tmp_dir, scm, dvc): def test_no_commits(tmp_dir): - from scmrepo.git import Git - from dvc.repo import Repo + from dvc.scm import Git git = Git.init(tmp_dir.fs_path) assert git.no_commits diff --git a/tests/func/test_external_repo.py b/tests/func/test_external_repo.py index 9528e9929..133637e5b 100644 --- a/tests/func/test_external_repo.py +++ b/tests/func/test_external_repo.py @@ -1,9 +1,8 @@ import os from unittest.mock import ANY -from scmrepo.git import Git - from dvc.external_repo import CLONES, external_repo +from dvc.scm import Git from dvc.testing.tmp_dir import make_subrepo from dvc.utils import relpath from dvc.utils.fs import remove diff --git a/tests/func/test_import.py b/tests/func/test_import.py index ec35cf97e..24b758596 100644 --- a/tests/func/test_import.py +++ b/tests/func/test_import.py @@ -3,7 +3,6 @@ import os import pytest from funcy import first -from scmrepo.git import Git from dvc.annotations import Annotation from dvc.config import NoRemoteError @@ -11,6 +10,7 @@ from dvc.dvcfile import load_file from dvc.exceptions import DownloadError, PathMissingError from dvc.fs import system from dvc.odbmgr import ODBManager +from dvc.scm import Git from dvc.stage.exceptions import StagePathNotFoundError from dvc.testing.tmp_dir import make_subrepo from dvc.utils.fs import remove