diff --git a/dvc/repo/experiments/apply.py b/dvc/repo/experiments/apply.py index 1a0b83df7c..2e0e9cec55 100644 --- a/dvc/repo/experiments/apply.py +++ b/dvc/repo/experiments/apply.py @@ -22,7 +22,7 @@ def apply(repo, rev, force=True, **kwargs): from scmrepo.exceptions import MergeConflictError from dvc.repo.checkout import checkout as dvc_checkout - from dvc.scm import RevError, SCMError, resolve_rev + from dvc.scm import GitMergeError, RevError, resolve_rev exps = repo.experiments @@ -51,7 +51,7 @@ def apply(repo, rev, force=True, **kwargs): try: repo.scm.merge(exp_rev, commit=False, squash=True) except _SCMError as exc: - raise SCMError(str(exc)) + raise GitMergeError(str(exc), scm=repo.scm) if workspace: try: diff --git a/dvc/repo/experiments/executor/local.py b/dvc/repo/experiments/executor/local.py index fc74942af3..221e3116e5 100644 --- a/dvc/repo/experiments/executor/local.py +++ b/dvc/repo/experiments/executor/local.py @@ -5,8 +5,9 @@ from typing import TYPE_CHECKING, Optional from funcy import cached_property +from scmrepo.exceptions import SCMError as _SCMError -from dvc.scm import SCM +from dvc.scm import SCM, GitMergeError from dvc.utils.fs import remove from ..base import ( @@ -88,7 +89,10 @@ def init_git(self, scm: "Git", branch: Optional[str] = None): head = EXEC_BRANCH if branch else EXEC_HEAD self.scm.checkout(head, detach=True) merge_rev = self.scm.get_ref(EXEC_MERGE) - self.scm.merge(merge_rev, squash=True, commit=False) + try: + self.scm.merge(merge_rev, squash=True, commit=False) + except _SCMError as exc: + raise GitMergeError(str(exc), scm=self.scm) def _config(self, cache_dir): local_config = os.path.join( @@ -155,7 +159,10 @@ def init_git(self, scm: "Git", branch: Optional[str] = None): ) ) merge_rev = self.scm.get_ref(EXEC_MERGE) - self.scm.merge(merge_rev, squash=True, commit=False) + try: + self.scm.merge(merge_rev, squash=True, commit=False) + except _SCMError as exc: + raise GitMergeError(str(exc), scm=self.scm) if branch: self.scm.set_ref(EXEC_BRANCH, branch, symbolic=True) elif scm.get_ref(EXEC_BRANCH): diff --git a/dvc/scm.py b/dvc/scm.py index ceb39ff8e6..05f29f1502 100644 --- a/dvc/scm.py +++ b/dvc/scm.py @@ -1,4 +1,5 @@ """Manages source control systems (e.g. Git).""" +import os from contextlib import contextmanager from functools import partial from typing import TYPE_CHECKING, Iterator, List, Mapping, Optional @@ -10,6 +11,7 @@ from dvc.exceptions import DvcException from dvc.progress import Tqdm +from dvc.utils import format_link if TYPE_CHECKING: from scmrepo.progress import GitProgressEvent @@ -47,6 +49,25 @@ def __init__(self, reason: str) -> None: super().__init__(f"{reason}\n{doc}") +class GitMergeError(SCMError): + def __init__(self, msg: str, scm: Optional["Git"] = None) -> None: + if scm and self._is_shallow(scm): + url = format_link( + "https://dvc.org/doc/user-guide/troubleshooting#git-shallow" + ) + msg = ( + f"{msg}: `dvc exp` does not work in shallow Git repos. " + f"See {url} for more information." + ) + super().__init__(msg) + + @staticmethod + def _is_shallow(scm: "Git") -> bool: + if os.path.exists(os.path.join(scm.root_dir, Git.GIT_DIR, "shallow")): + return True + return False + + @contextmanager def map_scm_exception(with_cause: bool = False) -> Iterator[None]: from scmrepo.exceptions import SCMError as InternalSCMError @@ -115,8 +136,6 @@ def update_git(self, event: "GitProgressEvent") -> None: def clone(url: str, to_path: str, **kwargs): - import os - from scmrepo.exceptions import CloneError as InternalCloneError from dvc.repo.experiments.utils import fetch_all_exps diff --git a/tests/unit/scm/test_scm.py b/tests/unit/scm/test_scm.py index 338cc74ce4..e078021fcd 100644 --- a/tests/unit/scm/test_scm.py +++ b/tests/unit/scm/test_scm.py @@ -1,5 +1,5 @@ from dvc.repo.experiments import ExpRefInfo -from dvc.scm import iter_revs +from dvc.scm import GitMergeError, iter_revs def test_iter_revs( @@ -61,3 +61,11 @@ def test_iter_revs( rev_old: [rev_old], rev_root: [rev_root], } + + +def test_merge_error(tmp_dir, scm): + exc = GitMergeError("Merge failed") + assert "shallow" not in str(exc) + tmp_dir.gen({".git": {"shallow": ""}}) + exc = GitMergeError("Merge failed", scm=scm) + assert "shallow" in str(exc)