Files
dvc/tests/unit/scm/test_git.py
T
Peter Rowlands (변기호) 7a92ff5e5d experiments: commit with --no-verify in executors (#5078)
* git: add no_verify flag for commit()

* experiments: commit with --no-verify in executors

* git: wrap hook exceptions with SCMError

* update hook file perms

* update dulwich to 0.20.15

* update pygit2 commit def

* skip hook tests on windows
2020-12-28 17:35:35 +09:00

324 lines
9.6 KiB
Python

import os
import pytest
from dvc.scm.base import SCMError
from tests.basic_env import TestDvcGit
# Behaves the same as SCM but will test against all suported Git backends.
# tmp_dir.scm will still contain a default SCM instance.
@pytest.fixture(params=["gitpython", "dulwich"])
def git(tmp_dir, scm, request):
from dvc.scm.git import Git
git_ = Git(os.fspath(tmp_dir), backends=[request.param])
yield git_
git_.close()
class TestGit(TestDvcGit):
def test_belongs_to_scm_true_on_gitignore(self):
path = os.path.join("path", "to", ".gitignore")
self.assertTrue(self.dvc.scm.belongs_to_scm(path))
def test_belongs_to_scm_true_on_git_internal(self):
path = os.path.join("path", "to", ".git", "internal", "file")
self.assertTrue(self.dvc.scm.belongs_to_scm(path))
def test_belongs_to_scm_false(self):
path = os.path.join("some", "non-.git", "file")
self.assertFalse(self.dvc.scm.belongs_to_scm(path))
def test_walk_with_submodules(tmp_dir, scm, git_dir):
git_dir.scm_gen(
{"foo": "foo", "bar": "bar", "dir": {"data": "data"}},
commit="add dir and files",
)
scm.gitpython.repo.create_submodule(
"submodule", "submodule", url=os.fspath(git_dir)
)
scm.commit("added submodule")
files = []
dirs = []
tree = scm.get_tree("HEAD")
for _, dnames, fnames in tree.walk("."):
dirs.extend(dnames)
files.extend(fnames)
# currently we don't walk through submodules
assert not dirs
assert set(files) == {".gitmodules", "submodule"}
def test_walk_onerror(tmp_dir, scm):
def onerror(exc):
raise exc
tmp_dir.scm_gen(
{"foo": "foo"}, commit="init",
)
tree = scm.get_tree("HEAD")
# path does not exist
for _ in tree.walk("dir"):
pass
with pytest.raises(OSError):
for _ in tree.walk("dir", onerror=onerror):
pass
# path is not a directory
for _ in tree.walk("foo"):
pass
with pytest.raises(OSError):
for _ in tree.walk("foo", onerror=onerror):
pass
def test_is_tracked(tmp_dir, scm):
tmp_dir.scm_gen(
{
"tracked": "tracked",
"dir": {"data": "data", "subdir": {"subdata": "subdata"}},
},
commit="add dirs and files",
)
tmp_dir.gen({"untracked": "untracked", "dir": {"untracked": "untracked"}})
# sanity check
assert (tmp_dir / "untracked").exists()
assert (tmp_dir / "tracked").exists()
assert (tmp_dir / "dir" / "untracked").exists()
assert (tmp_dir / "dir" / "data").exists()
assert (tmp_dir / "dir" / "subdir" / "subdata").exists()
assert not scm.is_tracked("untracked")
assert not scm.is_tracked(os.path.join("dir", "untracked"))
assert scm.is_tracked("tracked")
assert scm.is_tracked("dir")
assert scm.is_tracked(os.path.join("dir", "data"))
assert scm.is_tracked(os.path.join("dir", "subdir"))
assert scm.is_tracked(os.path.join("dir", "subdir", "subdata"))
def test_is_tracked_unicode(tmp_dir, scm):
tmp_dir.scm_gen("ṭṝḁḉḵḗḋ", "tracked", commit="add unicode")
tmp_dir.gen("ṳṋṭṝḁḉḵḗḋ", "untracked")
assert scm.is_tracked("ṭṝḁḉḵḗḋ")
assert not scm.is_tracked("ṳṋṭṝḁḉḵḗḋ")
def test_no_commits(tmp_dir):
from dvc.scm.git import Git
from tests.dir_helpers import git_init
git_init(".")
assert Git().no_commits
tmp_dir.gen("foo", "foo")
Git().add(["foo"])
Git().commit("foo")
assert not Git().no_commits
def test_branch_revs(tmp_dir, scm):
tmp_dir.scm_gen({"file": "0"}, commit="init")
init_rev = scm.get_rev()
expected = []
for i in range(1, 5):
tmp_dir.scm_gen({"file": f"{i}"}, commit=f"{i}")
expected.append(scm.get_rev())
for rev in scm.branch_revs("master", init_rev):
assert rev == expected.pop()
assert len(expected) == 0
def test_set_ref(tmp_dir, git):
tmp_dir.scm_gen({"file": "0"}, commit="init")
init_rev = tmp_dir.scm.get_rev()
tmp_dir.scm_gen({"file": "1"}, commit="commit")
commit_rev = tmp_dir.scm.get_rev()
git.set_ref("refs/foo/bar", init_rev)
assert (
init_rev
== (tmp_dir / ".git" / "refs" / "foo" / "bar").read_text().strip()
)
with pytest.raises(SCMError):
git.set_ref("refs/foo/bar", commit_rev, old_ref=commit_rev)
git.set_ref("refs/foo/bar", commit_rev, old_ref=init_rev)
assert (
commit_rev
== (tmp_dir / ".git" / "refs" / "foo" / "bar").read_text().strip()
)
git.set_ref("refs/foo/baz", "refs/heads/master", symbolic=True)
assert (
"ref: refs/heads/master"
== (tmp_dir / ".git" / "refs" / "foo" / "baz").read_text().strip()
)
def test_get_ref(tmp_dir, git):
tmp_dir.scm_gen({"file": "0"}, commit="init")
init_rev = tmp_dir.scm.get_rev()
tmp_dir.gen(
{
os.path.join(".git", "refs", "foo", "bar"): init_rev,
os.path.join(
".git", "refs", "foo", "baz"
): "ref: refs/heads/master",
}
)
assert init_rev == git.get_ref("refs/foo/bar")
assert init_rev == git.get_ref("refs/foo/baz")
assert "refs/heads/master" == git.get_ref("refs/foo/baz", follow=False)
assert git.get_ref("refs/foo/qux") is None
def test_remove_ref(tmp_dir, git):
tmp_dir.scm_gen({"file": "0"}, commit="init")
init_rev = tmp_dir.scm.get_rev()
tmp_dir.gen(os.path.join(".git", "refs", "foo", "bar"), init_rev)
tmp_dir.scm_gen({"file": "1"}, commit="commit")
commit_rev = tmp_dir.scm.get_rev()
with pytest.raises(SCMError):
git.remove_ref("refs/foo/bar", old_ref=commit_rev)
git.remove_ref("refs/foo/bar", old_ref=init_rev)
assert not (tmp_dir / ".git" / "refs" / "foo" / "bar").exists()
def test_refs_containing(tmp_dir, scm):
tmp_dir.scm_gen({"file": "0"}, commit="init")
init_rev = scm.get_rev()
tmp_dir.gen(
{
os.path.join(".git", "refs", "foo", "bar"): init_rev,
os.path.join(".git", "refs", "foo", "baz"): init_rev,
}
)
expected = {"refs/foo/bar", "refs/foo/baz", "refs/heads/master"}
assert expected == set(scm.get_refs_containing(init_rev))
@pytest.mark.parametrize("use_url", [True, False])
def test_push_refspec(tmp_dir, scm, make_tmp_dir, use_url):
tmp_dir.scm_gen({"file": "0"}, commit="init")
init_rev = scm.get_rev()
tmp_dir.gen(
{
os.path.join(".git", "refs", "foo", "bar"): init_rev,
os.path.join(".git", "refs", "foo", "baz"): init_rev,
}
)
remote_dir = make_tmp_dir("git-remote", scm=True)
url = "file://{}".format(remote_dir.resolve().as_posix())
scm.gitpython.repo.create_remote("origin", url)
with pytest.raises(SCMError):
scm.push_refspec("bad-remote", "refs/foo/bar", "refs/foo/bar")
remote = url if use_url else "origin"
scm.push_refspec(remote, "refs/foo/bar", "refs/foo/bar")
assert init_rev == remote_dir.scm.get_ref("refs/foo/bar")
remote_dir.scm.checkout("refs/foo/bar")
assert init_rev == remote_dir.scm.get_rev()
assert "0" == (remote_dir / "file").read_text()
scm.push_refspec(remote, "refs/foo/", "refs/foo/")
assert init_rev == remote_dir.scm.get_ref("refs/foo/baz")
scm.push_refspec(remote, None, "refs/foo/baz")
assert remote_dir.scm.get_ref("refs/foo/baz") is None
def test_fetch_refspecs(tmp_dir, scm, make_tmp_dir):
remote_dir = make_tmp_dir("git-remote", scm=True)
url = "file://{}".format(remote_dir.resolve().as_posix())
remote_dir.scm_gen({"file": "0"}, commit="init")
init_rev = remote_dir.scm.get_rev()
remote_dir.gen(
{
os.path.join(".git", "refs", "foo", "bar"): init_rev,
os.path.join(".git", "refs", "foo", "baz"): init_rev,
}
)
scm.fetch_refspecs(
url, ["refs/foo/bar:refs/foo/bar", "refs/foo/baz:refs/foo/baz"]
)
assert init_rev == scm.get_ref("refs/foo/bar")
assert init_rev == scm.get_ref("refs/foo/baz")
remote_dir.scm.checkout("refs/foo/bar")
assert init_rev == remote_dir.scm.get_rev()
assert "0" == (remote_dir / "file").read_text()
def test_list_all_commits(tmp_dir, scm):
tmp_dir.scm_gen("1", "1", commit="1")
rev_a = scm.get_rev()
tmp_dir.scm_gen("2", "2", commit="2")
rev_b = scm.get_rev()
scm.tag("tag")
tmp_dir.scm_gen("3", "3", commit="3")
rev_c = scm.get_rev()
scm.gitpython.git.reset(rev_a, hard=True)
scm.set_ref("refs/foo/bar", rev_c)
assert {rev_a, rev_b} == set(scm.list_all_commits())
def test_ignore_remove_empty(tmp_dir, scm, git):
test_entries = [
{"entry": "/foo1", "path": f"{tmp_dir}/foo1"},
{"entry": "/foo2", "path": f"{tmp_dir}/foo2"},
]
path_to_gitignore = tmp_dir / ".gitignore"
with open(path_to_gitignore, "a") as f:
for entry in test_entries:
f.write(entry["entry"] + "\n")
assert path_to_gitignore.exists()
git.ignore_remove(test_entries[0]["path"])
assert path_to_gitignore.exists()
git.ignore_remove(test_entries[1]["path"])
assert not path_to_gitignore.exists()
@pytest.mark.skipif(
os.name == "nt", reason="Git hooks not supported on Windows"
)
@pytest.mark.parametrize("hook", ["pre-commit", "commit-msg"])
def test_commit_no_verify(tmp_dir, scm, git, hook):
import stat
hook_file = os.path.join(".git", "hooks", hook)
tmp_dir.gen(
hook_file, "#!/usr/bin/env python\nimport sys\nsys.exit(1)",
)
os.chmod(hook_file, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
tmp_dir.gen("foo", "foo")
git.add(["foo"])
with pytest.raises(SCMError):
git.commit("commit foo")
git.commit("commit foo", no_verify=True)