2.5.0 #10

Merged
rune merged 41 commits from 2.5.0 into main 2026-08-03 08:33:23 +02:00
2 changed files with 28 additions and 1 deletions
Showing only changes of commit ae3de3d927 - Show all commits
+14 -1
View File
@@ -68,6 +68,11 @@ class GitSyncService {
_ = try await runGit(["clone", url, localPath])
syncStatus.isCloned = true
// Import immediately so this machine's DB is never left empty after a clone
// an empty DB is what makes the next export think every existing conversation
// was deleted (see exportAllConversations's orphan-cleanup guard).
_ = try? await importAllConversations()
await updateStatus()
}
@@ -202,7 +207,15 @@ class GitSyncService {
currentIds: Set<String>,
files: [(filename: String, markdown: String)]
) -> [String] {
files.compactMap { file in
// A locally-empty conversation list is indistinguishable here from "nothing has been
// imported into this machine's DB yet" (e.g. right after a fresh clone). Treating it as
// "every existing file was deleted" wiped a user's entire sync repo in production: clone
// completed, an auto-sync fired before the post-clone import finished, every synced
// conversation looked orphaned, and the deletion got committed and pushed. Skipping
// cleanup here means a genuine last-conversation deletion won't propagate until another
// conversation exists locally a far smaller cost than mass data loss.
guard !currentIds.isEmpty else { return [] }
return files.compactMap { file in
guard let export = try? ConversationExport.fromMarkdown(file.markdown) else { return nil }
return currentIds.contains(export.id) ? nil : file.filename
}
+14
View File
@@ -151,4 +151,18 @@ struct GitSyncServiceTests {
func emptyFileListProducesNoOrphans() {
#expect(GitSyncService.orphanedExportFilenames(currentIds: [UUID().uuidString], files: []).isEmpty)
}
@Test("Empty local conversation list never flags files as orphaned, even when files exist")
func emptyCurrentIdsNeverOrphansExistingFiles() {
// Regression test: this is the exact shape of the production bug. A fresh clone (or any
// moment where the local DB hasn't caught up yet) has zero local conversations, while the
// sync repo still has every previously-synced file on disk. Without the guard, all of them
// used to be flagged orphaned and deleted+pushed, wiping the whole sync repo.
let files = [
(filename: "a.md", markdown: makeExportMarkdown(id: UUID().uuidString)),
(filename: "b.md", markdown: makeExportMarkdown(id: UUID().uuidString)),
]
let orphans = GitSyncService.orphanedExportFilenames(currentIds: [], files: files)
#expect(orphans.isEmpty)
}
}