78 lines
2.8 KiB
JavaScript
78 lines
2.8 KiB
JavaScript
function mergeChecklistConflict({ baseBody, localBody, remoteBody }) {
|
|
const taskPattern = /^(\s*[-*+]\s+\[)([ xX])(\]\s+)(.*)$/;
|
|
|
|
function tasks(body) {
|
|
const entries = [];
|
|
String(body || '').split('\n').forEach((line, lineIndex) => {
|
|
const match = line.match(taskPattern);
|
|
if (!match) return;
|
|
const label = match[4].trim();
|
|
const key = label.replace(/\s+/g, ' ').toLocaleLowerCase();
|
|
if (!key) return;
|
|
entries.push({ key, label, checked: match[2].toLowerCase() === 'x', lineIndex, match });
|
|
});
|
|
return entries;
|
|
}
|
|
|
|
function grouped(entries) {
|
|
const result = new Map();
|
|
entries.forEach(entry => result.set(entry.key, [...(result.get(entry.key) || []), entry]));
|
|
return result;
|
|
}
|
|
|
|
const base = grouped(tasks(baseBody));
|
|
const local = grouped(tasks(localBody));
|
|
const remoteEntries = tasks(remoteBody);
|
|
const remote = grouped(remoteEntries);
|
|
const changes = [];
|
|
const conflicts = [];
|
|
|
|
for (const [key, baseMatches] of base) {
|
|
const localMatches = local.get(key) || [];
|
|
if (baseMatches.length !== 1 || localMatches.length !== 1) continue;
|
|
if (baseMatches[0].checked === localMatches[0].checked) continue;
|
|
const remoteMatches = remote.get(key) || [];
|
|
if (remoteMatches.length !== 1) {
|
|
conflicts.push({
|
|
label: baseMatches[0].label,
|
|
reason: remoteMatches.length ? 'ambiguous' : 'missing',
|
|
});
|
|
continue;
|
|
}
|
|
changes.push({ label: remoteMatches[0].label, checked: localMatches[0].checked });
|
|
}
|
|
|
|
for (const [key, localMatches] of local) {
|
|
if (base.has(key)) continue;
|
|
if (localMatches.length !== 1) {
|
|
conflicts.push({ label:localMatches[0].label, reason:'ambiguous' });
|
|
continue;
|
|
}
|
|
const remoteMatches = remote.get(key) || [];
|
|
if (remoteMatches.length > 1) {
|
|
conflicts.push({ label: localMatches[0].label, reason: 'ambiguous' });
|
|
continue;
|
|
}
|
|
if (remoteMatches.length === 0) {
|
|
changes.push({ label: localMatches[0].label, checked:localMatches[0].checked, added:true });
|
|
}
|
|
}
|
|
|
|
if (conflicts.length) return { body: null, changes, conflicts };
|
|
const desired = new Map(changes.map(change => [
|
|
change.label.replace(/\s+/g, ' ').toLocaleLowerCase(), change.checked,
|
|
]));
|
|
const lines = String(remoteBody || '').split('\n');
|
|
remoteEntries.forEach(entry => {
|
|
if (!desired.has(entry.key)) return;
|
|
const marker = desired.get(entry.key) ? 'x' : ' ';
|
|
lines[entry.lineIndex] = entry.match[1] + marker + entry.match[3] + entry.match[4];
|
|
});
|
|
changes.filter(change => change.added).forEach(change => {
|
|
lines.push('- [' + (change.checked ? 'x' : ' ') + '] ' + change.label);
|
|
});
|
|
return { body: lines.join('\n'), changes, conflicts: [] };
|
|
}
|
|
|
|
if (typeof module !== 'undefined') module.exports = mergeChecklistConflict;
|