mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-14 21:32:31 +00:00
Fixed: Hidden file JSON conflicts no longer keep re-opening and dismissing the merge dialogue before we can act, which fixes persistent unresolvable data.json conflicts in plug-in settings sync (related: #850).
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -30,7 +30,8 @@
|
|||||||
type JSONData = Record<string | number | symbol, any> | [any];
|
type JSONData = Record<string | number | symbol, any> | [any];
|
||||||
|
|
||||||
const docsArray = $derived.by(() => {
|
const docsArray = $derived.by(() => {
|
||||||
if (docs && docs.length >= 1) {
|
// The merge pane compares two revisions, so guard against incomplete input before reading docs[1].
|
||||||
|
if (docs && docs.length >= 2) {
|
||||||
if (keepOrder || docs[0].mtime < docs[1].mtime) {
|
if (keepOrder || docs[0].mtime < docs[1].mtime) {
|
||||||
return { a: docs[0], b: docs[1] } as const;
|
return { a: docs[0], b: docs[1] } as const;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -636,10 +636,24 @@ Offline Changed files: ${processFiles.length}`;
|
|||||||
|
|
||||||
// --> Conflict processing
|
// --> Conflict processing
|
||||||
|
|
||||||
|
// Keep one in-flight conflict check per path so repeated sync events do not close the active merge dialogue.
|
||||||
|
pendingConflictChecks = new Set<FilePathWithPrefix>();
|
||||||
|
|
||||||
queueConflictCheck(path: FilePathWithPrefix) {
|
queueConflictCheck(path: FilePathWithPrefix) {
|
||||||
|
if (this.pendingConflictChecks.has(path)) return;
|
||||||
|
this.pendingConflictChecks.add(path);
|
||||||
this.conflictResolutionProcessor.enqueue(path);
|
this.conflictResolutionProcessor.enqueue(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finishConflictCheck(path: FilePathWithPrefix) {
|
||||||
|
this.pendingConflictChecks.delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
requeueConflictCheck(path: FilePathWithPrefix) {
|
||||||
|
this.finishConflictCheck(path);
|
||||||
|
this.queueConflictCheck(path);
|
||||||
|
}
|
||||||
|
|
||||||
async resolveConflictOnInternalFiles() {
|
async resolveConflictOnInternalFiles() {
|
||||||
// Scan all conflicted internal files
|
// Scan all conflicted internal files
|
||||||
const conflicted = this.localDatabase.findEntries(ICHeader, ICHeaderEnd, { conflicts: true });
|
const conflicted = this.localDatabase.findEntries(ICHeader, ICHeaderEnd, { conflicts: true });
|
||||||
@@ -648,7 +662,7 @@ Offline Changed files: ${processFiles.length}`;
|
|||||||
for await (const doc of conflicted) {
|
for await (const doc of conflicted) {
|
||||||
if (!("_conflicts" in doc)) continue;
|
if (!("_conflicts" in doc)) continue;
|
||||||
if (isInternalMetadata(doc._id)) {
|
if (isInternalMetadata(doc._id)) {
|
||||||
this.conflictResolutionProcessor.enqueue(doc.path);
|
this.queueConflictCheck(doc.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -679,21 +693,27 @@ Offline Changed files: ${processFiles.length}`;
|
|||||||
const cc = await this.localDatabase.getRaw(id, { conflicts: true });
|
const cc = await this.localDatabase.getRaw(id, { conflicts: true });
|
||||||
if (cc._conflicts?.length === 0) {
|
if (cc._conflicts?.length === 0) {
|
||||||
await this.extractInternalFileFromDatabase(stripAllPrefixes(path));
|
await this.extractInternalFileFromDatabase(stripAllPrefixes(path));
|
||||||
|
this.finishConflictCheck(path);
|
||||||
} else {
|
} else {
|
||||||
this.conflictResolutionProcessor.enqueue(path);
|
this.requeueConflictCheck(path);
|
||||||
}
|
}
|
||||||
// check the file again
|
// check the file again
|
||||||
}
|
}
|
||||||
conflictResolutionProcessor = new QueueProcessor(
|
conflictResolutionProcessor = new QueueProcessor(
|
||||||
async (paths: FilePathWithPrefix[]) => {
|
async (paths: FilePathWithPrefix[]) => {
|
||||||
const path = paths[0];
|
const path = paths[0];
|
||||||
sendSignal(`cancel-internal-conflict:${path}`);
|
|
||||||
try {
|
try {
|
||||||
// Retrieve data
|
// Retrieve data
|
||||||
const id = await this.path2id(path, ICHeader);
|
const id = await this.path2id(path, ICHeader);
|
||||||
const doc = await this.localDatabase.getRaw<MetaEntry>(id, { conflicts: true });
|
const doc = await this.localDatabase.getRaw<MetaEntry>(id, { conflicts: true });
|
||||||
if (doc._conflicts === undefined) return [];
|
if (doc._conflicts === undefined) {
|
||||||
if (doc._conflicts.length == 0) return [];
|
this.finishConflictCheck(path);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (doc._conflicts.length == 0) {
|
||||||
|
this.finishConflictCheck(path);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
this._log(`Hidden file conflicted:${path}`);
|
this._log(`Hidden file conflicted:${path}`);
|
||||||
const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
|
const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
|
||||||
const revA = doc._rev;
|
const revA = doc._rev;
|
||||||
@@ -725,7 +745,7 @@ Offline Changed files: ${processFiles.length}`;
|
|||||||
await this.storeInternalFileToDatabase({ path: filename, ...stat });
|
await this.storeInternalFileToDatabase({ path: filename, ...stat });
|
||||||
await this.extractInternalFileFromDatabase(filename);
|
await this.extractInternalFileFromDatabase(filename);
|
||||||
await this.localDatabase.removeRevision(id, revB);
|
await this.localDatabase.removeRevision(id, revB);
|
||||||
this.conflictResolutionProcessor.enqueue(path);
|
this.requeueConflictCheck(path);
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
this._log(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE);
|
this._log(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -743,6 +763,7 @@ Offline Changed files: ${processFiles.length}`;
|
|||||||
await this.resolveByNewerEntry(id, path, doc, revA, revB);
|
await this.resolveByNewerEntry(id, path, doc, revA, revB);
|
||||||
return [];
|
return [];
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
this.finishConflictCheck(path);
|
||||||
this._log(`Failed to resolve conflict (Hidden): ${path}`);
|
this._log(`Failed to resolve conflict (Hidden): ${path}`);
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return [];
|
return [];
|
||||||
@@ -761,15 +782,22 @@ Offline Changed files: ${processFiles.length}`;
|
|||||||
const prefixedPath = addPrefix(path, ICHeader);
|
const prefixedPath = addPrefix(path, ICHeader);
|
||||||
const docAMerge = await this.localDatabase.getDBEntry(prefixedPath, { rev: revA });
|
const docAMerge = await this.localDatabase.getDBEntry(prefixedPath, { rev: revA });
|
||||||
const docBMerge = await this.localDatabase.getDBEntry(prefixedPath, { rev: revB });
|
const docBMerge = await this.localDatabase.getDBEntry(prefixedPath, { rev: revB });
|
||||||
if (docAMerge != false && docBMerge != false) {
|
try {
|
||||||
if (await this.showJSONMergeDialogAndMerge(docAMerge, docBMerge)) {
|
if (docAMerge != false && docBMerge != false) {
|
||||||
// Again for other conflicted revisions.
|
if (await this.showJSONMergeDialogAndMerge(docAMerge, docBMerge)) {
|
||||||
this.conflictResolutionProcessor.enqueue(path);
|
// Again for other conflicted revisions.
|
||||||
|
this.requeueConflictCheck(path);
|
||||||
|
} else {
|
||||||
|
this.finishConflictCheck(path);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// If either revision could not read, force resolving by the newer one.
|
||||||
|
await this.resolveByNewerEntry(id, path, doc, revA, revB);
|
||||||
}
|
}
|
||||||
return;
|
} catch (ex) {
|
||||||
} else {
|
this.finishConflictCheck(path);
|
||||||
// If either revision could not read, force resolving by the newer one.
|
throw ex;
|
||||||
await this.resolveByNewerEntry(id, path, doc, revA, revB);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -793,6 +821,8 @@ Offline Changed files: ${processFiles.length}`;
|
|||||||
const storeFilePath = strippedPath;
|
const storeFilePath = strippedPath;
|
||||||
const displayFilename = `${storeFilePath}`;
|
const displayFilename = `${storeFilePath}`;
|
||||||
// const path = this.prefixedConfigDir2configDir(stripAllPrefixes(docA.path)) || docA.path;
|
// const path = this.prefixedConfigDir2configDir(stripAllPrefixes(docA.path)) || docA.path;
|
||||||
|
// Cancel only when replacing an existing dialogue for the same path, not on every queue pass.
|
||||||
|
sendSignal(`cancel-internal-conflict:${docA.path}`);
|
||||||
const modal = new JsonResolveModal(this.app, storageFilePath, [docA, docB], async (keep, result) => {
|
const modal = new JsonResolveModal(this.app, storageFilePath, [docA, docB], async (keep, result) => {
|
||||||
// modal.close();
|
// modal.close();
|
||||||
try {
|
try {
|
||||||
@@ -1164,7 +1194,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
// Check if the file is conflicted, and if so, enqueue to resolve.
|
// Check if the file is conflicted, and if so, enqueue to resolve.
|
||||||
// Until the conflict is resolved, the file will not be processed.
|
// Until the conflict is resolved, the file will not be processed.
|
||||||
if (docMeta._conflicts && docMeta._conflicts.length > 0) {
|
if (docMeta._conflicts && docMeta._conflicts.length > 0) {
|
||||||
this.conflictResolutionProcessor.enqueue(path);
|
this.queueConflictCheck(path);
|
||||||
this._log(`${headerLine} Hidden file conflicted, enqueued to resolve`);
|
this._log(`${headerLine} Hidden file conflicted, enqueued to resolve`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid
|
|||||||
- Improved background worker crash cleanup so pending split/encryption tasks are released cleanly instead of being left in a waiting state (related: #855).
|
- Improved background worker crash cleanup so pending split/encryption tasks are released cleanly instead of being left in a waiting state (related: #855).
|
||||||
- On start-up, the selected remote configuration is now applied to runtime connection fields as well, reducing intermittent authentication failures caused by stale runtime settings (related: #855).
|
- On start-up, the selected remote configuration is now applied to runtime connection fields as well, reducing intermittent authentication failures caused by stale runtime settings (related: #855).
|
||||||
- Issue report generation now redacts `remoteConfigurations` connection strings and keeps only the scheme (e.g. `sls+https://`), so credentials are not exposed in reports.
|
- Issue report generation now redacts `remoteConfigurations` connection strings and keeps only the scheme (e.g. `sls+https://`), so credentials are not exposed in reports.
|
||||||
|
- Hidden file JSON conflicts no longer keep re-opening and dismissing the merge dialogue before we can act, which fixes persistent unresolvable `data.json` conflicts in plug-in settings sync (related: #850).
|
||||||
|
|
||||||
## 0.25.57
|
## 0.25.57
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user