Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions dvc/repo/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -966,12 +966,17 @@ def index_from_targets(
continue
file, name = parse_target(target)
if file and not name:
index = Index.from_file(repo, file)
idx = Index.from_file(repo, file)
else:
stages = repo.stage.collect(target)
index = Index(repo, stages=list(stages))
indexes.append(index)
idx = Index(repo, stages=list(stages))
indexes.append(idx)
except (StageFileDoesNotExistError, StageNotFound):
# A target that is not a stage/.dvc file, such as a granular path
# inside a tracked directory, aborts the per-target merge partway
# through. Fall back to the full repo index with the original
# targets: a partial index built only from the targets parsed
# before the failure silently drops the rest (#11075).
pass
else:
index = Index.from_indexes(repo, indexes)
Expand Down
38 changes: 38 additions & 0 deletions tests/func/test_data_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,44 @@ def test_verify_hashes(tmp_dir, scm, dvc, mocker, tmp_path_factory, local_remote
assert hash_spy.call_count == 10


def test_pull_mixed_dvcfile_and_granular_targets(tmp_dir, scm, dvc, local_remote):
"""Regression test for #11075.

A ``pull`` target list that mixes a ``.dvc``-file target with a granular path
inside a tracked directory used to build a partial index from only the
``.dvc`` target while still filtering against the full target list, so the
granular target was silently skipped (or, when the directory had drifted,
``checkout`` crashed with an uncaught ``KeyError``).
"""
tmp_dir.dvc_gen(
{"datadir": {"f1.txt": "one", "f2.txt": "two", "f3.txt": "three"}},
commit="add dir",
)
tmp_dir.dvc_gen("single.csv", "single", commit="add single")
dvc.push()

mixed_targets = ["single.csv.dvc", join("datadir", "f1.txt")]

# Fresh-clone state: the granular target must be checked out, not skipped.
remove("datadir")
remove("single.csv")
dvc.cache.local.clear()

dvc.pull(mixed_targets)

assert (tmp_dir / "datadir" / "f1.txt").read_text() == "one"
assert (tmp_dir / "single.csv").read_text() == "single"

# A drifted (untracked) file inside the tracked directory must not turn the
# mixed-target pull into an uncaught KeyError during checkout.
dvc.pull()
(tmp_dir / "datadir" / "extra-drift.txt").write_text("extra")

dvc.pull(mixed_targets) # must not raise

assert (tmp_dir / "datadir" / "f1.txt").read_text() == "one"


# @pytest.mark.flaky(reruns=3)
@pytest.mark.parametrize("erepo_type", ["git_dir", "erepo_dir"])
def test_pull_git_imports(request, tmp_dir, dvc, scm, erepo_type):
Expand Down
Loading