Merge branch 'codex-auto-resolve-log-conflicts'

This commit is contained in:
tiagoarroz 2026-05-06 10:30:43 +01:00
commit bf164c00ab
3 changed files with 69 additions and 21 deletions

View file

@ -10,6 +10,7 @@ export interface GitHubSyncSettings {
syncOnStartup: boolean;
syncConfigDir: boolean;
conflictHandling: "overwriteLocal" | "ask" | "overwriteRemote";
autoResolveLogConflicts: boolean;
conflictViewMode: "default" | "unified" | "split";
showStatusBarItem: boolean;
showSyncRibbonButton: boolean;
@ -29,6 +30,7 @@ export const DEFAULT_SETTINGS: GitHubSyncSettings = {
syncOnStartup: false,
syncConfigDir: false,
conflictHandling: "ask",
autoResolveLogConflicts: true,
conflictViewMode: "default",
showStatusBarItem: true,
showSyncRibbonButton: true,

View file

@ -201,6 +201,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)

View file

@ -551,37 +551,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 };
}),
);
}
}
@ -673,6 +701,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