diff --git a/src/basic_memory/cli/commands/sync.py b/src/basic_memory/cli/commands/sync.py index 655cb105..0a042d86 100644 --- a/src/basic_memory/cli/commands/sync.py +++ b/src/basic_memory/cli/commands/sync.py @@ -29,7 +29,6 @@ from basic_memory.services.link_resolver import LinkResolver from basic_memory.services.search_service import SearchService from basic_memory.sync import SyncService, FileChangeScanner, EntitySyncService from basic_memory.sync.utils import SyncReport -from basic_memory.utils.file_utils import ParseError console = Console() @@ -61,10 +60,7 @@ async def get_sync_service(db_type=DatabaseType.FILESYSTEM): # Initialize services knowledge_sync_service = EntitySyncService( - entity_repository, - observation_repository, - relation_repository, - link_resolver + entity_repository, observation_repository, relation_repository, link_resolver ) entity_parser = EntityParser(config.home) @@ -193,33 +189,12 @@ def display_detailed_sync_results(knowledge: SyncReport): deleted.add(f"[red]{path}[/red]") console.print(knowledge_tree) -async def validate_knowledge_files( - sync_service: SyncService, directory: Path -) -> List[ValidationIssue]: - """Pre-validate knowledge files and collect all issues.""" - issues = [] - changes = await sync_service.scanner.find_knowledge_changes(directory) - - for file_path in [*changes.new, *changes.modified]: - try: - await sync_service.entity_parser.parse_file(directory / file_path) - except ParseError as e: - issues.append(ValidationIssue(file_path=file_path, error=str(e))) - - return issues - async def run_sync(verbose: bool = False): """Run sync operation.""" sync_service = await get_sync_service() - # Validate knowledge files before attempting sync - issues = await validate_knowledge_files(sync_service, config.home) - if issues: - display_validation_errors(issues) - raise typer.Exit(1) - # Sync knowledge_changes = await sync_service.sync(config.home) diff --git a/src/basic_memory/sync/file_change_scanner.py b/src/basic_memory/sync/file_change_scanner.py index 37ee61e5..51198816 100644 --- a/src/basic_memory/sync/file_change_scanner.py +++ b/src/basic_memory/sync/file_change_scanner.py @@ -98,28 +98,27 @@ class FileChangeScanner: report = SyncReport() # Track potentially moved files by checksum - files_by_checksum = {} # checksum -> (file_path, permalink) + files_by_checksum = {} # checksum -> file_path - # Find new and modified files + # First find potential new files and record checksums for file_path, checksum in current_files.items(): logger.debug(f"{file_path} ({checksum[:8]})") if file_path not in db_file_state: + # Could be new or could be the destination of a move report.new.add(file_path) - # Track new file's checksum for move detection files_by_checksum[checksum] = file_path elif checksum != db_file_state[file_path].checksum: report.modified.add(file_path) report.checksums[file_path] = checksum - # Find deleted and moved files + # Now detect moves and deletions for db_file_path, db_state in db_file_state.items(): if db_file_path not in current_files: - # Check if this file was moved by looking for same checksum if db_state.checksum in files_by_checksum: - new_path = files_by_checksum[db_state.checksum] # Found a move - file exists at new path with same checksum + new_path = files_by_checksum[db_state.checksum] report.moves[db_file_path] = new_path # Remove from new files since it's a move report.new.remove(new_path) diff --git a/src/basic_memory/sync/sync_service.py b/src/basic_memory/sync/sync_service.py index 5fdf9301..03337e9f 100644 --- a/src/basic_memory/sync/sync_service.py +++ b/src/basic_memory/sync/sync_service.py @@ -40,7 +40,18 @@ class SyncService: changes = await self.scanner.find_knowledge_changes(directory) logger.info(f"Found {changes.total_changes} knowledge changes") - # Handle deletions first + # Handle moves first + for old_path, new_path in changes.moves.items(): + logger.debug(f"Moving entity: {old_path} -> {new_path}") + entity = await self.entity_repository.get_by_file_path(old_path) + if entity: + # Update file_path but keep the same permalink for link stability + await self.entity_repository.update( + entity.id, + {"file_path": new_path, "checksum": changes.checksums[new_path]} + ) + + # Handle deletions next # remove rows from db for files no longer present for file_path in changes.deleted: logger.debug(f"Deleting entity from db: {file_path}")