mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 08:18:36 +00:00
fix(config): persist removed registered projects (#1925)
Fixes regression introduced in unreleased commit 8a4d116
Co-authored-by: Yu-0312 <2.66498311e+08+Yu-0312@users.noreply.github.com>
Co-authored-by: Dr. Dominik Jain <dominik.jain@oraios-ai.de>
This commit is contained in:
2 files changed
+70
-8
No files matched your search
@@ -958,6 +958,8 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
|
||||
the path to the configuration file to which updates of the configuration shall be saved;
|
||||
if None, the configuration is not saved to disk
|
||||
"""
|
||||
_projects_at_load: set[str] = field(default_factory=set, repr=False)
|
||||
"""Project roots present when this configuration instance was last persisted."""
|
||||
|
||||
# *** static members ***
|
||||
|
||||
@@ -1096,6 +1098,8 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
|
||||
)
|
||||
instance.projects.append(project)
|
||||
|
||||
instance._projects_at_load = {str(project.project_root) for project in instance.projects}
|
||||
|
||||
# determine language backend
|
||||
language_backend = get_dataclass_default(SerenaConfig, "language_backend")
|
||||
if "language_backend" in loaded_commented_yaml:
|
||||
@@ -1295,22 +1299,38 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
|
||||
self._persist_projects()
|
||||
|
||||
def _persist_projects(self) -> None:
|
||||
"""
|
||||
Persists the list of registered projects, merging it with the list currently found on disk
|
||||
(parallel agent instances may have added or removed projects in the meantime).
|
||||
"""Persist this instance's project-list changes without undoing concurrent edits.
|
||||
|
||||
The disk copy is reloaded because multiple agent processes may update the global
|
||||
configuration concurrently. The instance baseline lets us distinguish this instance's
|
||||
removals and additions from changes made by another process: unchanged baseline projects
|
||||
follow the current disk copy, while removed baseline projects are filtered out and newly
|
||||
added instance projects are appended.
|
||||
"""
|
||||
if self.config_file_path is None:
|
||||
return
|
||||
|
||||
persisted = SerenaConfig.from_config_file()
|
||||
current_projects_by_path = {str(project.project_root): project for project in self.projects}
|
||||
current_paths = set(current_projects_by_path)
|
||||
removed_paths = self._projects_at_load - current_paths
|
||||
added_paths = current_paths - self._projects_at_load
|
||||
|
||||
combined_projects = []
|
||||
handled_project_paths = set()
|
||||
for p in persisted.projects + self.projects:
|
||||
str_path = str(p.project_root)
|
||||
if str_path not in handled_project_paths:
|
||||
combined_projects.append(p)
|
||||
handled_project_paths.add(str_path)
|
||||
for project in persisted.projects:
|
||||
project_path = str(project.project_root)
|
||||
if project_path not in removed_paths:
|
||||
combined_projects.append(project)
|
||||
handled_project_paths.add(project_path)
|
||||
for project_path in added_paths:
|
||||
if project_path not in handled_project_paths:
|
||||
combined_projects.append(current_projects_by_path[project_path])
|
||||
handled_project_paths.add(project_path)
|
||||
|
||||
persisted.projects = combined_projects
|
||||
persisted._save()
|
||||
self._projects_at_load = current_paths
|
||||
|
||||
def _save(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -604,6 +604,48 @@ class TestSerenaConfigLoadSave:
|
||||
|
||||
assert len(resulting_config.projects) == 4
|
||||
|
||||
def test_remove_project_persists_across_reload(self):
|
||||
"""Removing a project must update the on-disk project registry."""
|
||||
p1 = self._make_project_dir("project1", 'project_name: "project1"\nlanguages: ["python"]\n')
|
||||
p2 = self._make_project_dir("project2", 'project_name: "project2"\nlanguages: ["python"]\n')
|
||||
self._write_master_config([p1, p2])
|
||||
|
||||
config = SerenaConfig.from_config_file(generate_if_missing=False)
|
||||
config.remove_project("project2")
|
||||
|
||||
reloaded = SerenaConfig.from_config_file(generate_if_missing=False)
|
||||
assert [project.project_config.project_name for project in reloaded.projects] == ["project1"]
|
||||
|
||||
def test_remove_project_preserves_concurrent_addition(self):
|
||||
"""A removal must not discard a project added by another config instance."""
|
||||
p1 = self._make_project_dir("project1", 'project_name: "project1"\nlanguages: ["python"]\n')
|
||||
p2 = self._make_project_dir("project2", 'project_name: "project2"\nlanguages: ["python"]\n')
|
||||
p3 = self._make_project_dir("project3", 'project_name: "project3"\nlanguages: ["python"]\n')
|
||||
self._write_master_config([p1, p2])
|
||||
|
||||
removing_config = SerenaConfig.from_config_file(generate_if_missing=False)
|
||||
adding_config = SerenaConfig.from_config_file(generate_if_missing=False)
|
||||
adding_config.add_project_from_path(p3)
|
||||
removing_config.remove_project("project2")
|
||||
|
||||
reloaded = SerenaConfig.from_config_file(generate_if_missing=False)
|
||||
assert {project.project_config.project_name for project in reloaded.projects} == {"project1", "project3"}
|
||||
|
||||
def test_add_project_preserves_concurrent_removal(self):
|
||||
"""An addition must not resurrect a project removed by another config instance."""
|
||||
p1 = self._make_project_dir("project1", 'project_name: "project1"\nlanguages: ["python"]\n')
|
||||
p2 = self._make_project_dir("project2", 'project_name: "project2"\nlanguages: ["python"]\n')
|
||||
p3 = self._make_project_dir("project3", 'project_name: "project3"\nlanguages: ["python"]\n')
|
||||
self._write_master_config([p1, p2])
|
||||
|
||||
removing_config = SerenaConfig.from_config_file(generate_if_missing=False)
|
||||
adding_config = SerenaConfig.from_config_file(generate_if_missing=False)
|
||||
removing_config.remove_project("project2")
|
||||
adding_config.add_project_from_path(p3)
|
||||
|
||||
reloaded = SerenaConfig.from_config_file(generate_if_missing=False)
|
||||
assert {project.project_config.project_name for project in reloaded.projects} == {"project1", "project3"}
|
||||
|
||||
|
||||
class TestGetRegisteredProjectWithDanglingProject:
|
||||
"""A registered project whose root directory was deleted (e.g. a removed git
|
||||
|
||||
Reference in new issue
Block a user