diff --git a/custom_components/hacs/base.py b/custom_components/hacs/base.py index 7580b0f0f56..158a1047fa5 100644 --- a/custom_components/hacs/base.py +++ b/custom_components/hacs/base.py @@ -235,9 +235,7 @@ def register(self, repository: HacsRepository, default: bool = False) -> None: if registered_repo.data.full_name == repository.data.full_name: return - self.unregister(registered_repo) - - registered_repo.data.full_name = repository.data.full_name + self.rename(registered_repo, repository.data.full_name) registered_repo.data.new = False repository = registered_repo @@ -269,6 +267,14 @@ def unregister(self, repository: HacsRepository) -> None: self._repositories_by_id.pop(repo_id, None) self._repositories_by_full_name.pop(repository.data.full_name_lower, None) + def rename(self, repository: HacsRepository, new_full_name: str) -> None: + """Rename a repository, keeping the name index in sync.""" + self._repositories_by_full_name.pop(repository.data.full_name_lower, None) + repository.data.full_name = new_full_name + + if repository in self._repositories: + self._repositories_by_full_name[repository.data.full_name_lower] = repository + def mark_default(self, repository: HacsRepository) -> None: """Mark a repository as default.""" repo_id = str(repository.data.id) diff --git a/custom_components/hacs/repositories/appdaemon.py b/custom_components/hacs/repositories/appdaemon.py index f271a39ef0f..64f95c64e60 100644 --- a/custom_components/hacs/repositories/appdaemon.py +++ b/custom_components/hacs/repositories/appdaemon.py @@ -22,7 +22,6 @@ def __init__(self, hacs: HacsBase, full_name: str): """Initialize.""" super().__init__(hacs=hacs) self.data.full_name = full_name - self.data.full_name_lower = full_name.lower() self.data.category = HacsCategory.APPDAEMON self.content.path.local = self.localpath self.content.path.remote = "apps" diff --git a/custom_components/hacs/repositories/base.py b/custom_components/hacs/repositories/base.py index 43fa1967501..dba2da2dd9e 100644 --- a/custom_components/hacs/repositories/base.py +++ b/custom_components/hacs/repositories/base.py @@ -184,6 +184,15 @@ def name(self): return self.domain return self.full_name.split("/")[-1] + @property + def full_name_lower(self) -> str: + """Return the lowercase full name. + + Derived from full_name so it can never go stale when + a repository is renamed. + """ + return self.full_name.lower() + def to_json(self): """Export to json.""" return attr.asdict(self, filter=lambda attr, value: attr.name != "last_fetched") @@ -523,7 +532,9 @@ async def common_update(self, ignore_issues=False, force=False, skip_releases=Fa skip_releases=skip_releases, ) except HacsRepositoryExistException: - self.data.full_name = self.hacs.common.renamed_repositories[self.data.full_name] + self.hacs.repositories.rename( + self, self.hacs.common.renamed_repositories[self.data.full_name] + ) await self.common_update_data(ignore_issues=ignore_issues, force=force) except HacsException: diff --git a/custom_components/hacs/repositories/integration.py b/custom_components/hacs/repositories/integration.py index d3ed9ca89f1..84a3515b03c 100644 --- a/custom_components/hacs/repositories/integration.py +++ b/custom_components/hacs/repositories/integration.py @@ -27,7 +27,6 @@ def __init__(self, hacs: HacsBase, full_name: str): """Initialize.""" super().__init__(hacs=hacs) self.data.full_name = full_name - self.data.full_name_lower = full_name.lower() self.data.category = HacsCategory.INTEGRATION self.content.path.remote = "custom_components" self.content.path.local = self.localpath diff --git a/custom_components/hacs/repositories/plugin.py b/custom_components/hacs/repositories/plugin.py index e7830348aa0..312e5ba87ba 100644 --- a/custom_components/hacs/repositories/plugin.py +++ b/custom_components/hacs/repositories/plugin.py @@ -26,7 +26,6 @@ def __init__(self, hacs: HacsBase, full_name: str): """Initialize.""" super().__init__(hacs=hacs) self.data.full_name = full_name - self.data.full_name_lower = full_name.lower() self.data.file_name = None self.data.category = HacsCategory.PLUGIN self.content.path.local = self.localpath diff --git a/custom_components/hacs/repositories/python_script.py b/custom_components/hacs/repositories/python_script.py index f634e6a7de2..8370c0b70f2 100644 --- a/custom_components/hacs/repositories/python_script.py +++ b/custom_components/hacs/repositories/python_script.py @@ -22,7 +22,6 @@ def __init__(self, hacs: HacsBase, full_name: str): """Initialize.""" super().__init__(hacs=hacs) self.data.full_name = full_name - self.data.full_name_lower = full_name.lower() self.data.category = HacsCategory.PYTHON_SCRIPT self.content.path.remote = "python_scripts" self.content.path.local = self.localpath diff --git a/custom_components/hacs/repositories/template.py b/custom_components/hacs/repositories/template.py index 7b5c19cbbe1..f8ca3c25ae1 100644 --- a/custom_components/hacs/repositories/template.py +++ b/custom_components/hacs/repositories/template.py @@ -22,7 +22,6 @@ def __init__(self, hacs: HacsBase, full_name: str): """Initialize.""" super().__init__(hacs=hacs) self.data.full_name = full_name - self.data.full_name_lower = full_name.lower() self.data.category = HacsCategory.TEMPLATE self.content.path.remote = "" self.content.path.local = self.localpath diff --git a/custom_components/hacs/repositories/theme.py b/custom_components/hacs/repositories/theme.py index 89294f85873..aabfde48c06 100644 --- a/custom_components/hacs/repositories/theme.py +++ b/custom_components/hacs/repositories/theme.py @@ -22,7 +22,6 @@ def __init__(self, hacs: HacsBase, full_name: str): """Initialize.""" super().__init__(hacs=hacs) self.data.full_name = full_name - self.data.full_name_lower = full_name.lower() self.data.category = HacsCategory.THEME self.content.path.remote = "themes" self.content.path.local = self.localpath diff --git a/tests/common.py b/tests/common.py index f7f193374c8..39582543d8d 100644 --- a/tests/common.py +++ b/tests/common.py @@ -212,7 +212,6 @@ def dummy_repository_base(hacs, repository=None): if repository is None: repository = HacsRepository(hacs) repository.data.full_name = "test/test" - repository.data.full_name_lower = "test/test" repository.hacs = hacs repository.hacs.hass = hacs.hass repository.hacs.core.config_path = hacs.hass.config.path() diff --git a/tests/hacsbase/test_hacs.py b/tests/hacsbase/test_hacs.py index aae7d18be1b..e24ee762ea8 100644 --- a/tests/hacsbase/test_hacs.py +++ b/tests/hacsbase/test_hacs.py @@ -3,6 +3,7 @@ from custom_components.hacs.base import HacsRepositories from custom_components.hacs.enums import HacsCategory +from custom_components.hacs.repositories.base import HacsRepository async def test_hacs(hacs, repository, tmpdir): @@ -57,3 +58,46 @@ async def test_add_remove_repository(hacs, repository, tmpdir): # Verify second removal does not raise hacs.repositories.unregister(repository) + + +async def test_register_renamed_repository(hacs, repository, tmpdir): + """Registering a known id under a new name must update the name index.""" + hacs.hass.config.config_dir = tmpdir + + hacs.repositories = HacsRepositories() + + repository.data.id = "1337" + hacs.repositories.register(repository, True) + + renamed = HacsRepository(hacs) + renamed.data.id = "1337" + renamed.data.full_name = "test/renamed" + + hacs.repositories.register(renamed) + + assert repository.data.full_name == "test/renamed" + assert repository.data.full_name_lower == "test/renamed" + assert hacs.repositories.get_by_full_name("test/renamed") is repository + assert hacs.repositories.get_by_full_name("test/test") is None + assert hacs.repositories.get_by_id("1337") is repository + + # The repository keeps its default status through the rename + assert hacs.repositories.is_default("1337") + + +async def test_rename_repository(hacs, repository, tmpdir): + """Renaming a repository keeps the name index in sync.""" + hacs.hass.config.config_dir = tmpdir + + hacs.repositories = HacsRepositories() + + repository.data.id = "1337" + hacs.repositories.register(repository) + + hacs.repositories.rename(repository, "Test/Renamed") + + assert repository.data.full_name == "Test/Renamed" + assert repository.data.full_name_lower == "test/renamed" + assert hacs.repositories.get_by_full_name("Test/Renamed") is repository + assert hacs.repositories.get_by_full_name("test/renamed") is repository + assert hacs.repositories.get_by_full_name("test/test") is None diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacstest-register-renamed-repository.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacstest-register-renamed-repository.json new file mode 100644 index 00000000000..0de0b5a7c0f --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacstest-register-renamed-repository.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacs.py::test_register_renamed_repository": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacstest-rename-repository.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacstest-rename-repository.json new file mode 100644 index 00000000000..85b3aaadf28 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacstest-rename-repository.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacs.py::test_rename_repository": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file