|
@@ -0,0 +1,499 @@
|
|
|
+"use strict";
|
|
|
+Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
+exports.VersionRegistry = exports.Version = void 0;
|
|
|
+const util_1 = require("util"); // NodeJS library
|
|
|
+const buffer_1 = require("buffer"); // NodeJS library
|
|
|
+const dfs_1 = require("./util/dfs");
|
|
|
+const permutations_1 = require("./util/permutations");
|
|
|
+const buffer_xor_1 = require("./util/buffer_xor");
|
|
|
+// not exported -> use VersionRegistry to create versions
|
|
|
+class Version {
|
|
|
+ // DO NOT USE constructor directly - instead use VersionRegistry.createVersion.
|
|
|
+ constructor(parents, hash, size, embeddings) {
|
|
|
+ this.children = []; // reverse parents
|
|
|
+ this.reverseEmbeddings = new Map(); // (host) versions in which this (guest) version is embedded.
|
|
|
+ this.implicitSelfEmbedding = {
|
|
|
+ version: this,
|
|
|
+ overridings: new Map(), // no overrides
|
|
|
+ };
|
|
|
+ this.parents = parents;
|
|
|
+ this.hash = hash;
|
|
|
+ this.size = size;
|
|
|
+ this.embeddings = embeddings(this);
|
|
|
+ }
|
|
|
+ // Returns iterator that yields all deltas of this version, from recent to early.
|
|
|
+ // Or put more precisely: a delta's dependencies will be yielded AFTER the delta itself.
|
|
|
+ *[Symbol.iterator]() {
|
|
|
+ let current = this;
|
|
|
+ while (current.parents.length !== 0) {
|
|
|
+ // There may be multiple parents due to commutativity (multiple orders of deltas that yield the same version), but it doesn't matter which one we pick: all paths will yield the same set of deltas.
|
|
|
+ const [parent, delta] = current.parents[0];
|
|
|
+ yield delta;
|
|
|
+ current = parent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ *iterPrimitiveDeltas() {
|
|
|
+ const executionOrder = [...this].reverse();
|
|
|
+ for (const d of executionOrder) {
|
|
|
+ yield* d.iterPrimitiveDeltas();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ [util_1.inspect.custom](depth, options) {
|
|
|
+ return "Version{" + [...this].map(d => (0, util_1.inspect)(d, options)).join(",") + "}";
|
|
|
+ }
|
|
|
+ isSubSetOf(otherVersion) {
|
|
|
+ // current implementation is probably quite slow
|
|
|
+ for (const delta of this) {
|
|
|
+ if (!otherVersion.contains(delta)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ contains(delta) {
|
|
|
+ for (const d of this) {
|
|
|
+ if (d === delta) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ containsPrimitive(delta) {
|
|
|
+ for (const d of this) {
|
|
|
+ for (const p of d.iterPrimitiveDeltas()) {
|
|
|
+ if (p === delta) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // Returns a sequence of Delta's to be undone/redone to go from this version to the 'otherVersion'
|
|
|
+ // Returned sequence is empty if otherVersion === this
|
|
|
+ // Returns undefined when there is no path (which is impossible if both versions are part of the same VersionRegistry).
|
|
|
+ // TODO: implement Breadth-First Search (BFS) for better performance.
|
|
|
+ findPathTo(otherVersion) {
|
|
|
+ const getNeighbors = (v) => {
|
|
|
+ const parentVersions = v.parents.map(([parentVersion, delta]) => [['p', delta, parentVersion], parentVersion]);
|
|
|
+ const childVersions = v.children.map(([childVersion, delta]) => [['c', delta, childVersion], childVersion]);
|
|
|
+ // heuristic: look in newer or older versions first?
|
|
|
+ if (v.size < otherVersion.size) {
|
|
|
+ // make the common case fast: most of the time,
|
|
|
+ // we just want to advance to the next version (i.e., after a user edit)
|
|
|
+ return [...childVersions, ...parentVersions];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return [...parentVersions, ...childVersions];
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // console.time("findDFS")
|
|
|
+ const result = (0, dfs_1.findDFS)(this, otherVersion, getNeighbors);
|
|
|
+ // console.timeEnd("findDFS");
|
|
|
+ // if (result)
|
|
|
+ // console.log("findPath:", result.map(([linkType])=>linkType));
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ // Like findPathTo, but only searches 'down' (younger versions).
|
|
|
+ findDescendant(otherVersion) {
|
|
|
+ const getNeighbors = (v) => {
|
|
|
+ const result = v.children.map(([childVersion, delta]) => [['c', delta, childVersion], childVersion]);
|
|
|
+ return result;
|
|
|
+ };
|
|
|
+ return (0, dfs_1.findDFS)(this, otherVersion, getNeighbors);
|
|
|
+ }
|
|
|
+ getEmbedding(key) {
|
|
|
+ return this.embeddings.get(key) || this.implicitSelfEmbedding;
|
|
|
+ }
|
|
|
+ getReverseEmbeddings(key) {
|
|
|
+ return this.reverseEmbeddings.get(key) || [this];
|
|
|
+ }
|
|
|
+ // Serialize a path of Deltas from a Version in alreadyHave, to fully reconstruct this version.
|
|
|
+ serialize(alreadyHave = new Set()) {
|
|
|
+ const deltas = [];
|
|
|
+ const versions = [];
|
|
|
+ this.serializeInternal(new Set(alreadyHave), new Set(), deltas, versions);
|
|
|
+ return {
|
|
|
+ externalDependencies: [...alreadyHave].map(v => v.hash.toString('hex')),
|
|
|
+ deltas,
|
|
|
+ versions,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions) {
|
|
|
+ if (alreadyHaveVersions.has(this)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ alreadyHaveVersions.add(this);
|
|
|
+ const embeddings = new Array();
|
|
|
+ for (const [guestId, { version, overridings }] of this.embeddings) {
|
|
|
+ version.serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions);
|
|
|
+ const ovr = {};
|
|
|
+ for (const [key, val] of overridings.entries()) {
|
|
|
+ ovr[key.hash.toString('hex')] = val.hash.toString('hex');
|
|
|
+ }
|
|
|
+ embeddings.push({ guestId, v: version.hash.toString('hex'), ovr });
|
|
|
+ }
|
|
|
+ if (this.parents.length > 0) {
|
|
|
+ const [parentVersion, delta] = this.parents[0];
|
|
|
+ parentVersion.serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions);
|
|
|
+ function visitDelta(delta) {
|
|
|
+ for (const d of delta.getDependencies()) {
|
|
|
+ visitDelta(d);
|
|
|
+ }
|
|
|
+ if (!alreadyHaveDeltas.has(delta)) {
|
|
|
+ deltas.push(delta.serialize());
|
|
|
+ alreadyHaveDeltas.add(delta);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ visitDelta(delta);
|
|
|
+ versions.push({
|
|
|
+ id: this.hash.toString('hex'),
|
|
|
+ parent: parentVersion.hash.toString('hex'),
|
|
|
+ delta: delta.hash.toString('hex'),
|
|
|
+ embeddings,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+exports.Version = Version;
|
|
|
+const initialHash = buffer_1.Buffer.alloc(32); // all zeros
|
|
|
+function isConflicting(deltaA, deltaB) {
|
|
|
+ // for performance, iterate over the delta that has the least conflicts:
|
|
|
+ if (deltaA.conflictsWith.length < deltaB.conflictsWith.length) {
|
|
|
+ return deltaA.conflictsWith.some(([d]) => d === deltaB);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return deltaB.conflictsWith.some(([d]) => d === deltaA);
|
|
|
+ }
|
|
|
+}
|
|
|
+class VersionRegistry {
|
|
|
+ constructor() {
|
|
|
+ this.initialVersion = new Version([], initialHash, 0, () => new Map());
|
|
|
+ // Maps version ID (as string, because a Buffer cannot be a map key) to Version
|
|
|
+ this.versionMap = new Map([
|
|
|
+ [initialHash.toString('hex'), this.initialVersion], // the initial version, always already there
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ lookupOptional(hash) {
|
|
|
+ return this.versionMap.get(hash.toString('hex'));
|
|
|
+ }
|
|
|
+ lookup(hash) {
|
|
|
+ const hex = hash.toString('hex');
|
|
|
+ const version = this.versionMap.get(hex);
|
|
|
+ if (version === undefined) {
|
|
|
+ throw new Error("no such version: " + hex);
|
|
|
+ }
|
|
|
+ return version;
|
|
|
+ }
|
|
|
+ putVersion(hash, version) {
|
|
|
+ this.versionMap.set(hash.toString('hex'), version);
|
|
|
+ }
|
|
|
+ // Idempotent
|
|
|
+ // Pre-condition 1: all of the dependencies of delta must exist in parent.
|
|
|
+ // Pre-condition 2: delta must be non-conflicting with any delta in parent.
|
|
|
+ // Pre-condition 3: if the to-be-created ("host") version embeds other ("guest") versions,
|
|
|
+ // then all of the guest versions' deltas must exist in the host version, or be explicitly overridden.
|
|
|
+ createVersion(parent, delta, embeddings = () => new Map()) {
|
|
|
+ // Check pre-condition 1:
|
|
|
+ for (const [dep] of delta.getDependencies()) {
|
|
|
+ if (!parent.contains(dep)) {
|
|
|
+ throw new Error("createVersion: precondition failed: Missing dependency: " + dep.description);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Check pre-condition 2:
|
|
|
+ for (const [conflictingDelta] of delta.conflictsWith) {
|
|
|
+ if (parent.contains(conflictingDelta)) {
|
|
|
+ throw new Error("createVersion: precondition failed: Delta " + delta.description + " conflicts with " + conflictingDelta.description);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const newVersion = this.createVersionUnsafe(parent, delta, embeddings);
|
|
|
+ // Check pre-condition 3:
|
|
|
+ const primitiveDeltas = [...delta.iterPrimitiveDeltas()];
|
|
|
+ for (const [guestId, { version: guest, overridings }] of newVersion.embeddings.entries()) {
|
|
|
+ const { version: guestParent } = parent.getEmbedding(guestId);
|
|
|
+ const guestDiff = guestParent.findDescendant(guest);
|
|
|
+ for (const [_, guestDelta] of guestDiff) {
|
|
|
+ for (const guestPDelta of guestDelta.iterPrimitiveDeltas()) {
|
|
|
+ if (!primitiveDeltas.includes(overridings.get(guestPDelta) || guestPDelta)) {
|
|
|
+ throw new Error("createVersion: precondition failed: Guest's primitive delta " + guestPDelta.description + " does not occur in host, nor is it overridden.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return newVersion;
|
|
|
+ }
|
|
|
+ // Faster than createVersion, but does not check pre-conditions.
|
|
|
+ // Idempotent
|
|
|
+ createVersionUnsafe(parent, delta, embeddings = () => new Map()) {
|
|
|
+ const newHash = (0, buffer_xor_1.bufferXOR)(parent.hash, delta.hash);
|
|
|
+ // TODO: include embeddings in hash digest.
|
|
|
+ const existingVersion = this.lookupOptional(newHash);
|
|
|
+ if (existingVersion !== undefined) {
|
|
|
+ // this Version already exists
|
|
|
+ const havePath = existingVersion.parents.some(([parentVersion, delta]) => parentVersion === parent);
|
|
|
+ if (!havePath) {
|
|
|
+ // but the path is new (there can be multiple 'paths' to the same version, because of commutation of deltas)
|
|
|
+ existingVersion.parents.push([parent, delta]);
|
|
|
+ parent.children.push([existingVersion, delta]);
|
|
|
+ }
|
|
|
+ for (const [guestId, { version, overridings }] of embeddings(existingVersion)) {
|
|
|
+ const found = existingVersion.embeddings.get(guestId);
|
|
|
+ if (!found) {
|
|
|
+ existingVersion.embeddings.set(guestId, { version, overridings });
|
|
|
+ // throw new Error("Assertion failed: created version already exists, but does not embed '" + guestId + "'");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const { version: v, overridings: o } = found;
|
|
|
+ if (v !== version) {
|
|
|
+ throw new Error("Assertion failed: created version already exists and embeds a differrent version '" + guestId + "'");
|
|
|
+ }
|
|
|
+ // Merge overridings:
|
|
|
+ for (const [guestDelta, hostDelta] of overridings.entries()) {
|
|
|
+ const alreadyHostDelta = o.get(guestDelta) || hostDelta;
|
|
|
+ if (hostDelta !== alreadyHostDelta) {
|
|
|
+ throw new Error("Assertion failed: created version already exists BUT overrides delta '" + guestDelta.description + "' with another delta.");
|
|
|
+ }
|
|
|
+ o.set(guestDelta, hostDelta);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return existingVersion;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const newVersion = new Version([[parent, delta]], newHash, parent.size + 1, embeddings);
|
|
|
+ // Create reverse parent links:
|
|
|
+ for (const [parent, delta] of newVersion.parents) {
|
|
|
+ parent.children.push([newVersion, delta]);
|
|
|
+ }
|
|
|
+ // Create reverse embedding:
|
|
|
+ for (const [key, embedding] of newVersion.embeddings.entries()) {
|
|
|
+ const reverse = embedding.version.reverseEmbeddings.get(key);
|
|
|
+ if (reverse !== undefined) {
|
|
|
+ reverse.push(newVersion);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ embedding.version.reverseEmbeddings.set(key, [newVersion]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.putVersion(newHash, newVersion);
|
|
|
+ return newVersion;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Mostly used for testing purposes.
|
|
|
+ // Order of deltas should be recent -> early
|
|
|
+ // Or put more precisely: a delta's dependencies should occur AFTER the delta in the array.
|
|
|
+ quickVersion(deltas) {
|
|
|
+ return deltas.reduceRight((parentVersion, delta) => this.createVersion(parentVersion, delta), this.initialVersion);
|
|
|
+ }
|
|
|
+ // Get the version whose deltas are a subset of all given versions AKA the 'largest common ancestor'.
|
|
|
+ getIntersection(versions) {
|
|
|
+ // treat special case first:
|
|
|
+ if (versions.length === 0) {
|
|
|
+ return this.initialVersion;
|
|
|
+ }
|
|
|
+ // sort versions (out place) from few deltas to many (FASTEST):
|
|
|
+ const sortedVersions = versions.slice().sort((versionA, versionB) => versionA.size - versionB.size);
|
|
|
+ const intersection = [];
|
|
|
+ for (const delta of sortedVersions[0]) {
|
|
|
+ let allVersionsHaveIt = true;
|
|
|
+ for (let i = 1; i < sortedVersions.length; i++) {
|
|
|
+ let thisVersionHasIt = false;
|
|
|
+ for (const otherDelta of sortedVersions[i]) {
|
|
|
+ if (delta === otherDelta) {
|
|
|
+ thisVersionHasIt = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!thisVersionHasIt) {
|
|
|
+ allVersionsHaveIt = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (allVersionsHaveIt) {
|
|
|
+ intersection.push(delta);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this.quickVersion(intersection);
|
|
|
+ }
|
|
|
+ getLCAInternal(versionA, versionB) {
|
|
|
+ const ancestorsA = [versionA];
|
|
|
+ const ancestorsB = [versionB];
|
|
|
+ while (true) {
|
|
|
+ const a = ancestorsA[ancestorsA.length - 1];
|
|
|
+ // @ts-ignore: TypeScript doesn't know about 'findLast' method yet.
|
|
|
+ if (ancestorsB.findLast(v => v === a)) {
|
|
|
+ return a;
|
|
|
+ }
|
|
|
+ if (a.parents.length > 0) {
|
|
|
+ const [parent] = a.parents[0];
|
|
|
+ ancestorsA.push(parent);
|
|
|
+ }
|
|
|
+ const b = ancestorsB[ancestorsB.length - 1];
|
|
|
+ // @ts-ignore: TypeScript doesn't know about 'findLast' method yet.
|
|
|
+ if (ancestorsA.findLast(v => v === b)) {
|
|
|
+ return b;
|
|
|
+ }
|
|
|
+ if (b.parents.length > 0) {
|
|
|
+ const [parent] = b.parents[0];
|
|
|
+ ancestorsB.push(parent);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ getLCA(versions) {
|
|
|
+ if (versions.length === 0) {
|
|
|
+ return this.initialVersion;
|
|
|
+ }
|
|
|
+ return versions.reduce((a, b) => this.getLCAInternal(a, b));
|
|
|
+ }
|
|
|
+ // Idempotent
|
|
|
+ // Of the union of all deltas of the versions given, compute the maximal left-closed conflict-free subsets.
|
|
|
+ // These are the subsets to which no delta can be added without introducing a conflict or missing dependency.
|
|
|
+ merge(versions, debugNames) {
|
|
|
+ function printDebug(...args) {
|
|
|
+ if (debugNames !== undefined) {
|
|
|
+ for (const [i, arg] of args.entries()) {
|
|
|
+ try {
|
|
|
+ const name = debugNames(arg);
|
|
|
+ if (name !== undefined) {
|
|
|
+ args[i] = name;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (e) { }
|
|
|
+ }
|
|
|
+ console.log(...args);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const lca = this.getLCA(versions);
|
|
|
+ // ATTENTION: Constructing 'diff' must be made recursive (guest could also be a host)
|
|
|
+ function visitEmbeddings(currentHost, nextHost) {
|
|
|
+ const guestDeltaMap = new Map();
|
|
|
+ for (const [guestId, { version: nextGuest, overridings }] of nextHost.embeddings.entries()) {
|
|
|
+ const currentGuest = currentHost.getEmbedding(guestId).version;
|
|
|
+ const guestPath = currentGuest.findDescendant(nextGuest);
|
|
|
+ if (guestPath.length > 1) {
|
|
|
+ throw new Error("Did not expect guestPath to be longer than one delta");
|
|
|
+ }
|
|
|
+ if (guestPath.length === 1) {
|
|
|
+ const guestDelta = guestPath[0][1];
|
|
|
+ if (currentHost === currentGuest && nextHost === nextGuest) {
|
|
|
+ // prevent infinite recursion in case of self-embedding:
|
|
|
+ guestDeltaMap.set(guestId, [[guestDelta, guestDeltaMap], overridings]);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const recursiveGuestDeltaMap = visitEmbeddings(currentGuest, nextGuest);
|
|
|
+ guestDeltaMap.set(guestId, [[guestDelta, recursiveGuestDeltaMap], overridings]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ guestDeltaMap.set(guestId, [null, new Map()]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return guestDeltaMap;
|
|
|
+ }
|
|
|
+ let diff = [];
|
|
|
+ for (const v of versions) {
|
|
|
+ const path = lca.findDescendant(v);
|
|
|
+ // all deltas on path from lca to 'v':
|
|
|
+ let currentVersion = lca;
|
|
|
+ for (const [_, delta, nextVersion] of path) {
|
|
|
+ const guestDeltaMap = visitEmbeddings(currentVersion, nextVersion);
|
|
|
+ // The following condition may be wrong, check this later:
|
|
|
+ if (!diff.some(([d]) => d === delta)) {
|
|
|
+ diff.push([delta, guestDeltaMap]);
|
|
|
+ }
|
|
|
+ currentVersion = nextVersion;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Now we're ready to actually start merging...
|
|
|
+ const result = [];
|
|
|
+ // Recursively attempts to add deltas from 'deltasToTry' to 'startVersion'
|
|
|
+ // When nothing can be added anymore without introducing a conflict, we have a 'maximal version' (a result).
|
|
|
+ // It's possible that there is more than one 'maximal version'.
|
|
|
+ // Precondition: We assume that any single delta of 'deltasToTry' will not be conflicting with 'startVersion'.
|
|
|
+ const depthFirst = (startVersion, candidates, depth) => {
|
|
|
+ function printIndent(...args) {
|
|
|
+ printDebug(" ".repeat(depth), ...args);
|
|
|
+ }
|
|
|
+ // printIndent("deltasToTry=", ...candidates.map(([d])=>d));
|
|
|
+ let couldNotRecurse = true;
|
|
|
+ for (const [delta, guestDeltaMap] of candidates) {
|
|
|
+ const haveMissingDependency = delta.getDependencies().some(([dependency]) => !startVersion.contains(dependency));
|
|
|
+ if (haveMissingDependency) {
|
|
|
+ printIndent("missing dependency, trying next delta");
|
|
|
+ continue; // skip this delta, but keep it in deltasToTry (its missing dependency may be in deltasToTry)
|
|
|
+ }
|
|
|
+ // ATTENTION: Constructing embeddings must be made recursive (guest could also be a host)
|
|
|
+ // 'delta' can be added
|
|
|
+ // printIndent("including", delta, " => new version: ", delta, ...startVersion);
|
|
|
+ const createMergedVersionRecursive = (startVersion, [delta, guestDeltaMap]) => {
|
|
|
+ const embeddings = new Map();
|
|
|
+ const selfEmbeddingKeys = new Set();
|
|
|
+ for (const [guestId, [guestDelta, overridings]] of guestDeltaMap.entries()) {
|
|
|
+ const { version: guestStartVersion } = startVersion.getEmbedding(guestId);
|
|
|
+ // console.log(guestId, "guestStartVersion: ", guestStartVersion);
|
|
|
+ let nextGuestVersion;
|
|
|
+ if (guestDelta === null) {
|
|
|
+ nextGuestVersion = guestStartVersion;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if (guestStartVersion === startVersion && guestDelta[0] === delta && guestDelta[1] === guestDeltaMap) {
|
|
|
+ selfEmbeddingKeys.add(guestId);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ nextGuestVersion = createMergedVersionRecursive(guestStartVersion, guestDelta);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ embeddings.set(guestId, { version: nextGuestVersion, overridings });
|
|
|
+ }
|
|
|
+ // printIndent("Creating version (", delta, ...[...startVersion].map(d => d), ") with embeddings", embeddings);
|
|
|
+ const nextVersion = this.createVersion(startVersion, delta, newVersion => {
|
|
|
+ // add self-embeddings:
|
|
|
+ for (const guestId of selfEmbeddingKeys) {
|
|
|
+ embeddings.set(guestId, { version: newVersion, overridings: new Map() });
|
|
|
+ }
|
|
|
+ return embeddings;
|
|
|
+ });
|
|
|
+ // printIndent("Created version (", ...[...nextVersion].map(d => d), ") with embeddings", embeddings);
|
|
|
+ return nextVersion;
|
|
|
+ };
|
|
|
+ const nextVersion = createMergedVersionRecursive(startVersion, [delta, guestDeltaMap]);
|
|
|
+ // current delta does not have to be included again in next loop iterations
|
|
|
+ candidates = candidates.filter(([d]) => d !== delta);
|
|
|
+ // const [conflicting, nonConflicting] = _.partition(deltasToTry, ([d]) => isConflicting(d, delta));
|
|
|
+ const nonConflicting = candidates.filter(([candidate]) => !isConflicting(candidate, delta));
|
|
|
+ // if (conflicting.length > 0)
|
|
|
+ // printIndent("will be skipped (conflicts with", delta, "):", ...conflicting);
|
|
|
+ depthFirst(nextVersion, nonConflicting, depth + 1);
|
|
|
+ couldNotRecurse = false;
|
|
|
+ if (nonConflicting.length === candidates.length) {
|
|
|
+ // all deltas from deltasToTry were included -> no need to try alternatives
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (couldNotRecurse) {
|
|
|
+ // possibly have a new maximal version
|
|
|
+ if (!result.some(v => startVersion.isSubSetOf(v))) {
|
|
|
+ // printIndent("new result");
|
|
|
+ result.push(startVersion);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ depthFirst(lca, diff, 0);
|
|
|
+ // printDebug("result of merge:", ..._.flatten(result.map(v => [...v, ","])));
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ // Idempotent
|
|
|
+ // Calls the merge-function for every permutation of 'versions'
|
|
|
+ // This is useful for didactic purposes: a path is created from every input to at least one output.
|
|
|
+ // Of course it won't scale to many inputs.
|
|
|
+ crazyMerge(versions, debugNames) {
|
|
|
+ let result;
|
|
|
+ for (const v of (0, permutations_1.permutations)(versions)) {
|
|
|
+ // whatever the permutation, result will always be the same:
|
|
|
+ result = this.merge(v, debugNames);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+}
|
|
|
+exports.VersionRegistry = VersionRegistry;
|
|
|
+//# sourceMappingURL=version.js.map
|