|
12 | 12 |
|
13 | 13 | import pytest |
14 | 14 |
|
| 15 | +from vcspull._internal.config_reader import DuplicateAwareConfigReader |
15 | 16 | from vcspull.cli.add import add_repo, create_add_subparser, handle_add_command |
16 | 17 | from vcspull.util import contract_user_home |
17 | 18 |
|
@@ -772,3 +773,115 @@ def test_add_parser_rejects_extra_positional() -> None: |
772 | 773 |
|
773 | 774 | with pytest.raises(SystemExit): |
774 | 775 | parser.parse_args(["add", "name", "https://example.com/repo.git"]) |
| 776 | + |
| 777 | + |
| 778 | +class NoMergePreservationFixture(t.NamedTuple): |
| 779 | + """Fixture for asserting --no-merge keeps duplicate sections intact.""" |
| 780 | + |
| 781 | + test_id: str |
| 782 | + initial_yaml: str |
| 783 | + expected_original_repos: tuple[str, ...] |
| 784 | + new_repo_name: str |
| 785 | + new_repo_url: str |
| 786 | + workspace_label: str |
| 787 | + |
| 788 | + |
| 789 | +NO_MERGE_PRESERVATION_FIXTURES: list[NoMergePreservationFixture] = [ |
| 790 | + NoMergePreservationFixture( |
| 791 | + test_id="duplicate-root-yaml", |
| 792 | + initial_yaml=textwrap.dedent( |
| 793 | + """\ |
| 794 | + ~/study/python/: |
| 795 | + Flexget: |
| 796 | + repo: git+https://github.com/Flexget/Flexget.git |
| 797 | + MyST-Parser: |
| 798 | + repo: git@github.com:executablebooks/MyST-Parser.git |
| 799 | + RootTheBox: |
| 800 | + repo: git+https://github.com/moloch--/RootTheBox.git |
| 801 | + ~/study/python/: |
| 802 | + bubbles: |
| 803 | + repo: git+https://github.com/Stiivi/bubbles.git |
| 804 | + cubes: |
| 805 | + repo: git+https://github.com/Stiivi/cubes.git |
| 806 | + """ |
| 807 | + ), |
| 808 | + expected_original_repos=( |
| 809 | + "Flexget", |
| 810 | + "MyST-Parser", |
| 811 | + "RootTheBox", |
| 812 | + "bubbles", |
| 813 | + "cubes", |
| 814 | + ), |
| 815 | + new_repo_name="pytest-docker", |
| 816 | + new_repo_url="git+https://github.com/avast/pytest-docker", |
| 817 | + workspace_label="~/study/python/", |
| 818 | + ), |
| 819 | +] |
| 820 | + |
| 821 | + |
| 822 | +@pytest.mark.xfail( |
| 823 | + reason="vcspull add --no-merge overwrites earlier duplicate workspace sections (data loss bug)", |
| 824 | +) |
| 825 | +@pytest.mark.parametrize( |
| 826 | + list(NoMergePreservationFixture._fields), |
| 827 | + NO_MERGE_PRESERVATION_FIXTURES, |
| 828 | + ids=[fixture.test_id for fixture in NO_MERGE_PRESERVATION_FIXTURES], |
| 829 | +) |
| 830 | +def test_add_repo_no_merge_preserves_duplicate_sections( |
| 831 | + test_id: str, |
| 832 | + initial_yaml: str, |
| 833 | + expected_original_repos: tuple[str, ...], |
| 834 | + new_repo_name: str, |
| 835 | + new_repo_url: str, |
| 836 | + workspace_label: str, |
| 837 | + tmp_path: pathlib.Path, |
| 838 | + monkeypatch: MonkeyPatch, |
| 839 | +) -> None: |
| 840 | + """vcspull add should not drop duplicate workspace sections when --no-merge.""" |
| 841 | + monkeypatch.setenv("HOME", str(tmp_path)) |
| 842 | + monkeypatch.chdir(tmp_path) |
| 843 | + |
| 844 | + config_file = tmp_path / ".vcspull.yaml" |
| 845 | + config_file.write_text(initial_yaml, encoding="utf-8") |
| 846 | + |
| 847 | + repo_path = tmp_path / "study/python" / new_repo_name |
| 848 | + repo_path.mkdir(parents=True, exist_ok=True) |
| 849 | + |
| 850 | + ( |
| 851 | + _initial_config, |
| 852 | + initial_duplicates, |
| 853 | + ) = DuplicateAwareConfigReader.load_with_duplicates(config_file) |
| 854 | + assert workspace_label in initial_duplicates |
| 855 | + assert len(initial_duplicates[workspace_label]) == 2 |
| 856 | + |
| 857 | + add_repo( |
| 858 | + name=new_repo_name, |
| 859 | + url=new_repo_url, |
| 860 | + config_file_path_str=str(config_file), |
| 861 | + path=str(repo_path), |
| 862 | + workspace_root_path=workspace_label, |
| 863 | + dry_run=False, |
| 864 | + merge_duplicates=False, |
| 865 | + ) |
| 866 | + |
| 867 | + ( |
| 868 | + _final_config, |
| 869 | + duplicate_sections, |
| 870 | + ) = DuplicateAwareConfigReader.load_with_duplicates(config_file) |
| 871 | + |
| 872 | + assert workspace_label in duplicate_sections, f"{test_id}: workspace missing" |
| 873 | + workspace_entries = duplicate_sections[workspace_label] |
| 874 | + assert len(workspace_entries) == 2, f"{test_id}: duplicate sections collapsed" |
| 875 | + |
| 876 | + combined_repos: set[str] = set() |
| 877 | + contains_new_repo = False |
| 878 | + |
| 879 | + for entry in workspace_entries: |
| 880 | + assert isinstance(entry, dict), f"{test_id}: workspace entry not dict" |
| 881 | + combined_repos.update(entry.keys()) |
| 882 | + if new_repo_name in entry: |
| 883 | + contains_new_repo = True |
| 884 | + |
| 885 | + expected_repos = set(expected_original_repos) | {new_repo_name} |
| 886 | + assert combined_repos == expected_repos, f"{test_id}: repositories mismatch" |
| 887 | + assert contains_new_repo, f"{test_id}: new repo missing from duplicate sections" |
0 commit comments