Skip to content

Commit b70ded9

Browse files
Zhe YuDavidyz
authored andcommitted
feat(cli): Remove orphaned files from vector database in LSP and MCP servers
1 parent 0ed0e2b commit b70ded9

5 files changed

Lines changed: 52 additions & 15 deletions

File tree

src/vectorcode/common.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import httpx
1313
from chromadb.api import AsyncClientAPI
1414
from chromadb.api.models.AsyncCollection import AsyncCollection
15+
from chromadb.api.types import IncludeEnum
1516
from chromadb.config import APIVersion, Settings
1617
from chromadb.utils import embedding_functions
1718

@@ -248,3 +249,15 @@ def verify_ef(collection: AsyncCollection, configs: Config):
248249
f"The collection was embedded with a different set of configurations: {collection_ep}. The result may be inaccurate.",
249250
)
250251
return True
252+
253+
254+
async def list_collection_files(collection: AsyncCollection) -> list[str]:
255+
return list(
256+
set(
257+
str(c.get("path", None))
258+
for c in (await collection.get(include=[IncludeEnum.metadatas])).get(
259+
"metadatas"
260+
)
261+
or []
262+
)
263+
)

src/vectorcode/lsp_main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
exclude_paths_by_spec,
1616
find_exclude_specs,
1717
load_files_from_include,
18+
remove_orphanes,
1819
)
1920

2021
try: # pragma: nocover
@@ -220,6 +221,9 @@ async def execute_command(ls: LanguageServer, args: list[str]):
220221
percentage=int(100 * i / len(tasks)),
221222
),
222223
)
224+
225+
await remove_orphanes(collection, collection_lock, stats, stats_lock)
226+
223227
ls.progress.end(
224228
progress_token,
225229
types.WorkDoneProgressEnd(

src/vectorcode/mcp_main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
chunked_add,
1818
exclude_paths_by_spec,
1919
find_exclude_specs,
20+
remove_orphanes,
2021
)
2122

2223
try: # pragma: nocover
@@ -157,6 +158,9 @@ async def vectorise_files(paths: list[str], project_root: str) -> dict[str, int]
157158
]
158159
for i, task in enumerate(asyncio.as_completed(tasks), start=1):
159160
await task
161+
162+
await remove_orphanes(collection, collection_lock, stats, stats_lock)
163+
160164
return stats.to_dict()
161165

162166

src/vectorcode/subcommands/vectorise.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@
2323
expand_globs,
2424
expand_path,
2525
)
26-
from vectorcode.common import get_client, get_collection, verify_ef
26+
from vectorcode.common import (
27+
get_client,
28+
get_collection,
29+
list_collection_files,
30+
verify_ef,
31+
)
2732

2833
logger = logging.getLogger(name=__name__)
2934

@@ -155,6 +160,25 @@ async def chunked_add(
155160
stats.add += 1
156161

157162

163+
async def remove_orphanes(
164+
collection: AsyncCollection,
165+
collection_lock: Lock,
166+
stats: VectoriseStats,
167+
stats_lock: Lock,
168+
):
169+
async with collection_lock:
170+
paths = await list_collection_files(collection)
171+
orphans = set()
172+
for path in paths:
173+
if isinstance(path, str) and not os.path.isfile(path):
174+
orphans.add(path)
175+
async with stats_lock:
176+
stats.removed = len(orphans)
177+
if len(orphans):
178+
logger.info(f"Removing {len(orphans)} orphaned files from database.")
179+
await collection.delete(where={"path": {"$in": list(orphans)}})
180+
181+
158182
def show_stats(configs: Config, stats: VectoriseStats):
159183
if configs.pipe:
160184
print(stats.to_json())
@@ -284,19 +308,7 @@ async def vectorise(configs: Config) -> int:
284308
print("Abort.", file=sys.stderr)
285309
return 1
286310

287-
async with collection_lock:
288-
all_results = await collection.get(include=[IncludeEnum.metadatas])
289-
if all_results is not None and all_results.get("metadatas"):
290-
paths = (meta["path"] for meta in (all_results["metadatas"] or []))
291-
orphans = set()
292-
for path in paths:
293-
if isinstance(path, str) and not os.path.isfile(path):
294-
orphans.add(path)
295-
async with stats_lock:
296-
stats.removed = len(orphans)
297-
if len(orphans):
298-
logger.info(f"Removing {len(orphans)} orphaned files from database.")
299-
await collection.delete(where={"path": {"$in": list(orphans)}})
311+
await remove_orphanes(collection, collection_lock, stats, stats_lock)
300312

301313
show_stats(configs=configs, stats=stats)
302314
return 0

tests/test_lsp.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ async def test_execute_command_vectorise(mock_language_server, mock_config: Conf
265265
patch(
266266
"vectorcode.lsp_main.make_caches", new_callable=AsyncMock
267267
), # Mock make_caches to avoid actual file system ops
268+
patch(
269+
"vectorcode.lsp_main.remove_orphanes", new_callable=AsyncMock
270+
) as mock_remove_orphanes,
268271
):
269272
from unittest.mock import ANY
270273

@@ -279,7 +282,7 @@ async def test_execute_command_vectorise(mock_language_server, mock_config: Conf
279282
mock_parse_cli_args.return_value = mock_config
280283
mock_client = AsyncMock()
281284
mock_get_client.return_value = mock_client
282-
mock_collection = MagicMock()
285+
mock_collection = AsyncMock()
283286
mock_get_collection.return_value = mock_collection
284287
mock_client.get_max_batch_size.return_value = 100 # Mock batch size
285288

@@ -337,6 +340,7 @@ async def test_execute_command_vectorise(mock_language_server, mock_config: Conf
337340
assert mock_language_server.progress.report.call_count == len(
338341
dummy_expanded_files
339342
)
343+
mock_remove_orphanes.assert_called_once()
340344
mock_language_server.progress.end.assert_called_once()
341345

342346

0 commit comments

Comments
 (0)