Show manual sync-conflict fix instructions in-app instead of deep-linking to Help
NSWorkspace.shared.open() silently drops #fragment anchors on file:// URLs, so "Fix It Myself" always landed on the Help Book index instead of the relevant section. Replaced with GitSyncManualFixSheet, an in-app sheet showing the real conflicting filenames and sync path. Also indent conversation rows one level deeper than their containing folder in the sidebar and conversation list, so nesting is visible on the conversations themselves and not just the folder headers.
This commit is contained in:
@@ -285,4 +285,71 @@ struct GitSyncServiceTests {
|
||||
#expect(decoded.notes[conversationId]?.filename == "Chat-a3f2.md")
|
||||
#expect(decoded.notes[conversationId]?.enabled == true)
|
||||
}
|
||||
|
||||
// MARK: - parseUntrackedFileConflict
|
||||
|
||||
@Test("Parses a single colliding file out of git's untracked-files error")
|
||||
func parseUntrackedFileConflictSingleFile() {
|
||||
let message = "error: The following untracked working tree files would be overwritten by merge:\n\tfolders.json\nPlease move or remove them before you merge.\nAborting"
|
||||
#expect(GitSyncService.parseUntrackedFileConflict(from: message) == ["folders.json"])
|
||||
}
|
||||
|
||||
@Test("Parses multiple colliding files out of git's untracked-files error")
|
||||
func parseUntrackedFileConflictMultipleFiles() {
|
||||
let message = "error: The following untracked working tree files would be overwritten by merge:\n\tfolders.json\n\tnotes.json\nPlease move or remove them before you merge.\nAborting"
|
||||
#expect(GitSyncService.parseUntrackedFileConflict(from: message) == ["folders.json", "notes.json"])
|
||||
}
|
||||
|
||||
@Test("Wrapped SyncError.gitFailed description still parses correctly")
|
||||
func parseUntrackedFileConflictWrappedMessage() {
|
||||
let message = "Git command failed: error: The following untracked working tree files would be overwritten by merge:\n\tnotes/Chat-a3f2.md\nPlease move or remove them before you merge.\nAborting"
|
||||
#expect(GitSyncService.parseUntrackedFileConflict(from: message) == ["notes/Chat-a3f2.md"])
|
||||
}
|
||||
|
||||
@Test("Unrelated git errors return nil, not an empty or bogus file list")
|
||||
func parseUntrackedFileConflictUnrelatedErrors() {
|
||||
#expect(GitSyncService.parseUntrackedFileConflict(from: "fatal: Authentication failed for 'https://gitlab.pm/rune/oai-swift.git/'") == nil)
|
||||
#expect(GitSyncService.parseUntrackedFileConflict(from: "fatal: unable to access: Could not resolve host") == nil)
|
||||
#expect(GitSyncService.parseUntrackedFileConflict(from: "error: Your local changes to the following files would be overwritten by merge:\n\tconversations/x.md") == nil)
|
||||
}
|
||||
|
||||
// MARK: - isFileSafeToAutoDelete
|
||||
|
||||
@Test("Known sync-manifest files are safe to auto-delete")
|
||||
func isFileSafeToAutoDeleteAllowsKnownFiles() {
|
||||
#expect(GitSyncService.isFileSafeToAutoDelete("folders.json"))
|
||||
#expect(GitSyncService.isFileSafeToAutoDelete("notes.json"))
|
||||
#expect(GitSyncService.isFileSafeToAutoDelete("conversations/my-chat.md"))
|
||||
#expect(GitSyncService.isFileSafeToAutoDelete("notes/Chat-a3f2.md"))
|
||||
}
|
||||
|
||||
@Test("Path traversal and absolute paths are never safe to auto-delete")
|
||||
func isFileSafeToAutoDeleteRejectsTraversal() {
|
||||
#expect(!GitSyncService.isFileSafeToAutoDelete("../etc/passwd"))
|
||||
#expect(!GitSyncService.isFileSafeToAutoDelete("/etc/passwd"))
|
||||
#expect(!GitSyncService.isFileSafeToAutoDelete("conversations/../../../etc/passwd"))
|
||||
}
|
||||
|
||||
@Test("Files outside the known shape are never safe to auto-delete")
|
||||
func isFileSafeToAutoDeleteRejectsUnknownFiles() {
|
||||
#expect(!GitSyncService.isFileSafeToAutoDelete("README.md"))
|
||||
#expect(!GitSyncService.isFileSafeToAutoDelete("conversations/sub/x.md"))
|
||||
#expect(!GitSyncService.isFileSafeToAutoDelete("notes/sub/x.md"))
|
||||
#expect(!GitSyncService.isFileSafeToAutoDelete(""))
|
||||
#expect(!GitSyncService.isFileSafeToAutoDelete("conversations/"))
|
||||
}
|
||||
|
||||
// MARK: - PendingGitConflict.canAutoFix
|
||||
|
||||
@Test("canAutoFix is true when every file is safe to auto-delete")
|
||||
func pendingGitConflictCanAutoFixAllSafe() {
|
||||
let conflict = GitSyncService.PendingGitConflict(files: ["folders.json", "notes.json"], rawError: "")
|
||||
#expect(conflict.canAutoFix)
|
||||
}
|
||||
|
||||
@Test("canAutoFix is false when any file isn't safe to auto-delete")
|
||||
func pendingGitConflictCanAutoFixOneUnsafe() {
|
||||
let conflict = GitSyncService.PendingGitConflict(files: ["folders.json", "README.md"], rawError: "")
|
||||
#expect(!conflict.canAutoFix)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user