mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
ignore: introduce .dvcignore, apply when searching for stage files
This commit is contained in:
+134
@@ -0,0 +1,134 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from dulwich.ignore import match_pattern, read_ignore_patterns
|
||||||
|
from dvc.utils.compat import cast_bytes
|
||||||
|
from dvc.utils.fs import get_parent_dirs_up_to
|
||||||
|
|
||||||
|
|
||||||
|
class DvcIgnoreFileHandler(object):
|
||||||
|
def __init__(self, tree):
|
||||||
|
self.tree = tree
|
||||||
|
|
||||||
|
def read_patterns(self, path):
|
||||||
|
with self.tree.open(path, binary=True) as stream:
|
||||||
|
return self._read_patterns(stream)
|
||||||
|
|
||||||
|
def get_repo_root(self):
|
||||||
|
return self.tree.tree_root
|
||||||
|
|
||||||
|
def _read_patterns(self, binary_stream):
|
||||||
|
negate_patterns = []
|
||||||
|
patterns = []
|
||||||
|
for pattern in read_ignore_patterns(binary_stream):
|
||||||
|
if pattern.lstrip().startswith(b"!"):
|
||||||
|
negate_patterns.append(pattern)
|
||||||
|
else:
|
||||||
|
patterns.append(pattern)
|
||||||
|
|
||||||
|
return negate_patterns, patterns
|
||||||
|
|
||||||
|
|
||||||
|
class DvcIgnore(object):
|
||||||
|
DVCIGNORE_FILE = ".dvcignore"
|
||||||
|
|
||||||
|
def __call__(self, root, dirs, files):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class DvcIgnoreFromFile(DvcIgnore):
|
||||||
|
def __init__(self, ignore_file_path, ignore_handler):
|
||||||
|
self.ignore_file_path = ignore_file_path
|
||||||
|
self.dirname = os.path.normpath(os.path.dirname(ignore_file_path))
|
||||||
|
self.patterns = []
|
||||||
|
self.negate_patterns = []
|
||||||
|
|
||||||
|
self.negate_patterns, self.patterns = ignore_handler.read_patterns(
|
||||||
|
ignore_file_path
|
||||||
|
)
|
||||||
|
|
||||||
|
def __call__(self, root, dirs, files):
|
||||||
|
files = [f for f in files if not self.matches(root, f)]
|
||||||
|
dirs = [d for d in dirs if not self.matches(root, d)]
|
||||||
|
|
||||||
|
return dirs, files
|
||||||
|
|
||||||
|
def get_match(self, abs_path):
|
||||||
|
rel_path = os.path.relpath(abs_path, self.dirname)
|
||||||
|
if os.name == "nt":
|
||||||
|
rel_path = rel_path.replace("\\", "/")
|
||||||
|
rel_path = cast_bytes(rel_path, "utf-8")
|
||||||
|
|
||||||
|
for pattern in self.patterns:
|
||||||
|
if match_pattern(
|
||||||
|
rel_path, pattern
|
||||||
|
) and self._no_negate_pattern_matches(rel_path):
|
||||||
|
return (abs_path, pattern, self.ignore_file_path)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def matches(self, dirname, basename):
|
||||||
|
if self.get_match(os.path.join(dirname, basename)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _no_negate_pattern_matches(self, path):
|
||||||
|
return all([not match_pattern(path, p) for p in self.negate_patterns])
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.ignore_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
class DvcIgnoreConstant(DvcIgnore):
|
||||||
|
def __init__(self, basename):
|
||||||
|
self.basename = basename
|
||||||
|
|
||||||
|
|
||||||
|
class DvcIgnoreDir(DvcIgnoreConstant):
|
||||||
|
def __call__(self, root, dirs, files):
|
||||||
|
dirs = [d for d in dirs if not d == self.basename]
|
||||||
|
|
||||||
|
return dirs, files
|
||||||
|
|
||||||
|
|
||||||
|
class DvcIgnoreFile(DvcIgnoreConstant):
|
||||||
|
def __call__(self, root, dirs, files):
|
||||||
|
files = [f for f in files if not f == self.basename]
|
||||||
|
|
||||||
|
return dirs, files
|
||||||
|
|
||||||
|
|
||||||
|
class DvcIgnoreFilter(object):
|
||||||
|
def __init__(self, wdir, ignore_file_handler=None):
|
||||||
|
self.ignores = [
|
||||||
|
DvcIgnoreDir(".git"),
|
||||||
|
DvcIgnoreDir(".hg"),
|
||||||
|
DvcIgnoreDir(".dvc"),
|
||||||
|
DvcIgnoreFile(".dvcignore"),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.ignore_file_handler = ignore_file_handler
|
||||||
|
self._process_ignores_in_parent_dirs(wdir)
|
||||||
|
|
||||||
|
def _process_ignores_in_parent_dirs(self, wdir):
|
||||||
|
if self.ignore_file_handler:
|
||||||
|
wdir = os.path.normpath(os.path.abspath(wdir))
|
||||||
|
ignore_search_end_dir = self.ignore_file_handler.get_repo_root()
|
||||||
|
parent_dirs = get_parent_dirs_up_to(wdir, ignore_search_end_dir)
|
||||||
|
for d in parent_dirs:
|
||||||
|
self.update(d)
|
||||||
|
|
||||||
|
def update(self, wdir):
|
||||||
|
ignore_file_path = os.path.join(wdir, DvcIgnore.DVCIGNORE_FILE)
|
||||||
|
if os.path.exists(ignore_file_path):
|
||||||
|
file_ignore = DvcIgnoreFromFile(
|
||||||
|
ignore_file_path, ignore_handler=self.ignore_file_handler
|
||||||
|
)
|
||||||
|
self.ignores.append(file_ignore)
|
||||||
|
|
||||||
|
def __call__(self, root, dirs, files):
|
||||||
|
if self.ignore_file_handler:
|
||||||
|
self.update(root)
|
||||||
|
|
||||||
|
for ignore in self.ignores:
|
||||||
|
dirs, files = ignore(root, dirs, files)
|
||||||
|
|
||||||
|
return dirs, files
|
||||||
@@ -11,7 +11,7 @@ from dvc.exceptions import (
|
|||||||
OutputNotFoundError,
|
OutputNotFoundError,
|
||||||
TargetNotDirectoryError,
|
TargetNotDirectoryError,
|
||||||
)
|
)
|
||||||
|
from dvc.ignore import DvcIgnoreFileHandler
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class Repo(object):
|
|||||||
|
|
||||||
self.config = Config(self.dvc_dir)
|
self.config = Config(self.dvc_dir)
|
||||||
|
|
||||||
self.tree = WorkingTree()
|
self.tree = WorkingTree(self.root_dir)
|
||||||
|
|
||||||
self.scm = SCM(self.root_dir, repo=self)
|
self.scm = SCM(self.root_dir, repo=self)
|
||||||
self.lock = Lock(self.dvc_dir)
|
self.lock = Lock(self.dvc_dir)
|
||||||
@@ -390,7 +390,11 @@ class Repo(object):
|
|||||||
|
|
||||||
stages = []
|
stages = []
|
||||||
outs = []
|
outs = []
|
||||||
for root, dirs, files in self.tree.walk(from_directory):
|
|
||||||
|
ignore_file_handler = DvcIgnoreFileHandler(self.tree)
|
||||||
|
for root, dirs, files in self.tree.walk(
|
||||||
|
from_directory, ignore_file_handler=ignore_file_handler
|
||||||
|
):
|
||||||
for fname in files:
|
for fname in files:
|
||||||
path = os.path.join(root, fname)
|
path = os.path.join(root, fname)
|
||||||
if not Stage.is_valid_filename(path):
|
if not Stage.is_valid_filename(path):
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def brancher( # noqa: E302
|
|||||||
if self.scm.is_dirty():
|
if self.scm.is_dirty():
|
||||||
from dvc.scm.tree import WorkingTree
|
from dvc.scm.tree import WorkingTree
|
||||||
|
|
||||||
self.tree = WorkingTree()
|
self.tree = WorkingTree(self.root_dir)
|
||||||
yield "Working Tree"
|
yield "Working Tree"
|
||||||
|
|
||||||
if all_branches:
|
if all_branches:
|
||||||
|
|||||||
+32
-7
@@ -1,7 +1,8 @@
|
|||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from dvc.utils.compat import StringIO
|
from dvc.ignore import DvcIgnoreFilter
|
||||||
|
from dvc.utils.compat import StringIO, BytesIO
|
||||||
|
|
||||||
from dvc.scm.tree import BaseTree
|
from dvc.scm.tree import BaseTree
|
||||||
|
|
||||||
@@ -24,7 +25,11 @@ class GitTree(BaseTree):
|
|||||||
self.git = git
|
self.git = git
|
||||||
self.rev = rev
|
self.rev = rev
|
||||||
|
|
||||||
def open(self, path):
|
@property
|
||||||
|
def tree_root(self):
|
||||||
|
return self.git.working_dir
|
||||||
|
|
||||||
|
def open(self, path, binary=False):
|
||||||
|
|
||||||
relpath = os.path.relpath(path, self.git.working_dir)
|
relpath = os.path.relpath(path, self.git.working_dir)
|
||||||
|
|
||||||
@@ -39,7 +44,10 @@ class GitTree(BaseTree):
|
|||||||
# read it immediately, also it needs to be to decoded if we follow
|
# read it immediately, also it needs to be to decoded if we follow
|
||||||
# the `open()` behavior (since data_stream.read() returns bytes,
|
# the `open()` behavior (since data_stream.read() returns bytes,
|
||||||
# and `open` with default "r" mode returns str)
|
# and `open` with default "r" mode returns str)
|
||||||
return StringIO(obj.data_stream.read().decode("utf-8"))
|
data = obj.data_stream.read()
|
||||||
|
if binary:
|
||||||
|
return BytesIO(data)
|
||||||
|
return StringIO(data.decode("utf-8"))
|
||||||
|
|
||||||
def exists(self, path):
|
def exists(self, path):
|
||||||
return self.git_object_by_path(path) is not None
|
return self.git_object_by_path(path) is not None
|
||||||
@@ -81,8 +89,13 @@ class GitTree(BaseTree):
|
|||||||
tree = tree[i]
|
tree = tree[i]
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
def _walk(self, tree, topdown=True):
|
def _walk(
|
||||||
|
self,
|
||||||
|
tree,
|
||||||
|
topdown=True,
|
||||||
|
ignore_file_handler=None,
|
||||||
|
dvc_ignore_filter=None,
|
||||||
|
):
|
||||||
dirs, nondirs = [], []
|
dirs, nondirs = [], []
|
||||||
for i in tree:
|
for i in tree:
|
||||||
if i.mode == GIT_MODE_DIR:
|
if i.mode == GIT_MODE_DIR:
|
||||||
@@ -91,14 +104,26 @@ class GitTree(BaseTree):
|
|||||||
nondirs.append(i.name)
|
nondirs.append(i.name)
|
||||||
|
|
||||||
if topdown:
|
if topdown:
|
||||||
|
if not dvc_ignore_filter:
|
||||||
|
dvc_ignore_filter = DvcIgnoreFilter(
|
||||||
|
tree.abspath, ignore_file_handler=ignore_file_handler
|
||||||
|
)
|
||||||
|
dirs, nondirs = dvc_ignore_filter(tree.path, dirs, nondirs)
|
||||||
yield os.path.normpath(tree.path), dirs, nondirs
|
yield os.path.normpath(tree.path), dirs, nondirs
|
||||||
|
|
||||||
for i in dirs:
|
for i in dirs:
|
||||||
for x in self._walk(tree[i], topdown=True):
|
for x in self._walk(
|
||||||
|
tree[i],
|
||||||
|
topdown=True,
|
||||||
|
ignore_file_handler=ignore_file_handler,
|
||||||
|
dvc_ignore_filter=dvc_ignore_filter,
|
||||||
|
):
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
if not topdown:
|
if not topdown:
|
||||||
yield os.path.normpath(tree.path), dirs, nondirs
|
yield os.path.normpath(tree.path), dirs, nondirs
|
||||||
|
|
||||||
def walk(self, top, topdown=True):
|
def walk(self, top, topdown=True, ignore_file_handler=None):
|
||||||
"""Directory tree generator.
|
"""Directory tree generator.
|
||||||
|
|
||||||
See `os.walk` for the docs. Differences:
|
See `os.walk` for the docs. Differences:
|
||||||
|
|||||||
+23
-8
@@ -1,12 +1,17 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
from dvc.utils import dvc_walk
|
||||||
from dvc.utils.compat import open
|
from dvc.utils.compat import open
|
||||||
|
|
||||||
|
|
||||||
class BaseTree(object):
|
class BaseTree(object):
|
||||||
"""Abstract class to represent access to files"""
|
"""Abstract class to represent access to files"""
|
||||||
|
|
||||||
def open(self, path):
|
@property
|
||||||
|
def tree_root(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def open(self, path, binary=False):
|
||||||
"""Open file and return a stream."""
|
"""Open file and return a stream."""
|
||||||
|
|
||||||
def exists(self, path):
|
def exists(self, path):
|
||||||
@@ -18,7 +23,7 @@ class BaseTree(object):
|
|||||||
def isfile(self, path):
|
def isfile(self, path):
|
||||||
"""Test whether a path is a regular file"""
|
"""Test whether a path is a regular file"""
|
||||||
|
|
||||||
def walk(self, top, topdown=True):
|
def walk(self, top, topdown=True, ignore_file_handler=None):
|
||||||
"""Directory tree generator.
|
"""Directory tree generator.
|
||||||
|
|
||||||
See `os.walk` for the docs. Differences:
|
See `os.walk` for the docs. Differences:
|
||||||
@@ -30,8 +35,17 @@ class BaseTree(object):
|
|||||||
class WorkingTree(BaseTree):
|
class WorkingTree(BaseTree):
|
||||||
"""Proxies the repo file access methods to working tree files"""
|
"""Proxies the repo file access methods to working tree files"""
|
||||||
|
|
||||||
def open(self, path):
|
def __init__(self, repo_root=os.getcwd()):
|
||||||
|
self.repo_root = repo_root
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tree_root(self):
|
||||||
|
return self.repo_root
|
||||||
|
|
||||||
|
def open(self, path, binary=False):
|
||||||
"""Open file and return a stream."""
|
"""Open file and return a stream."""
|
||||||
|
if binary:
|
||||||
|
return open(path, "rb")
|
||||||
return open(path, encoding="utf-8")
|
return open(path, encoding="utf-8")
|
||||||
|
|
||||||
def exists(self, path):
|
def exists(self, path):
|
||||||
@@ -46,7 +60,7 @@ class WorkingTree(BaseTree):
|
|||||||
"""Test whether a path is a regular file"""
|
"""Test whether a path is a regular file"""
|
||||||
return os.path.isfile(path)
|
return os.path.isfile(path)
|
||||||
|
|
||||||
def walk(self, top, topdown=True):
|
def walk(self, top, topdown=True, ignore_file_handler=None):
|
||||||
"""Directory tree generator.
|
"""Directory tree generator.
|
||||||
|
|
||||||
See `os.walk` for the docs. Differences:
|
See `os.walk` for the docs. Differences:
|
||||||
@@ -57,9 +71,10 @@ class WorkingTree(BaseTree):
|
|||||||
def onerror(e):
|
def onerror(e):
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
for root, dirs, files in os.walk(
|
for root, dirs, files in dvc_walk(
|
||||||
top, topdown=topdown, onerror=onerror
|
top,
|
||||||
|
topdown=topdown,
|
||||||
|
onerror=onerror,
|
||||||
|
ignore_file_handler=ignore_file_handler,
|
||||||
):
|
):
|
||||||
if topdown:
|
|
||||||
dirs[:] = [i for i in dirs if i not in (".git", ".hg", ".dvc")]
|
|
||||||
yield os.path.normpath(root), dirs, files
|
yield os.path.normpath(root), dirs, files
|
||||||
|
|||||||
+33
-2
@@ -257,8 +257,39 @@ def load_stage_file_fobj(fobj, path):
|
|||||||
raise StageFileCorruptedError(path)
|
raise StageFileCorruptedError(path)
|
||||||
|
|
||||||
|
|
||||||
def walk_files(directory):
|
def dvc_walk(
|
||||||
for root, _, files in os.walk(str(directory)):
|
top,
|
||||||
|
topdown=True,
|
||||||
|
onerror=None,
|
||||||
|
followlinks=False,
|
||||||
|
ignore_file_handler=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Proxy for `os.walk` directory tree generator.
|
||||||
|
Utilizes DvcIgnoreFilter functionality.
|
||||||
|
"""
|
||||||
|
ignore_filter = None
|
||||||
|
if topdown:
|
||||||
|
from dvc.ignore import DvcIgnoreFilter
|
||||||
|
|
||||||
|
ignore_filter = DvcIgnoreFilter(
|
||||||
|
top, ignore_file_handler=ignore_file_handler
|
||||||
|
)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(
|
||||||
|
top, topdown=topdown, onerror=onerror, followlinks=followlinks
|
||||||
|
):
|
||||||
|
|
||||||
|
if ignore_filter:
|
||||||
|
dirs[:], files[:] = ignore_filter(root, dirs, files)
|
||||||
|
|
||||||
|
yield root, dirs, files
|
||||||
|
|
||||||
|
|
||||||
|
def walk_files(directory, ignore_file_handler=None):
|
||||||
|
for root, _, files in dvc_walk(
|
||||||
|
str(directory), ignore_file_handler=ignore_file_handler
|
||||||
|
):
|
||||||
for f in files:
|
for f in files:
|
||||||
yield os.path.join(root, f)
|
yield os.path.join(root, f)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -93,6 +93,7 @@ def _makedirs(name, mode=0o777, exist_ok=False):
|
|||||||
if is_py2:
|
if is_py2:
|
||||||
from urlparse import urlparse, urljoin # noqa: F401
|
from urlparse import urlparse, urljoin # noqa: F401
|
||||||
from StringIO import StringIO # noqa: F401
|
from StringIO import StringIO # noqa: F401
|
||||||
|
from io import BytesIO # noqa: F401
|
||||||
from BaseHTTPServer import HTTPServer # noqa: F401
|
from BaseHTTPServer import HTTPServer # noqa: F401
|
||||||
from SimpleHTTPServer import SimpleHTTPRequestHandler # noqa: F401
|
from SimpleHTTPServer import SimpleHTTPRequestHandler # noqa: F401
|
||||||
import ConfigParser # noqa: F401
|
import ConfigParser # noqa: F401
|
||||||
@@ -111,7 +112,7 @@ if is_py2:
|
|||||||
elif is_py3:
|
elif is_py3:
|
||||||
from os import makedirs # noqa: F401
|
from os import makedirs # noqa: F401
|
||||||
from urllib.parse import urlparse, urljoin # noqa: F401
|
from urllib.parse import urlparse, urljoin # noqa: F401
|
||||||
from io import StringIO # noqa: F401
|
from io import StringIO, BytesIO # noqa: F401
|
||||||
from http.server import ( # noqa: F401
|
from http.server import ( # noqa: F401
|
||||||
HTTPServer, # noqa: F401
|
HTTPServer, # noqa: F401
|
||||||
SimpleHTTPRequestHandler, # noqa: F401
|
SimpleHTTPRequestHandler, # noqa: F401
|
||||||
|
|||||||
+21
-1
@@ -7,6 +7,7 @@ import logging
|
|||||||
|
|
||||||
from dvc.exceptions import DvcException
|
from dvc.exceptions import DvcException
|
||||||
from dvc.system import System
|
from dvc.system import System
|
||||||
|
from dvc.utils import dvc_walk
|
||||||
from dvc.utils.compat import str
|
from dvc.utils.compat import str
|
||||||
|
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ def get_mtime_and_size(path):
|
|||||||
mtime = os.path.getmtime(path)
|
mtime = os.path.getmtime(path)
|
||||||
|
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
for root, dirs, files in os.walk(str(path)):
|
for root, dirs, files in dvc_walk(str(path)):
|
||||||
for name in dirs + files:
|
for name in dirs + files:
|
||||||
entry = os.path.join(root, name)
|
entry = os.path.join(root, name)
|
||||||
try:
|
try:
|
||||||
@@ -63,3 +64,22 @@ def contains_symlink_up_to(path, base_path):
|
|||||||
if os.path.dirname(path) == path:
|
if os.path.dirname(path) == path:
|
||||||
return False
|
return False
|
||||||
return contains_symlink_up_to(os.path.dirname(path), base_path)
|
return contains_symlink_up_to(os.path.dirname(path), base_path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_parent_dirs_up_to(wdir, root_dir):
|
||||||
|
|
||||||
|
assert os.path.isabs(wdir)
|
||||||
|
assert os.path.isabs(root_dir)
|
||||||
|
|
||||||
|
wdir = os.path.normpath(wdir)
|
||||||
|
root_dir = os.path.normpath(root_dir)
|
||||||
|
if root_dir not in wdir:
|
||||||
|
return []
|
||||||
|
|
||||||
|
dirs = []
|
||||||
|
dirs.append(wdir)
|
||||||
|
while wdir != root_dir:
|
||||||
|
wdir = os.path.dirname(wdir)
|
||||||
|
dirs.append(wdir)
|
||||||
|
|
||||||
|
return dirs
|
||||||
|
|||||||
@@ -28,3 +28,4 @@ appdirs>=1.4.3
|
|||||||
treelib>=1.5.5
|
treelib>=1.5.5
|
||||||
inflect>=2.1.0
|
inflect>=2.1.0
|
||||||
humanize>=0.5.1
|
humanize>=0.5.1
|
||||||
|
dulwich>=0.19.11
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ install_requires = [
|
|||||||
"treelib>=1.5.5",
|
"treelib>=1.5.5",
|
||||||
"inflect>=2.1.0",
|
"inflect>=2.1.0",
|
||||||
"humanize>=0.5.1",
|
"humanize>=0.5.1",
|
||||||
|
"dulwich>=0.19.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Extra dependencies for remote integrations
|
# Extra dependencies for remote integrations
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from dvc.ignore import DvcIgnoreFromFile, DvcIgnore, DvcIgnoreFileHandler
|
||||||
|
from dvc.utils.compat import cast_bytes
|
||||||
|
from tests.basic_env import TestDvc
|
||||||
|
|
||||||
|
|
||||||
|
class TestDvcIgnore(TestDvc):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDvcIgnore, self).setUp()
|
||||||
|
self.ignore_file_handler = DvcIgnoreFileHandler(self.dvc.tree)
|
||||||
|
|
||||||
|
def _get_all_paths(self):
|
||||||
|
|
||||||
|
paths = []
|
||||||
|
ignore_file_handler = DvcIgnoreFileHandler(self.dvc.tree)
|
||||||
|
for root, dirs, files in self.dvc.tree.walk(
|
||||||
|
self.dvc.root_dir, ignore_file_handler=ignore_file_handler
|
||||||
|
):
|
||||||
|
for dname in dirs:
|
||||||
|
paths.append(os.path.join(root, dname))
|
||||||
|
|
||||||
|
for fname in files:
|
||||||
|
paths.append(os.path.join(root, fname))
|
||||||
|
|
||||||
|
return paths
|
||||||
|
|
||||||
|
def test_ignore_comments(self):
|
||||||
|
ignore_file = os.path.join(self.dvc.root_dir, DvcIgnore.DVCIGNORE_FILE)
|
||||||
|
with open(ignore_file, "w") as fobj:
|
||||||
|
fobj.write(os.path.basename(self.DATA))
|
||||||
|
fobj.write(" #this is comment")
|
||||||
|
|
||||||
|
ignore = DvcIgnoreFromFile(ignore_file, self.ignore_file_handler)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(ignore.patterns))
|
||||||
|
|
||||||
|
def test_ignore_in_child_dir(self):
|
||||||
|
ignore_file = os.path.join(self.dvc.root_dir, DvcIgnore.DVCIGNORE_FILE)
|
||||||
|
with open(ignore_file, "w") as fobj:
|
||||||
|
fobj.write("data_dir/data")
|
||||||
|
|
||||||
|
forbidden_path = os.path.join(self.dvc.root_dir, self.DATA)
|
||||||
|
all_paths = self._get_all_paths()
|
||||||
|
|
||||||
|
self.assertNotIn(forbidden_path, all_paths)
|
||||||
|
|
||||||
|
def test_ignore_in_child_dir_unicode(self):
|
||||||
|
ignore_file = os.path.join(self.dvc.root_dir, DvcIgnore.DVCIGNORE_FILE)
|
||||||
|
with open(ignore_file, "wb") as fobj:
|
||||||
|
fobj.write(cast_bytes(self.UNICODE, "utf-8"))
|
||||||
|
|
||||||
|
forbidden_path = os.path.join(self.dvc.root_dir, self.UNICODE)
|
||||||
|
all_paths = self._get_all_paths()
|
||||||
|
|
||||||
|
self.assertNotIn(forbidden_path, all_paths)
|
||||||
|
|
||||||
|
def test_ignore_in_parent_dir(self):
|
||||||
|
ignore_file = os.path.join(self.dvc.root_dir, DvcIgnore.DVCIGNORE_FILE)
|
||||||
|
with open(ignore_file, "w") as fobj:
|
||||||
|
fobj.write("data_dir/data")
|
||||||
|
|
||||||
|
os.chdir(self.DATA_DIR)
|
||||||
|
|
||||||
|
forbidden_path = os.path.join(self.dvc.root_dir, self.DATA)
|
||||||
|
all_paths = self._get_all_paths()
|
||||||
|
|
||||||
|
self.assertNotIn(forbidden_path, all_paths)
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from dvc.main import main
|
||||||
|
from dvc.stage import Stage
|
||||||
from tests.basic_env import TestDvc
|
from tests.basic_env import TestDvc
|
||||||
|
|
||||||
from dvc.scm.git import GitTree
|
from dvc.scm.git import GitTree
|
||||||
@@ -44,3 +46,27 @@ class TestCollect(TestDvc):
|
|||||||
)
|
)
|
||||||
result = self._check("new_branch", "buzz", False, [["buzz"]])
|
result = self._check("new_branch", "buzz", False, [["buzz"]])
|
||||||
self.assertEqual([i.rel_path for i in result[0].deps], ["bar"])
|
self.assertEqual([i.rel_path for i in result[0].deps], ["bar"])
|
||||||
|
|
||||||
|
|
||||||
|
class TestIgnore(TestDvc):
|
||||||
|
def _stage_name(self, file):
|
||||||
|
return file + Stage.STAGE_FILE_SUFFIX
|
||||||
|
|
||||||
|
def test_should_not_gather_stage_files_from_ignored_d(self):
|
||||||
|
ret = main(["add", self.FOO, self.BAR, self.DATA, self.DATA_SUB])
|
||||||
|
self.assertEqual(0, ret)
|
||||||
|
|
||||||
|
stages = self.dvc.stages()
|
||||||
|
self.assertEqual(4, len(stages))
|
||||||
|
|
||||||
|
with open(".dvcignore", "w") as fobj:
|
||||||
|
fobj.write("data_dir")
|
||||||
|
|
||||||
|
stages = self.dvc.stages()
|
||||||
|
self.assertEqual(2, len(stages))
|
||||||
|
|
||||||
|
stagenames = [s.relpath for s in stages]
|
||||||
|
self.assertIn(self._stage_name(self.FOO), stagenames)
|
||||||
|
self.assertIn(self._stage_name(self.BAR), stagenames)
|
||||||
|
self.assertNotIn(self._stage_name(self.DATA), stagenames)
|
||||||
|
self.assertNotIn(self._stage_name(self.DATA_SUB), stagenames)
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from dvc.ignore import DvcIgnoreFromFile, DvcIgnoreDir, DvcIgnoreFile
|
||||||
|
from mock import patch, Mock
|
||||||
|
|
||||||
|
from dvc.utils.compat import cast_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def read_pattern(p):
|
||||||
|
return cast_bytes(p, "utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def mock_dvcignore(dvcignore_path, negate_patterns, patterns):
|
||||||
|
negate_patterns = [read_pattern(p) for p in negate_patterns]
|
||||||
|
patterns = [read_pattern(p) for p in patterns]
|
||||||
|
|
||||||
|
mock_ignore_file_handler = Mock()
|
||||||
|
with patch.object(
|
||||||
|
mock_ignore_file_handler,
|
||||||
|
"read_patterns",
|
||||||
|
return_value=(negate_patterns, patterns),
|
||||||
|
):
|
||||||
|
ignore_file = DvcIgnoreFromFile(
|
||||||
|
dvcignore_path, mock_ignore_file_handler
|
||||||
|
)
|
||||||
|
return ignore_file
|
||||||
|
|
||||||
|
|
||||||
|
def test_ignore_from_file_should_filter_dirs_and_files():
|
||||||
|
dvcignore_path = os.path.join(
|
||||||
|
os.path.sep, "full", "path", "to", "ignore", "file", ".dvcignore"
|
||||||
|
)
|
||||||
|
|
||||||
|
negate_patterns = []
|
||||||
|
patterns = ["dir_to_ignore", "file_to_ignore"]
|
||||||
|
|
||||||
|
root = os.path.dirname(dvcignore_path)
|
||||||
|
dirs = ["dir1", "dir2", "dir_to_ignore"]
|
||||||
|
files = ["file1", "file2", "file_to_ignore"]
|
||||||
|
|
||||||
|
ignore = mock_dvcignore(dvcignore_path, negate_patterns, patterns)
|
||||||
|
new_dirs, new_files = ignore(root, dirs, files)
|
||||||
|
|
||||||
|
assert {"dir1", "dir2"} == set(new_dirs)
|
||||||
|
assert {"file1", "file2"} == set(new_files)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"file_to_ignore_relpath, negate_patterns,patterns, expected_match",
|
||||||
|
[
|
||||||
|
("to_ignore", [], ["to_ignore"], True),
|
||||||
|
("to_ignore.txt", [], ["to_ignore*"], True),
|
||||||
|
(
|
||||||
|
os.path.join("rel", "p", "p2", "to_ignore"),
|
||||||
|
[],
|
||||||
|
["rel/**/to_ignore"],
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
os.path.join(
|
||||||
|
os.path.sep,
|
||||||
|
"full",
|
||||||
|
"path",
|
||||||
|
"to",
|
||||||
|
"ignore",
|
||||||
|
"file",
|
||||||
|
"to_ignore",
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
["to_ignore"],
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
("to_ignore.txt", [], ["/*.txt"], True),
|
||||||
|
(
|
||||||
|
os.path.join("rel", "path", "path2", "to_ignore"),
|
||||||
|
[],
|
||||||
|
["rel/*/to_ignore"],
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(os.path.join("path", "to_ignore.txt"), [], ["/*.txt"], False),
|
||||||
|
(
|
||||||
|
os.path.join("rel", "path", "path2", "dont_ignore"),
|
||||||
|
[],
|
||||||
|
["rel/**/to_ignore"],
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
("dont_ignore.txt", [], ["dont_ignore"], False),
|
||||||
|
("dont_ignore.txt", ["!dont_ignore.txt"], ["dont*"], False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_match_ignore_from_file(
|
||||||
|
file_to_ignore_relpath, negate_patterns, patterns, expected_match
|
||||||
|
):
|
||||||
|
|
||||||
|
dvcignore_path = os.path.join(
|
||||||
|
os.path.sep, "full", "path", "to", "ignore", "file", ".dvcignore"
|
||||||
|
)
|
||||||
|
dvcignore_dirname = os.path.dirname(dvcignore_path)
|
||||||
|
|
||||||
|
ignore_file = mock_dvcignore(dvcignore_path, negate_patterns, patterns)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
ignore_file.matches(dvcignore_dirname, file_to_ignore_relpath)
|
||||||
|
== expected_match
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("omit_dir", [".git", ".hg", ".dvc"])
|
||||||
|
def test_should_ignore_dir(omit_dir):
|
||||||
|
ignore = DvcIgnoreDir(omit_dir)
|
||||||
|
|
||||||
|
root = os.path.join(os.path.sep, "walk", "dir", "root")
|
||||||
|
dirs = [omit_dir, "dir1", "dir2"]
|
||||||
|
files = []
|
||||||
|
|
||||||
|
new_dirs, _ = ignore(root, dirs, files)
|
||||||
|
|
||||||
|
assert set(new_dirs) == {"dir1", "dir2"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_ignore_file():
|
||||||
|
dvcignore = ".dvcignore"
|
||||||
|
ignore = DvcIgnoreFile(dvcignore)
|
||||||
|
|
||||||
|
root = os.path.join(os.path.sep, "walk", "dir", "root")
|
||||||
|
dirs = []
|
||||||
|
files = ["file1", "file2", dvcignore]
|
||||||
|
|
||||||
|
_, new_files = ignore(root, dirs, files)
|
||||||
|
|
||||||
|
assert set(new_files) == {"file1", "file2"}
|
||||||
@@ -2,12 +2,14 @@ import os
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
import dvc
|
import dvc
|
||||||
|
import pytest
|
||||||
from dvc.system import System
|
from dvc.system import System
|
||||||
from dvc.utils.compat import str
|
from dvc.utils.compat import str
|
||||||
from dvc.utils.fs import (
|
from dvc.utils.fs import (
|
||||||
get_mtime_and_size,
|
get_mtime_and_size,
|
||||||
contains_symlink_up_to,
|
contains_symlink_up_to,
|
||||||
BasePathNotInCheckedPathException,
|
BasePathNotInCheckedPathException,
|
||||||
|
get_parent_dirs_up_to,
|
||||||
)
|
)
|
||||||
from mock import patch
|
from mock import patch
|
||||||
from tests.basic_env import TestDir
|
from tests.basic_env import TestDir
|
||||||
@@ -90,3 +92,46 @@ class TestContainsLink(TestCase):
|
|||||||
System, "is_symlink", side_effect=base_path_is_symlink
|
System, "is_symlink", side_effect=base_path_is_symlink
|
||||||
):
|
):
|
||||||
self.assertFalse(contains_symlink_up_to(target_path, base_path))
|
self.assertFalse(contains_symlink_up_to(target_path, base_path))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path1, path2",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
os.path.join("non", "abs", "path"),
|
||||||
|
os.path.join(os.sep, "full", "path"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
os.path.join(os.sep, "full", "path"),
|
||||||
|
os.path.join("non", "abs", "path"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_get_parent_dirs_up_to_should_raise_on_no_absolute(path1, path2):
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
get_parent_dirs_up_to(path1, path2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path1, path2, expected_dirs",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
os.path.join(os.sep, "non", "matching", "path"),
|
||||||
|
os.path.join(os.sep, "other", "path"),
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
os.path.join(os.sep, "some", "long", "path"),
|
||||||
|
os.path.join(os.sep, "some"),
|
||||||
|
[
|
||||||
|
os.path.join(os.sep, "some"),
|
||||||
|
os.path.join(os.sep, "some", "long"),
|
||||||
|
os.path.join(os.sep, "some", "long", "path"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_get_parent_dirs_up_to(path1, path2, expected_dirs):
|
||||||
|
result = get_parent_dirs_up_to(path1, path2)
|
||||||
|
|
||||||
|
assert set(result) == set(expected_dirs)
|
||||||
|
|||||||
Reference in New Issue
Block a user