diff --git a/dvc/scm/git/backend/gitpython.py b/dvc/scm/git/backend/gitpython.py index aed211d6e..08678ee30 100644 --- a/dvc/scm/git/backend/gitpython.py +++ b/dvc/scm/git/backend/gitpython.py @@ -3,13 +3,13 @@ import locale import logging import os from functools import partial -from typing import Callable, Iterable, Mapping, Optional, Tuple +from typing import Callable, Iterable, List, Mapping, Optional, Tuple from funcy import first, ignore from dvc.progress import Tqdm from dvc.scm.base import CloneError, MergeConflictError, RevError, SCMError -from dvc.utils import fix_env, is_binary +from dvc.utils import fix_env, is_binary, relpath from ..objects import GitObject from .base import BaseGitBackend @@ -526,7 +526,18 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method raise NotImplementedError def reset(self, hard: bool = False, paths: Iterable[str] = None): - self.repo.head.reset(index=True, working_tree=hard, paths=paths) + if paths: + paths_list: Optional[List[str]] = [ + relpath(path, self.root_dir) for path in paths + ] + if os.name == "nt": + paths_list = [ + path.replace("\\", "/") + for path in paths_list # type: ignore[union-attr] + ] + else: + paths_list = None + self.repo.head.reset(index=True, working_tree=hard, paths=paths_list) def checkout_index( self, @@ -548,7 +559,18 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method args.append(".") self.repo.git.checkout(*args) else: - self.repo.index.checkout(paths=paths, force=force) + if paths: + paths_list: Optional[List[str]] = [ + relpath(path, self.root_dir) for path in paths + ] + if os.name == "nt": + paths_list = [ + path.replace("\\", "/") + for path in paths_list # type: ignore[union-attr] + ] + else: + paths_list = None + self.repo.index.checkout(paths=paths_list, force=force) def status( self, ignored: bool = False diff --git a/dvc/scm/git/backend/pygit2.py b/dvc/scm/git/backend/pygit2.py index 7fd16830c..f6c332538 100644 --- a/dvc/scm/git/backend/pygit2.py +++ b/dvc/scm/git/backend/pygit2.py @@ -183,7 +183,10 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method raise NotImplementedError def is_ignored(self, path: str) -> bool: - return self.repo.path_is_ignored(relpath(path, self.root_dir)) + rel = relpath(path, self.root_dir) + if os.name == "nt": + rel.replace("\\", "/") + return self.repo.path_is_ignored(rel) def set_ref( self, @@ -287,7 +290,9 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method tree = self.repo.revparse_single("HEAD").tree for path in paths: rel = relpath(path, self.root_dir) - obj = tree[relpath(rel, self.root_dir)] + if os.name == "nt": + rel = rel.replace("\\", "/") + obj = tree[rel] self.repo.index.add(IndexEntry(rel, obj.oid, obj.filemode)) self.repo.index.write() elif hard: @@ -324,6 +329,11 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method path_list: Optional[List[str]] = [ relpath(path, self.root_dir) for path in paths ] + if os.name == "nt": + path_list = [ + path.replace("\\", "/") + for path in path_list # type: ignore[union-attr] + ] else: path_list = None self.repo.checkout_index( diff --git a/tests/unit/scm/test_git.py b/tests/unit/scm/test_git.py index 0dc4ac568..e7491a698 100644 --- a/tests/unit/scm/test_git.py +++ b/tests/unit/scm/test_git.py @@ -363,16 +363,21 @@ def test_checkout_index(tmp_dir, scm, git): if git.test_backend == "dulwich": pytest.skip() - tmp_dir.scm_gen({"foo": "foo", "bar": "bar"}, commit="init") - tmp_dir.gen("foo", "baz") + tmp_dir.scm_gen( + {"foo": "foo", "bar": "bar", "dir": {"baz": "baz"}}, commit="init" + ) + tmp_dir.gen({"foo": "baz", "dir": {"baz": "foo"}}) - git.checkout_index(["foo"], force=True) + with (tmp_dir / "dir").chdir(): + git.checkout_index([os.path.join("..", "foo"), "baz"], force=True) assert (tmp_dir / "foo").read_text() == "foo" + assert (tmp_dir / "dir" / "baz").read_text() == "baz" - tmp_dir.gen({"foo": "baz", "bar": "baz"}) + tmp_dir.gen({"foo": "baz", "bar": "baz", "dir": {"baz": "foo"}}) git.checkout_index(force=True) assert (tmp_dir / "foo").read_text() == "foo" assert (tmp_dir / "bar").read_text() == "bar" + assert (tmp_dir / "dir" / "baz").read_text() == "baz" @pytest.mark.parametrize( @@ -471,31 +476,37 @@ def test_reset(tmp_dir, scm, git): if git.test_backend == "dulwich": pytest.skip() - tmp_dir.scm_gen({"foo": "foo"}, commit="init") + tmp_dir.scm_gen( + {"foo": "foo", "dir": {"baz": "baz"}}, commit="init", + ) - tmp_dir.gen("foo", "bar") - scm.add(["foo"]) + tmp_dir.gen({"foo": "bar", "dir": {"baz": "bar"}}) + scm.add(["foo", os.path.join("dir", "baz")]) git.reset() assert (tmp_dir / "foo").read_text() == "bar" + assert (tmp_dir / "dir" / "baz").read_text() == "bar" staged, unstaged, _ = scm.status() assert len(staged) == 0 - assert list(unstaged) == ["foo"] + assert set(unstaged) == {"foo", "dir/baz"} - scm.add(["foo"]) + scm.add(["foo", os.path.join("dir", "baz")]) git.reset(hard=True) assert (tmp_dir / "foo").read_text() == "foo" + assert (tmp_dir / "dir" / "baz").read_text() == "baz" staged, unstaged, _ = scm.status() assert len(staged) == 0 assert len(unstaged) == 0 - tmp_dir.gen({"foo": "bar", "bar": "bar"}) - scm.add(["foo", "bar"]) - git.reset(paths=["foo"]) + tmp_dir.gen({"foo": "bar", "bar": "bar", "dir": {"baz": "bar"}}) + scm.add(["foo", "bar", os.path.join("dir", "baz")]) + with (tmp_dir / "dir").chdir(): + git.reset(paths=[os.path.join("..", "foo"), os.path.join("baz")]) assert (tmp_dir / "foo").read_text() == "bar" assert (tmp_dir / "bar").read_text() == "bar" + assert (tmp_dir / "dir" / "baz").read_text() == "bar" staged, unstaged, _ = scm.status() assert len(staged) == 1 - assert len(unstaged) == 1 + assert len(unstaged) == 2 def test_remind_to_track(scm, caplog):