一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
58 行
2.3 KiB
TypeScript
58 行
2.3 KiB
TypeScript
import { autoMerge, MergedSection } from './diffNotes';
|
|
|
|
export interface NoteVersion {
|
|
title: string;
|
|
body: string;
|
|
}
|
|
|
|
// A title is a conflict only when the both sides changed it differently
|
|
export const mergeTitle = (base: string, local: string, remote: string) => {
|
|
if (local === remote) return { merged: local, conflict: false };
|
|
if (base === local) return { merged: remote, conflict: false };
|
|
if (base === remote) return { merged: local, conflict: false };
|
|
return { merged: '', conflict: true };
|
|
};
|
|
|
|
// Only conflict sections differ between the two sides; everything else is shared between them
|
|
const buildBody = (sections: MergedSection[], conflictSide: 'localText' | 'remoteText'): string => {
|
|
const lineCount = conflictSide === 'localText' ? 'localLineCount' : 'remoteLineCount';
|
|
return sections
|
|
.filter(section => section.type !== 'conflict' || section[lineCount] !== 0)
|
|
.map(section => section.type === 'conflict' ? (section[conflictSide] ?? '') : section.text)
|
|
.join('\n');
|
|
};
|
|
|
|
export interface PartialMergeResult {
|
|
fullyMerged: boolean;
|
|
// Non-conflicting changes are merged, and any remaining conflicts keep the user's version
|
|
resolvedLocal: NoteVersion;
|
|
// Same, but remaining conflicts kept as the incoming side
|
|
resolvedCurrent: NoteVersion;
|
|
}
|
|
|
|
export default (base: NoteVersion, local: NoteVersion, remote: NoteVersion): PartialMergeResult => {
|
|
const title = mergeTitle(base.title ?? '', local.title ?? '', remote.title ?? '');
|
|
|
|
const localBody = local.body ?? '';
|
|
const remoteBody = remote.body ?? '';
|
|
|
|
if (localBody === remoteBody && !title.conflict) {
|
|
return { fullyMerged: true, resolvedLocal: { title: title.merged, body: localBody }, resolvedCurrent: { title: title.merged, body: localBody } };
|
|
}
|
|
|
|
const merged = autoMerge(base.body ?? '', localBody, remoteBody);
|
|
const bodyFullyMerged = !merged.sections.some(s => s.type === 'conflict');
|
|
|
|
return {
|
|
fullyMerged: bodyFullyMerged && !title.conflict,
|
|
resolvedLocal: {
|
|
title: title.conflict ? (local.title ?? '') : title.merged,
|
|
body: bodyFullyMerged ? merged.mergedText : buildBody(merged.sections, 'localText'),
|
|
},
|
|
resolvedCurrent: {
|
|
title: title.conflict ? (remote.title ?? '') : title.merged,
|
|
body: bodyFullyMerged ? merged.mergedText : buildBody(merged.sections, 'remoteText'),
|
|
},
|
|
};
|
|
};
|