2.5.0 #10
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user