Skip to content

Commit 2bf5efe

Browse files
committed
Let iter_gitworktree(fp=True) report on symlinked content
This generalizes an approach from datalad#539. It is implemented in a way that enables reuse of the helpers in that PR too. With this change regular files (tracked or untracked) and symlink targets (via the symlink) are also opened, if they actually exist. Closes datalad#553
1 parent 72d28aa commit 2bf5efe

File tree

2 files changed

+90
-5
lines changed

2 files changed

+90
-5
lines changed

datalad_next/iter_collections/gitworktree.py

+33-5
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,14 @@ def iter_gitworktree(
223223
# report on a pending item, this is not a "higher-stage"
224224
# report by ls-files
225225
item = _get_item(path, link_target, fp, *pending_item)
226-
if fp and item.type == FileSystemItemType.file:
227-
with (Path(path) / item.name).open('rb') as fp:
228-
item.fp = fp
229-
yield item
230-
else:
226+
fp_src = _get_fp_src(fp, path, item)
227+
if fp_src is None:
228+
# nothing to open
231229
yield item
230+
else:
231+
with fp_src.open('rb') as active_fp:
232+
item.fp = active_fp
233+
yield item
232234

233235
if ipath is None:
234236
# this is the trailing `None` record. we are done here
@@ -328,3 +330,29 @@ def _git_ls_files(path, *args):
328330
keep_ends=False,
329331
)
330332
)
333+
334+
335+
def _get_fp_src(
336+
fp: bool,
337+
basepath: Path,
338+
item: GitWorktreeItem | GitWorktreeFileSystemItem,
339+
) -> Path | None:
340+
if not fp:
341+
# no file pointer request, we are done
342+
return None
343+
344+
# if we get here, this is about file pointers...
345+
fp_src = None
346+
if item.type in (FileSystemItemType.file,
347+
FileSystemItemType.symlink):
348+
fp_src = item.name
349+
if fp_src is None:
350+
# nothing to open
351+
return None
352+
353+
fp_src_fullpath = basepath / fp_src
354+
if not fp_src_fullpath.exists():
355+
# nothing there to open (would resolve through a symlink)
356+
return None
357+
358+
return fp_src_fullpath

datalad_next/iter_collections/tests/test_itergitworktree.py

+57
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import pytest
77

8+
from datalad_next.utils import check_symlink_capability
9+
810
from datalad_next.tests.utils import rmtree
911

1012
from ..gitworktree import (
@@ -170,3 +172,58 @@ def test_iter_gitworktree_deadsymlinks(existing_dataset, no_result_rendering):
170172
# it may take a different form, hence not checking for type
171173
assert len(all_items) == 1
172174
assert all_items[0].name == PurePath('file1')
175+
176+
177+
def prep_fp_tester(ds):
178+
# we expect to process an exact number of files below
179+
# 3 annexed files, 1 untracked, 1 in git,
180+
# and possibly 1 symlink in git, 1 symlink untracked
181+
# we count them up on creation, and then down on test
182+
fcount = 0
183+
184+
# TODO bring back the umlaut. But waiting for triage
185+
# https://github.com/datalad/datalad-next/pull/539#issuecomment-1842605708
186+
#content_tmpl = 'content: #ö file_{}\n'
187+
content_tmpl = 'content: # file_{}\n'
188+
for i in range(3):
189+
(ds.pathobj / f'file_{i}').write_text(content_tmpl.format(i))
190+
fcount += 1
191+
ds.save()
192+
ds.drop(
193+
ds.pathobj / 'file_1',
194+
reckless='availability',
195+
)
196+
# and also add a file to git directly and a have one untracked too
197+
for i in ('untracked', 'ingit'):
198+
(ds.pathobj / f'file_{i}').write_text(content_tmpl.format(i))
199+
fcount += 1
200+
ds.save('file_ingit', to_git=True)
201+
# and add symlinks (untracked and in git)
202+
if check_symlink_capability(
203+
ds.pathobj / '_dummy', ds.pathobj / '_dummy_target'
204+
):
205+
for i in ('symlinkuntracked', 'symlinkingit'):
206+
tpath = ds.pathobj / f'target_{i}'
207+
lpath = ds.pathobj / f'file_{i}'
208+
tpath.write_text(content_tmpl.format(i))
209+
lpath.symlink_to(tpath)
210+
fcount += 1
211+
ds.save('file_symlinkingit', to_git=True)
212+
return fcount, content_tmpl
213+
214+
215+
def test_iter_gitworktree_basic_fp(existing_dataset, no_result_rendering):
216+
ds = existing_dataset
217+
fcount, content_tmpl = prep_fp_tester(ds)
218+
219+
for ai in filter(
220+
lambda i: str(i.name.name).startswith('file_'),
221+
iter_gitworktree(ds.pathobj, fp=True)
222+
):
223+
fcount -= 1
224+
if ai.fp:
225+
assert content_tmpl.format(
226+
ai.name.name[5:]) == ai.fp.read().decode()
227+
else:
228+
assert (ds.pathobj / ai.name).exists() is False
229+
assert not fcount

0 commit comments

Comments
 (0)