fix: scope merge notice suppression to bulk resolution

Reason:
- Bulk newest resolution should not display one success notice per file, while non-bulk resolution must retain its existing notice.

Changes:
- Pass an explicit notice flag only through the bulk resolution path.
- Cover bulk suppression, non-bulk notices, and ten-file progress updates.
This commit is contained in:
Ouyang Xingyuan
2026-07-16 10:02:01 +08:00
committed by vorotamoroz
parent 3103dc4f52
commit 32e992db93
2 changed files with 46 additions and 11 deletions
@@ -30,7 +30,8 @@ export class ModuleConflictResolver extends AbstractModule {
private async _resolveConflictByDeletingRev( private async _resolveConflictByDeletingRev(
path: FilePathWithPrefix, path: FilePathWithPrefix,
deleteRevision: string, deleteRevision: string,
subTitle = "" subTitle = "",
showNotice = true
): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> { ): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
const title = `Resolving ${subTitle ? `[${subTitle}]` : ""}:`; const title = `Resolving ${subTitle ? `[${subTitle}]` : ""}:`;
if (!(await this.core.fileHandler.deleteRevisionFromDB(path, deleteRevision))) { if (!(await this.core.fileHandler.deleteRevisionFromDB(path, deleteRevision))) {
@@ -58,7 +59,7 @@ export class ModuleConflictResolver extends AbstractModule {
this._log(`Could not write the resolved content to the storage: ${path}`, LOG_LEVEL_NOTICE); this._log(`Could not write the resolved content to the storage: ${path}`, LOG_LEVEL_NOTICE);
return MISSING_OR_ERROR; return MISSING_OR_ERROR;
} }
const level = subTitle.indexOf("same") !== -1 || subTitle === "NEWEST" ? LOG_LEVEL_INFO : LOG_LEVEL_NOTICE; const level = subTitle.indexOf("same") !== -1 || !showNotice ? LOG_LEVEL_INFO : LOG_LEVEL_NOTICE;
this._log(`${path} has been merged automatically`, level); this._log(`${path} has been merged automatically`, level);
return AUTO_MERGED; return AUTO_MERGED;
} }
@@ -165,7 +166,7 @@ export class ModuleConflictResolver extends AbstractModule {
}); });
} }
private async _anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> { private async _anyResolveConflictByNewest(filename: FilePathWithPrefix, showNotice = true): Promise<boolean> {
const currentRev = await this.core.databaseFileAccess.fetchEntryMeta(filename, undefined, true); const currentRev = await this.core.databaseFileAccess.fetchEntryMeta(filename, undefined, true);
if (currentRev == false) { if (currentRev == false) {
this._log(`Could not get current revision of ${filename}`); this._log(`Could not get current revision of ${filename}`);
@@ -203,7 +204,7 @@ export class ModuleConflictResolver extends AbstractModule {
this._log( this._log(
`conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}` `conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}`
); );
await this.services.conflict.resolveByDeletingRevision(filename, mTimeAndRev[i][1], "NEWEST"); await this._resolveConflictByDeletingRev(filename, mTimeAndRev[i][1], "NEWEST", showNotice);
} }
return true; return true;
} }
@@ -221,7 +222,7 @@ export class ModuleConflictResolver extends AbstractModule {
LOG_LEVEL_NOTICE, LOG_LEVEL_NOTICE,
"resolveAllConflictedFilesByNewerOnes" "resolveAllConflictedFilesByNewerOnes"
); );
await this.services.conflict.resolveByNewest(file); await this._anyResolveConflictByNewest(file, false);
} }
this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes"); this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
} }
@@ -1,9 +1,14 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "@lib/common/types"; import {
DEFAULT_SETTINGS,
LOG_LEVEL_INFO,
LOG_LEVEL_NOTICE,
type FilePathWithPrefix,
type MetaEntry,
} from "@lib/common/types";
import { ModuleConflictResolver } from "./ModuleConflictResolver"; import { ModuleConflictResolver } from "./ModuleConflictResolver";
function createModule(files: FilePathWithPrefix[] = []) { function createModule(files: FilePathWithPrefix[] = []) {
const resolveByNewest = vi.fn(async () => true);
const core = { const core = {
_services: { _services: {
API: { API: {
@@ -17,7 +22,7 @@ function createModule(files: FilePathWithPrefix[] = []) {
saveSettingData: vi.fn(async () => undefined), saveSettingData: vi.fn(async () => undefined),
}, },
conflict: { conflict: {
resolveByNewest, resolveByNewest: vi.fn(async () => true),
}, },
}, },
settings: DEFAULT_SETTINGS, settings: DEFAULT_SETTINGS,
@@ -36,26 +41,55 @@ function createModule(files: FilePathWithPrefix[] = []) {
const module = new ModuleConflictResolver(core); const module = new ModuleConflictResolver(core);
module._log = vi.fn(); module._log = vi.fn();
return { module, resolveByNewest }; return { module };
} }
describe("ModuleConflictResolver bulk newest resolution", () => { describe("ModuleConflictResolver bulk newest resolution", () => {
it("logs each successful newest resolution without displaying a notice", async () => { it("retains the success notice for a non-bulk newest resolution", async () => {
const { module } = createModule(); const { module } = createModule();
const path = "example.md" as FilePathWithPrefix; const path = "example.md" as FilePathWithPrefix;
await (module as any)._resolveConflictByDeletingRev(path, "2-old", "NEWEST"); await (module as any)._resolveConflictByDeletingRev(path, "2-old", "NEWEST");
expect(module._log).toHaveBeenLastCalledWith(`${path} has been merged automatically`, LOG_LEVEL_NOTICE);
});
it("logs a successful bulk newest resolution without displaying a notice", async () => {
const { module } = createModule();
const path = "example.md" as FilePathWithPrefix;
module.core.databaseFileAccess.fetchEntryMeta = vi.fn(
async (_path: unknown, rev?: string): Promise<MetaEntry> =>
({
_id: "doc-id",
_rev: rev ?? "2-current",
path,
ctime: 1,
mtime: rev ? 1 : 2,
size: 0,
children: [],
type: "plain",
eden: {},
}) as unknown as MetaEntry
);
module.core.databaseFileAccess.getConflictedRevs = vi
.fn()
.mockResolvedValueOnce(["1-old"])
.mockResolvedValue([]);
await (module as any)._anyResolveConflictByNewest(path, false);
expect(module._log).toHaveBeenLastCalledWith(`${path} has been merged automatically`, LOG_LEVEL_INFO); expect(module._log).toHaveBeenLastCalledWith(`${path} has been merged automatically`, LOG_LEVEL_INFO);
}); });
it("updates notice-level progress once every ten checked files", async () => { it("updates notice-level progress once every ten checked files", async () => {
const files = Array.from({ length: 11 }, (_, index) => `note-${index}.md` as FilePathWithPrefix); const files = Array.from({ length: 11 }, (_, index) => `note-${index}.md` as FilePathWithPrefix);
const { module, resolveByNewest } = createModule(files); const { module } = createModule(files);
const resolveByNewest = vi.spyOn(module as any, "_anyResolveConflictByNewest").mockResolvedValue(true);
await (module as any)._resolveAllConflictedFilesByNewerOnes(); await (module as any)._resolveAllConflictedFilesByNewerOnes();
expect(resolveByNewest).toHaveBeenCalledTimes(11); expect(resolveByNewest).toHaveBeenCalledTimes(11);
expect(resolveByNewest).toHaveBeenCalledWith(files[0], false);
expect(module._log).toHaveBeenCalledWith( expect(module._log).toHaveBeenCalledWith(
"Check and Processing 10 / 11", "Check and Processing 10 / 11",
LOG_LEVEL_NOTICE, LOG_LEVEL_NOTICE,