Skip to content

Commit ac1fb68

Browse files
feat: add conflict resolution strategies to settings
- Revert the "force pull" code and terminology - Add conflict resolution strategies to settings
1 parent 9cb11b6 commit ac1fb68

File tree

9 files changed

+74
-36
lines changed

9 files changed

+74
-36
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"svelte-preprocess": "^6.0.3",
4545
"tslib": "^2.8.1",
4646
"typescript": "5.8.3",
47-
"typescript-eslint": "^8.38.0"
47+
"typescript-eslint": "^8.8.1"
4848
},
4949
"dependencies": {
5050
"@codemirror/commands": "^6.8.1",
@@ -59,6 +59,7 @@
5959
"deep-equal": "^2.2.3",
6060
"diff": "^7.0.0",
6161
"diff2html": "^3.4.52",
62+
"diff3": "^0.0.4",
6263
"isomorphic-git": "^1.32.2",
6364
"js-sha256": "^0.9.0",
6465
"obsidian-community-lib": "github:Vinzent03/obsidian-community-lib#upgrade",

src/commands.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,6 @@ export function addCommmands(plugin: ObsidianGit) {
122122
plugin.promiseQueue.addTask(() => plugin.pullChangesFromRemote()),
123123
});
124124

125-
plugin.addCommand({
126-
id: "force-pull",
127-
name: "Force pull",
128-
callback: () =>
129-
plugin.promiseQueue.addTask(() => plugin.pullChangesFromRemote(true)),
130-
});
131-
132125
plugin.addCommand({
133126
id: "fetch",
134127
name: "Fetch",

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const DEFAULT_SETTINGS: ObsidianGitSettings = {
2727
showStatusBar: true,
2828
updateSubmodules: false,
2929
syncMethod: "merge",
30+
resolutionMethod: "none",
3031
customMessageOnAutoBackup: false,
3132
autoBackupAfterFileChange: false,
3233
treeStructure: false,

src/gitManager/gitManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
DiffFile,
66
FileStatusResult,
77
LogEntry,
8+
ResolutionMethod,
89
Status,
910
TreeItem,
1011
UnstagedFile,

src/gitManager/isomorphicGit.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { GeneralModal } from "../ui/modals/generalModal";
2525
import { splitRemoteBranch, worthWalking } from "../utils";
2626
import { GitManager } from "./gitManager";
2727
import { MyAdapter } from "./myAdapter";
28+
import diff3Merge from "diff3";
2829

2930
export class IsomorphicGit extends GitManager {
3031
private readonly FILE = 0;
@@ -459,10 +460,7 @@ export class IsomorphicGit extends GitManager {
459460
return this.wrapFS(git.resolveRef({ ...this.getRepo(), ref }));
460461
}
461462

462-
/**
463-
@param {boolean} force - If true, merge strategy `--strategy-option=theirs` is used. This will prefer remote changes over local changes.
464-
*/
465-
async pull(force: boolean = false): Promise<FileStatusResult[]> {
463+
async pull(): Promise<FileStatusResult[]> {
466464
const progressNotice = this.showNotice("Initializing pull");
467465
try {
468466
this.plugin.setPluginState({ gitAction: CurrentGitAction.pull });
@@ -479,10 +477,37 @@ export class IsomorphicGit extends GitManager {
479477
ours: branchInfo.current,
480478
theirs: branchInfo.tracking!,
481479
abortOnConflict: false,
482-
mergeDriver: force ? ({ contents }) => {
483-
const mergedText = contents[2];
484-
return { cleanMerge: true, mergedText };
485-
} : undefined,
480+
mergeDriver:
481+
this.plugin.settings.resolutionMethod !== "none"
482+
? ({ contents }) => {
483+
const baseContent = contents[0];
484+
const ourContent = contents[1];
485+
const theirContent = contents[2];
486+
487+
const LINEBREAKS = /^.*(\r?\n|$)/gm;
488+
const ours =
489+
ourContent.match(LINEBREAKS) ?? [];
490+
const base =
491+
baseContent.match(LINEBREAKS) ?? [];
492+
const theirs =
493+
theirContent.match(LINEBREAKS) ?? [];
494+
const result = diff3Merge(ours, base, theirs);
495+
let mergedText = "";
496+
for (const item of result) {
497+
if (item.ok) {
498+
mergedText += item.ok.join("");
499+
}
500+
if (item.conflict) {
501+
mergedText +=
502+
this.plugin.settings
503+
.resolutionMethod === "ours"
504+
? item.conflict.a.join("")
505+
: item.conflict.b.join("");
506+
}
507+
}
508+
return { cleanMerge: true, mergedText };
509+
}
510+
: undefined,
486511
})
487512
);
488513
if (!mergeRes.alreadyMerged) {

src/gitManager/simpleGit.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -584,11 +584,7 @@ export class SimpleGit extends GitManager {
584584
return this.discard(dir ?? ".");
585585
}
586586

587-
/**
588-
@param {boolean} force - If true, merge strategy `--strategy-option=theirs` is used. This will prefer remote changes over local changes.
589-
*/
590-
async pull(force: boolean): Promise<FileStatusResult[] | undefined> {
591-
const progressNotice = this.plugin.displayMessage("Initializing pull");
587+
async pull(): Promise<FileStatusResult[] | undefined> {
592588
this.plugin.setPluginState({ gitAction: CurrentGitAction.pull });
593589
try {
594590
if (this.plugin.settings.updateSubmodules)
@@ -618,15 +614,19 @@ export class SimpleGit extends GitManager {
618614
]);
619615

620616
if (localCommit !== upstreamCommit) {
621-
let err = false;
622617
if (
623618
this.plugin.settings.syncMethod === "merge" ||
624619
this.plugin.settings.syncMethod === "rebase"
625620
) {
626621
try {
627-
const args = force
628-
? [branchInfo.tracking!, "--strategy-option=theirs"]
629-
: [branchInfo.tracking!]
622+
const args = [branchInfo.tracking!];
623+
624+
if (this.plugin.settings.resolutionMethod !== "none") {
625+
args.push(
626+
`--strategy-option=${this.plugin.settings.resolutionMethod}`
627+
);
628+
}
629+
630630
switch (this.plugin.settings.syncMethod) {
631631
case "merge":
632632
await this.git.merge(args);
@@ -668,8 +668,6 @@ export class SimpleGit extends GitManager {
668668
"--name-only",
669669
]);
670670

671-
if (!err) this.plugin.displayMessage("Finished pull");
672-
673671
return filesChanged
674672
.split(/\r\n|\r|\n/)
675673
.filter((value) => value.length > 0)

src/main.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -748,10 +748,10 @@ export default class ObsidianGit extends Plugin {
748748
}
749749

750750
///Used for command
751-
async pullChangesFromRemote(force: boolean = false): Promise<void> {
751+
async pullChangesFromRemote(): Promise<void> {
752752
if (!(await this.isAllInitialized())) return;
753753

754-
const filesUpdated = await this.pull(force);
754+
const filesUpdated = await this.pull();
755755
if (filesUpdated === false) {
756756
return;
757757
}
@@ -1074,18 +1074,13 @@ export default class ObsidianGit extends Plugin {
10741074
}
10751075
}
10761076

1077-
/** Used for internals
1078-
Returns whether the pull added a commit or not.
1079-
@param {boolean} force - If true, merge strategy `--strategy-option=theirs` is used. This will prefer remote changes over local changes.
1080-
1081-
See {@link pullChangesFromRemote} for the command version. */
1082-
async pull(force: boolean = false): Promise<false | number> {
1077+
async pull(): Promise<false | number> {
10831078
if (!(await this.remotesAreSet())) {
10841079
return false;
10851080
}
10861081
try {
10871082
this.log("Pulling....");
1088-
const pulledFiles = (await this.gitManager.pull(force)) || [];
1083+
const pulledFiles = (await this.gitManager.pull()) || [];
10891084
this.setPluginState({ offlineMode: false });
10901085

10911086
if (pulledFiles.length > 0) {

src/setting/settings.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import type {
2525
import type ObsidianGit from "src/main";
2626
import type {
2727
ObsidianGitSettings,
28+
ResolutionMethod,
2829
ShowAuthorInHistoryView,
2930
SyncMethod,
3031
} from "src/types";
@@ -398,6 +399,26 @@ export class ObsidianGitSettingsTab extends PluginSettingTab {
398399
});
399400
});
400401

402+
new Setting(containerEl)
403+
.setName("Conflict resolution strategy")
404+
.setDesc(
405+
"Decide how to solve conflicts when pulling remote changes"
406+
)
407+
.addDropdown((dropdown) => {
408+
const options: Record<ResolutionMethod, string> = {
409+
none: "None (git default)",
410+
ours: "Our changes",
411+
theirs: "Their changes",
412+
};
413+
dropdown.addOptions(options);
414+
dropdown.setValue(plugin.settings.resolutionMethod);
415+
416+
dropdown.onChange(async (option: ResolutionMethod) => {
417+
plugin.settings.resolutionMethod = option;
418+
await plugin.saveSettings();
419+
});
420+
});
421+
401422
new Setting(containerEl)
402423
.setName("Pull on startup")
403424
.setDesc("Automatically pull commits when Obsidian starts.")

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ObsidianGitSettings {
1414
autoPullOnBoot: boolean;
1515
autoCommitOnlyStaged: boolean;
1616
syncMethod: SyncMethod;
17+
resolutionMethod: ResolutionMethod;
1718
/**
1819
* Whether to push on commit-and-sync
1920
*/
@@ -80,6 +81,8 @@ export function mergeSettingsByPriority(
8081

8182
export type SyncMethod = "rebase" | "merge" | "reset";
8283

84+
export type ResolutionMethod = "none" | "ours" | "theirs";
85+
8386
export type ShowAuthorInHistoryView = "full" | "initials" | "hide";
8487

8588
export interface Author {

0 commit comments

Comments
 (0)