Files
OpenNoodl/packages/noodl-git/src/merge-strategy.ts
Michael Cartner b9c60b07dc Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com>
Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com>
Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com>
Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com>
Co-Authored-By: Johan  <4934465+joolsus@users.noreply.github.com>
Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com>
Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
2024-01-26 11:52:55 +01:00

104 lines
3.6 KiB
TypeScript

import { IMergeTreeEntry, MergeTreeError } from './core/models/merge';
import { getFileContents } from './core/cat-file';
import { addAll } from './core/add';
import path from 'path';
import fs from 'fs';
function tryParseJson(str: string) {
try {
return JSON.parse(str);
} catch {
return null;
}
}
type JSON_OBJECT = {
[key: string]: any;
};
/** Merge the project.json file */
export type MergeStrategyFunc = (ancestors: JSON_OBJECT, ours: JSON_OBJECT, theirs: JSON_OBJECT) => JSON_OBJECT;
/**
* Defines the Noodl custom merge strategy.
*/
export class MergeStrategy {
constructor(
public readonly repositoryDir: string,
private readonly mergeProject: MergeStrategyFunc,
public readonly strategy: 'our' | 'their' = 'our'
) {}
public async solveConflicts(tree: MergeTreeError) {
const projectJsonFiles = new Set<IMergeTreeEntry>(); //handle repos with multiple noodl projects
const otherFiles = new Set<IMergeTreeEntry>();
for (const entry of tree.conflictedEntries) {
if (entry.hasConflicts) {
const parent = entry.base || entry.our || entry.their;
if (parent.path === 'project.json' || parent.path.slice(-13) === '/project.json') {
projectJsonFiles.add(entry);
} else {
otherFiles.add(entry);
}
}
}
// Resolve conflicts
const projectPromises = Array.from(projectJsonFiles).map((x) => this.resolveProjectJsonConflict(x));
const otherFilePromises = Array.from(otherFiles).map((x) => this.resolveFileConflict(x));
const allPromises = [...projectPromises, ...otherFilePromises];
await Promise.all(allPromises);
// Mark all the file as resolved resolution
await addAll(this.repositoryDir);
// Return all files that were resolved
return [...projectJsonFiles, ...otherFiles];
}
private async resolveFileConflict(entry: IMergeTreeEntry) {
const parent = entry.base || entry.our || entry.their;
console.log('resolveFileConflict', parent.path);
const targetEntry = entry[this.strategy];
console.log('vcs: resolving file conflict', targetEntry.path, 'using', this.strategy);
const ourBlob = await getFileContents(this.repositoryDir, targetEntry.sha);
const absolutePath = path.join(this.repositoryDir, parent.path);
// Write our version of the file
await fs.promises.writeFile(absolutePath, ourBlob);
}
private async resolveProjectJsonConflict(entry: IMergeTreeEntry) {
const parent = entry.base || entry.our || entry.their;
console.log('resolveProjectJsonConflict', parent.path);
const results = await Promise.all([
entry.base ? getFileContents(this.repositoryDir, entry.base.sha) : Promise.resolve('{}'),
entry.our ? getFileContents(this.repositoryDir, entry.our.sha) : Promise.resolve('{}'),
entry.their ? getFileContents(this.repositoryDir, entry.their.sha) : Promise.resolve('{}')
]);
const ancestor = tryParseJson(results[0].toString());
const ours = tryParseJson(results[1].toString());
const theirs = tryParseJson(results[2].toString());
if (!ancestor) {
throw new Error("Failed to solve project.json conflict. Couldn't parse ancestor: " + results[0].toString());
}
if (!ours) {
throw new Error("Failed to solve project.json conflict. Couldn't parse ours: " + results[1].toString());
} else if (!theirs) {
throw new Error("Failed to solve project.json conflict. Couldn't parse theirs: " + results[2].toString());
}
const mergedResult = this.mergeProject(ancestor, ours, theirs);
const absolutePath = path.join(this.repositoryDir, parent.path);
await fs.promises.writeFile(absolutePath, JSON.stringify(mergedResult));
}
}