diff --git a/commodore/component/__init__.py b/commodore/component/__init__.py index 97d91759..27838aa9 100644 --- a/commodore/component/__init__.py +++ b/commodore/component/__init__.py @@ -29,6 +29,7 @@ def clone(cls, cfg, clone_url: str, name: str, version: str = "master"): name, cdep, directory=component_dir(cfg.work_dir, name), + work_dir=cfg.work_dir, version=version, ) c.checkout() @@ -243,6 +244,25 @@ def checkout_is_dirty(self) -> bool: else: return False + def alias_checkout_is_dirty(self, alias: str) -> bool: + if alias not in self._aliases: + raise ValueError( + f"alias {alias} is not registered on component {self.name}" + ) + adep = self._aliases[alias][2] + dep_repo = adep.bare_repo + author_name = dep_repo.author.name + author_email = dep_repo.author.email + worktree = adep.get_component(alias) + + if worktree and worktree.is_dir(): + r = GitRepo( + None, worktree, author_name=author_name, author_email=author_email + ) + return r.repo.is_dirty() + else: + return False + def render_jsonnetfile_json(self, component_params): """ Render jsonnetfile.json from jsonnetfile.jsonnet diff --git a/commodore/dependency_mgmt/__init__.py b/commodore/dependency_mgmt/__init__.py index 27725919..87301f28 100644 --- a/commodore/dependency_mgmt/__init__.py +++ b/commodore/dependency_mgmt/__init__.py @@ -14,7 +14,7 @@ from .component_library import validate_component_library_name from .discovery import _discover_components, _discover_packages from .tools import format_component_list -from .version_parsing import _read_components, _read_packages +from .version_parsing import _read_components, _read_packages, DependencySpec def create_component_symlinks(cfg, component: Component): @@ -112,8 +112,16 @@ def fetch_components(cfg: Config): deps.setdefault(cdep.url, []).append(c) do_parallel(fetch_component, cfg, deps.values()) - components = cfg.get_components() + _setup_component_aliases(cfg, component_aliases, cspecs, set(deps.keys())) + +def _setup_component_aliases( + cfg: Config, + component_aliases: dict[str, str], + cspecs: dict[str, DependencySpec], + component_urls: set[str], +): + components = cfg.get_components() aliases: dict[str, list] = {} for alias, component in component_aliases.items(): if alias == component: @@ -126,7 +134,12 @@ def fetch_components(cfg: Config): if aspec.url != c.repo_url: adep = cfg.register_dependency_repo(aspec.url) c.register_alias(alias, aspec.version, adep, aspec.path) - if adep.url in deps: + if c.alias_checkout_is_dirty(alias) and not cfg.force: + raise click.ClickException( + f"Component alias {alias} has uncommitted changes. " + + "Please specify `--force` to discard them" + ) + if adep.url in component_urls: # NOTE(sg): if we already processed the dependency URL in the previous fetch # stage, we can create all instance worktrees in parallel. We do so by using # the alias name as the key for our "parallelization" dict. diff --git a/tests/test_component.py b/tests/test_component.py index 8b8c8e07..ef3b2841 100644 --- a/tests/test_component.py +++ b/tests/test_component.py @@ -475,6 +475,44 @@ def test_checkout_is_dirty(tmp_path: P, config: Config): assert not c.checkout_is_dirty() + with open(c.target_dir / "README.md", "a", encoding="utf-8") as f: + f.writelines(["", "change"]) + + assert c.checkout_is_dirty() + + +def test_alias_checkout_is_dirty(tmp_path: P, config: Config): + rem = _setup_existing_component(tmp_path, worktree=False) + clone_url = f"file://{rem.common_dir}" + + c = Component.clone(config, clone_url, "test-component") + + c.register_alias("test-alias", c.version, c.dependency) + c.checkout_alias("test-alias") + + assert not c.alias_checkout_is_dirty("test-alias") + + with open( + c.alias_directory("test-alias") / "README.md", "a", encoding="utf-8" + ) as f: + f.writelines(["", "change"]) + + assert c.alias_checkout_is_dirty("test-alias") + + +def test_component_alias_checkout_is_dirty_missing_alias(tmp_path: P, config: Config): + rem = _setup_existing_component(tmp_path, worktree=False) + clone_url = f"file://{rem.common_dir}" + + c = Component.clone(config, clone_url, "test-component") + + with pytest.raises(ValueError) as exc: + c.alias_checkout_is_dirty("test-alias") + + assert "alias test-alias is not registered on component test-component" in str( + exc.value + ) + @pytest.mark.parametrize("workdir", [True, False]) def test_component_register_alias_workdir(tmp_path: P, workdir: bool): diff --git a/tests/test_dependency_mgmt.py b/tests/test_dependency_mgmt.py index 585eeaa8..e0b5d02c 100644 --- a/tests/test_dependency_mgmt.py +++ b/tests/test_dependency_mgmt.py @@ -243,19 +243,25 @@ def test_fetch_components_with_alias_version( ).is_symlink() +@pytest.mark.parametrize("dirty_alias", [False, True]) @patch("commodore.dependency_mgmt._read_components") @patch("commodore.dependency_mgmt._discover_components") def test_fetch_components_raises( - patch_discover, patch_read, config: Config, tmp_path: Path + patch_discover, patch_read, config: Config, tmp_path: Path, dirty_alias: bool ): components = ["foo"] - patch_discover.return_value = (components, {}) + aliases = {"bar": "foo"} + patch_discover.return_value = (components, aliases) patch_read.return_value = setup_components_upstream(tmp_path, components) + patch_read.return_value["bar"] = patch_read.return_value["foo"] dependency_mgmt.fetch_components(config) + c = config.get_components()["foo"] + dirty_name = "bar" if dirty_alias else "foo" + with open( - config.get_components()["foo"].target_dir / "class" / "defaults.yml", + c.alias_directory(dirty_name) / "class" / "defaults.yml", "w", encoding="utf-8", ) as f: @@ -264,13 +270,15 @@ def test_fetch_components_raises( config._dependency_repos.clear() config._components.clear() + if dirty_alias: + msg = "Component alias bar has uncommitted changes. Please specify `--force` to discard them" + else: + msg = "Component foo has uncommitted changes. Please specify `--force` to discard them" + with pytest.raises(click.ClickException) as excinfo: dependency_mgmt.fetch_components(config) - assert ( - "Component foo has uncommitted changes. Please specify `--force` to discard them" - in str(excinfo.value) - ) + assert msg in str(excinfo.value) @patch("commodore.dependency_mgmt._read_components")