Skip to content

Commit a041917

Browse files
authored
Merge pull request #3088 from efiop/3080
dvc: add sanity checks for hard/symlinks
2 parents 4903996 + d863c46 commit a041917

File tree

4 files changed

+33
-16
lines changed

4 files changed

+33
-16
lines changed

dvc/command/version.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ def get_fs_type(path):
8686
@staticmethod
8787
def get_linktype_support_info(repo):
8888
links = {
89-
"reflink": System.reflink,
90-
"hardlink": System.hardlink,
91-
"symlink": System.symlink,
89+
"reflink": (System.reflink, None),
90+
"hardlink": (System.hardlink, System.is_hardlink),
91+
"symlink": (System.symlink, System.is_symlink),
9292
}
9393

9494
fname = "." + str(uuid.uuid4())
@@ -98,18 +98,16 @@ def get_linktype_support_info(repo):
9898

9999
cache = []
100100

101-
for name, link in links.items():
101+
for name, (link, is_link) in links.items():
102102
try:
103103
link(src, dst)
104+
status = "supported"
105+
if is_link and not is_link(dst):
106+
status = "broken"
104107
os.unlink(dst)
105-
supported = True
106108
except DvcException:
107-
supported = False
108-
cache.append(
109-
"{name} - {supported}".format(
110-
name=name, supported=True if supported else False
111-
)
112-
)
109+
status = "not supported"
110+
cache.append("{name} - {status}".format(name=name, status=status))
113111
os.remove(src)
114112

115113
return ", ".join(cache)

dvc/remote/base.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,13 +392,24 @@ def _link(self, from_info, to_info, link_types):
392392

393393
self._try_links(from_info, to_info, link_types)
394394

395+
def _verify_link(self, path_info, link_type):
396+
if self.cache_type_confirmed:
397+
return
398+
399+
is_link = getattr(self, "is_{}".format(link_type), None)
400+
if is_link and not is_link(path_info):
401+
self.remove(path_info)
402+
raise DvcException("failed to verify {}".format(link_type))
403+
404+
self.cache_type_confirmed = True
405+
395406
@slow_link_guard
396407
def _try_links(self, from_info, to_info, link_types):
397408
while link_types:
398409
link_method = getattr(self, link_types[0])
399410
try:
400411
self._do_link(from_info, to_info, link_method)
401-
self.cache_type_confirmed = True
412+
self._verify_link(to_info, link_types[0])
402413
return
403414

404415
except DvcException as exc:

dvc/remote/local.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ def copy(self, from_info, to_info):
178178
def symlink(from_info, to_info):
179179
System.symlink(from_info, to_info)
180180

181+
@staticmethod
182+
def is_symlink(path_info):
183+
return System.is_symlink(path_info)
184+
181185
def hardlink(self, from_info, to_info):
182186
# If there are a lot of empty files (which happens a lot in datasets),
183187
# and the cache type is `hardlink`, we might reach link limits and
@@ -204,6 +208,10 @@ def hardlink(self, from_info, to_info):
204208

205209
System.hardlink(from_info, to_info)
206210

211+
@staticmethod
212+
def is_hardlink(path_info):
213+
return System.is_hardlink(path_info)
214+
207215
@staticmethod
208216
def reflink(from_info, to_info):
209217
System.reflink(from_info, to_info)

tests/func/test_version.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ def test_info_in_repo(tmp_dir, dvc, caplog):
1717
assert re.search(r"Platform: .*", caplog.text)
1818
assert re.search(r"Binary: (True|False)", caplog.text)
1919
assert re.search(r"Package: .*", caplog.text)
20-
assert re.search(r"(Cache: (.*link - (True|False)(,\s)?){3})", caplog.text)
20+
assert re.search(
21+
r"(Cache: (.*link - (not )?supported(,\s)?){3})", caplog.text
22+
)
2123

2224

2325
@pytest.mark.skipif(psutil is None, reason="No psutil.")
@@ -37,9 +39,7 @@ def test_info_outside_of_repo(tmp_dir, caplog):
3739
assert re.search(r"Platform: .*", caplog.text)
3840
assert re.search(r"Binary: (True|False)", caplog.text)
3941
assert re.search(r"Package: .*", caplog.text)
40-
assert not re.search(
41-
r"(Cache: (.*link - (True|False)(,\s)?){3})", caplog.text
42-
)
42+
assert not re.search(r"(Cache: (.*link - (not )?(,\s)?){3})", caplog.text)
4343

4444

4545
@pytest.mark.skipif(psutil is None, reason="No psutil.")

0 commit comments

Comments
 (0)