mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-08-27 05:47:07 +00:00
Handle multiple conflict revisions deterministically
This commit is contained in:
@@ -22,9 +22,10 @@ import type { Editor, MarkdownFileInfo, MarkdownView } from "@/deps.ts";
|
||||
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
private postponedConflictEpisodes = new Set<FilePathWithPrefix>();
|
||||
|
||||
private async isConflicted(filename: FilePathWithPrefix): Promise<boolean | undefined> {
|
||||
private async getConflictVersionCount(filename: FilePathWithPrefix): Promise<number | undefined> {
|
||||
try {
|
||||
return (await this.core.databaseFileAccess.getConflictedRevs(filename)).length > 0;
|
||||
const conflictCount = (await this.core.databaseFileAccess.getConflictedRevs(filename)).length;
|
||||
return conflictCount === 0 ? 0 : conflictCount + 1;
|
||||
} catch (error) {
|
||||
this._log(`Could not inspect the conflict state of ${filename}`, LOG_LEVEL_VERBOSE);
|
||||
this._log(error, LOG_LEVEL_VERBOSE);
|
||||
@@ -35,19 +36,26 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
private async getActiveConflictMessages(): Promise<string[]> {
|
||||
const filename = this.services.vault.getActiveFilePath();
|
||||
if (!filename) return [];
|
||||
const conflicted = await this.isConflicted(filename);
|
||||
if (conflicted === false) {
|
||||
const versionCount = await this.getConflictVersionCount(filename);
|
||||
if (versionCount === 0) {
|
||||
this.postponedConflictEpisodes.delete(filename);
|
||||
return [];
|
||||
}
|
||||
if (conflicted === true || this.postponedConflictEpisodes.has(filename)) {
|
||||
if (versionCount !== undefined && versionCount >= 3) {
|
||||
return [
|
||||
$msg("This file has ${COUNT} unresolved versions. They will be reviewed one pair at a time.", {
|
||||
COUNT: `${versionCount}`,
|
||||
}),
|
||||
];
|
||||
}
|
||||
if (versionCount === 2 || this.postponedConflictEpisodes.has(filename)) {
|
||||
return [$msg("This file has unresolved conflicts.")];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private async refreshConflictState(filename: FilePathWithPrefix): Promise<void> {
|
||||
if ((await this.isConflicted(filename)) === false) {
|
||||
if ((await this.getConflictVersionCount(filename)) === 0) {
|
||||
this.postponedConflictEpisodes.delete(filename);
|
||||
}
|
||||
eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR);
|
||||
@@ -115,8 +123,21 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
this._log(`Merge: Could not read ${filename} from the local database`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
if (!testDoc._conflicts) {
|
||||
if (!testDoc._conflicts || testDoc._conflicts.length === 0) {
|
||||
this._log(`Merge: Nothing to do ${filename}`, LOG_LEVEL_VERBOSE);
|
||||
await this.refreshConflictState(filename);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
testDoc._rev !== conflictCheckResult.left.rev ||
|
||||
!testDoc._conflicts.includes(conflictCheckResult.right.rev)
|
||||
) {
|
||||
this._log(
|
||||
`Merge: The compared revisions changed while the dialogue was open: ${filename}`,
|
||||
LOG_LEVEL_INFO
|
||||
);
|
||||
await this.refreshConflictState(filename);
|
||||
await this.services.conflict.queueCheckFor(filename);
|
||||
return false;
|
||||
}
|
||||
const toDelete = selected;
|
||||
@@ -125,7 +146,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
// Concatenate both conflicted revisions.
|
||||
// Create a new file by concatenating both conflicted revisions.
|
||||
const p = conflictCheckResult.diff.map((e) => e[1]).join("");
|
||||
const delRev = testDoc._conflicts[0];
|
||||
const delRev = conflictCheckResult.right.rev;
|
||||
if (!(await this.core.databaseFileAccess.storeContent(filename, p))) {
|
||||
this._log(`Concatenated content cannot be stored:${filename}`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
@@ -141,7 +162,10 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else if (typeof toDelete === "string") {
|
||||
} else if (
|
||||
typeof toDelete === "string" &&
|
||||
(toDelete === conflictCheckResult.left.rev || toDelete === conflictCheckResult.right.rev)
|
||||
) {
|
||||
// Select one of the conflicted revision to delete.
|
||||
if (
|
||||
(await this.services.conflict.resolveByDeletingRevision(filename, toDelete, "UI Selected")) ==
|
||||
@@ -151,7 +175,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
this._log(`Merge: Something went wrong: ${filename}, (${toDelete as string})`, LOG_LEVEL_NOTICE);
|
||||
this._log(`Merge: Something went wrong: ${filename}, (${String(toDelete)})`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
// In here, some merge has been processed.
|
||||
@@ -166,10 +190,13 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
});
|
||||
}
|
||||
async allConflictCheck() {
|
||||
while (await this.pickFileForResolve());
|
||||
let notifyIfEmpty = true;
|
||||
while (await this.pickFileForResolve(notifyIfEmpty)) {
|
||||
notifyIfEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
async pickFileForResolve() {
|
||||
async pickFileForResolve(notifyIfEmpty = true) {
|
||||
const notes: { id: DocumentID; path: FilePathWithPrefix; dispPath: string; mtime: number }[] = [];
|
||||
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
||||
if (!("_conflicts" in doc)) continue;
|
||||
@@ -183,7 +210,9 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
notes.sort((a, b) => b.mtime - a.mtime);
|
||||
const notesList = notes.map((e) => e.dispPath);
|
||||
if (notesList.length == 0) {
|
||||
this._log("There are no conflicted documents", LOG_LEVEL_NOTICE);
|
||||
if (notifyIfEmpty) {
|
||||
this._log("There are no conflicted documents", LOG_LEVEL_NOTICE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
|
||||
|
||||
Reference in New Issue
Block a user