Skip to content

Commit 8405666

Browse files
committed
fix(import[prune]) Scope prune skip-check to (workspace, name) pairs
why: fetched_repo_names was a flat set of names, so a stale entry in workspace B was wrongly protected from pruning if a same-named repo existed in the remote's workspace A. what: - Track imported repos as (workspace_label, name) tuples - Prune skip-check now uses (ws_label, repo_name) membership test - Add test for cross-workspace same-source same-name pruning
1 parent a3938dd commit 8405666

2 files changed

Lines changed: 70 additions & 3 deletions

File tree

src/vcspull/cli/import_cmd/_common.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,7 @@ def _run_import(
855855
added_count = updated_url_count = 0
856856
skip_existing_count = skip_pinned_count = skip_unchanged_count = 0
857857
provenance_tagged_count = 0
858+
imported_workspace_repos: set[tuple[str, str]] = set()
858859

859860
for repo in repos:
860861
# Determine workspace for this repo
@@ -933,6 +934,7 @@ def _run_import(
933934
existing_entry=existing_raw,
934935
sync=sync,
935936
)
937+
imported_workspace_repos.add((repo_workspace_label, repo.name))
936938

937939
if action == ImportAction.ADD:
938940
if not dry_run:
@@ -991,12 +993,11 @@ def _run_import(
991993
# Prune stale entries tagged by previous imports from this source
992994
pruned_count = 0
993995
if (sync or prune) and import_source:
994-
fetched_repo_names = {repo.name for repo in repos}
995-
for _ws_label, ws_entries in list(raw_config.items()):
996+
for ws_label, ws_entries in list(raw_config.items()):
996997
if not isinstance(ws_entries, dict):
997998
continue
998999
for repo_name in list(ws_entries):
999-
if repo_name in fetched_repo_names:
1000+
if (ws_label, repo_name) in imported_workspace_repos:
10001001
continue
10011002
prune_action = _classify_prune_action(
10021003
existing_entry=ws_entries[repo_name],

tests/cli/test_import_repos.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4288,6 +4288,72 @@ def test_import_sync_same_name_from_remote_not_pruned(
42884288
assert entry["metadata"]["imported_from"] == "github:testuser"
42894289

42904290

4291+
def test_import_prune_cross_workspace_same_source_same_name(
4292+
tmp_path: pathlib.Path,
4293+
monkeypatch: MonkeyPatch,
4294+
) -> None:
4295+
"""Stale entry in workspace B is pruned even if workspace A has same name."""
4296+
monkeypatch.setenv("HOME", str(tmp_path))
4297+
workspace_a = tmp_path / "code"
4298+
workspace_a.mkdir()
4299+
workspace_b = tmp_path / "work"
4300+
workspace_b.mkdir()
4301+
config_file = tmp_path / ".vcspull.yaml"
4302+
4303+
# Both workspaces have "myrepo" from the same import source
4304+
save_config_yaml(
4305+
config_file,
4306+
{
4307+
"~/code/": {
4308+
"myrepo": {
4309+
"repo": "git+git@github.com:testuser/myrepo.git",
4310+
"metadata": {"imported_from": "github:testuser"},
4311+
},
4312+
},
4313+
"~/work/": {
4314+
"myrepo": {
4315+
"repo": "git+git@github.com:testuser/myrepo.git",
4316+
"metadata": {"imported_from": "github:testuser"},
4317+
},
4318+
},
4319+
},
4320+
)
4321+
4322+
# Remote returns myrepo only to workspace A — workspace B's entry is stale
4323+
importer = MockImporter(repos=[_make_repo("myrepo")])
4324+
_run_import(
4325+
importer,
4326+
service_name="github",
4327+
target="testuser",
4328+
workspace=str(workspace_a),
4329+
mode="user",
4330+
language=None,
4331+
topics=None,
4332+
min_stars=0,
4333+
include_archived=False,
4334+
include_forks=False,
4335+
limit=100,
4336+
config_path_str=str(config_file),
4337+
dry_run=False,
4338+
yes=True,
4339+
output_json=False,
4340+
output_ndjson=False,
4341+
color="never",
4342+
sync=True,
4343+
import_source="github:testuser",
4344+
)
4345+
4346+
from vcspull._internal.config_reader import ConfigReader
4347+
4348+
final_config = ConfigReader._from_file(config_file)
4349+
assert final_config is not None
4350+
4351+
# Workspace A keeps myrepo (it was imported there)
4352+
assert "myrepo" in final_config["~/code/"]
4353+
# Workspace B's stale myrepo should be pruned
4354+
assert "myrepo" not in final_config.get("~/work/", {})
4355+
4356+
42914357
def test_import_parser_has_prune_flag() -> None:
42924358
"""The shared parent parser must expose --prune."""
42934359
from vcspull.cli.import_cmd._common import _create_shared_parent

0 commit comments

Comments
 (0)