diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 97baf09..0a3e637 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -9,6 +9,7 @@ export interface GitHubSyncSettings { syncOnStartup: boolean; syncConfigDir: boolean; conflictHandling: "overwriteLocal" | "ask" | "overwriteRemote"; + autoResolveLogConflicts: boolean; conflictViewMode: "default" | "unified" | "split"; showStatusBarItem: boolean; showSyncRibbonButton: boolean; @@ -27,6 +28,7 @@ export const DEFAULT_SETTINGS: GitHubSyncSettings = { syncOnStartup: false, syncConfigDir: false, conflictHandling: "ask", + autoResolveLogConflicts: true, conflictViewMode: "default", showStatusBarItem: true, showSyncRibbonButton: true, diff --git a/src/settings/tab.ts b/src/settings/tab.ts index 8921460..cca35fc 100644 --- a/src/settings/tab.ts +++ b/src/settings/tab.ts @@ -189,6 +189,20 @@ export default class GitHubSyncSettingsTab extends PluginSettingTab { }); }); + new Setting(containerEl) + .setName("Automatically resolve conflicts in logs") + .setDesc( + "Automatically resolves conflicts in this plugin's logs prioritizing the local file", + ) + .addToggle((toggle) => { + toggle + .setValue(this.plugin.settings.autoResolveLogConflicts) + .onChange(async (value) => { + this.plugin.settings.autoResolveLogConflicts = value; + await this.plugin.saveSettings(); + }); + }); + new Setting(containerEl).setName("Interface").setHeading(); new Setting(containerEl) diff --git a/src/sync-manager.ts b/src/sync-manager.ts index da11a5a..b8ac9bc 100644 --- a/src/sync-manager.ts +++ b/src/sync-manager.ts @@ -452,37 +452,65 @@ export default class SyncManager { // commit the sync. let conflictResolutions: ConflictResolution[] = []; - if (conflicts.length > 0) { - await this.logger.warn("Found conflicts", conflicts); + const autoResolvableLogConflicts = this.settings.autoResolveLogConflicts + ? conflicts.filter((conflict) => this.isLogFile(conflict.filePath)) + : []; + + const logConflictResolutions = autoResolvableLogConflicts + .map((conflict) => ({ + filePath: conflict.filePath, + content: conflict.localContent, + })); + if (logConflictResolutions.length > 0) { + // Logs are specific to each vault and can change on every run, + // so these conflicts are always resolved by keeping local content. + await this.logger.info( + "Automatically resolved log conflicts", + logConflictResolutions.map((resolution) => resolution.filePath), + ); + conflictResolutions.push(...logConflictResolutions); + conflictActions.push( + ...logConflictResolutions.map((resolution: ConflictResolution) => { + return { type: "upload" as const, filePath: resolution.filePath }; + }), + ); + } + + const remainingConflicts = conflicts.filter( + (conflict) => + !this.settings.autoResolveLogConflicts || + !this.isLogFile(conflict.filePath), + ); + + if (remainingConflicts.length > 0) { + await this.logger.warn("Found conflicts", remainingConflicts); if (this.settings.conflictHandling === "ask") { // Here we block the sync process until the user has resolved all the conflicts - conflictResolutions = await this.onConflicts(conflicts); - conflictActions = conflictResolutions.map( - (resolution: ConflictResolution) => { - return { type: "upload", filePath: resolution.filePath }; - }, + const manualConflictResolutions = + await this.onConflicts(remainingConflicts); + conflictResolutions.push(...manualConflictResolutions); + conflictActions.push( + ...manualConflictResolutions.map( + (resolution: ConflictResolution) => { + return { type: "upload" as const, filePath: resolution.filePath }; + }, + ), ); } else if (this.settings.conflictHandling === "overwriteLocal") { // The user explicitly wants to always overwrite the local file // in case of conflicts so we just download the remote file to solve it - - // It's not necessary to set conflict resolutions as the content the - // user expect must be the content of the remote file with no changes. - conflictActions = conflictResolutions.map( - (resolution: ConflictResolution) => { - return { type: "download", filePath: resolution.filePath }; - }, + conflictActions.push( + ...remainingConflicts.map((conflict: ConflictFile) => { + return { type: "download" as const, filePath: conflict.filePath }; + }), ); } else if (this.settings.conflictHandling === "overwriteRemote") { // The user explicitly wants to always overwrite the remote file // in case of conflicts so we just upload the remote file to solve it. - - // It's not necessary to set conflict resolutions as the content the - // user expect must be the content of the local file with no changes. - conflictActions = conflictResolutions.map( - (resolution: ConflictResolution) => { - return { type: "upload", filePath: resolution.filePath }; - }, + conflictActions.push( + ...remainingConflicts.map((conflict: ConflictFile) => { + return { type: "upload" as const, filePath: conflict.filePath }; + }), ); } } @@ -574,6 +602,10 @@ export default class SyncManager { await this.commitSync(newTreeFiles, treeSha, conflictResolutions); } + private isLogFile(filePath: string): boolean { + return filePath === `${this.vault.configDir}/${LOG_FILE_NAME}`; + } + /** * Finds conflicts between local and remote files. * @param filesMetadata Remote files metadata