|
@@ -25,12 +25,15 @@ export interface EmbeddedVersion {
|
|
|
|
|
|
export type EmbeddedVersionMap = Map<string, EmbeddedVersion>;
|
|
|
|
|
|
-// Just a shorthand
|
|
|
+export type ParentLink = [Version, Delta, EmbeddedVersionMap];
|
|
|
+
|
|
|
+
|
|
|
+// Just a shorthand and more readable
|
|
|
export function embed(...triples: [string, Version, Map<Delta,Delta>][]): EmbeddedVersionMap {
|
|
|
return new Map(triples.map(([id, version, overriddenDeltas]) => [id, {embedded: version, overriddenDeltas}]));
|
|
|
}
|
|
|
|
|
|
-// Just a shorthand
|
|
|
+// Just a shorthand and more readable
|
|
|
export function overrideDeltas(...tuples: [Delta,Delta][]): Map<Delta,Delta> {
|
|
|
return new Map(tuples);
|
|
|
}
|
|
@@ -43,9 +46,9 @@ type VersionLink = [('p'|'c'), Delta];
|
|
|
|
|
|
// not exported -> use VersionRegistry to create versions
|
|
|
export class Version {
|
|
|
- readonly parents: Array<[Version, Delta]>;
|
|
|
+ readonly parents: Array<ParentLink>;
|
|
|
readonly children: Array<[Version, Delta]> = [];
|
|
|
- private readonly embeddings: EmbeddedVersionMap; // do not access directly - use getEmbedded() instead
|
|
|
+ // private readonly embeddings: EmbeddedVersionMap; // do not access directly - use getEmbedded() instead
|
|
|
|
|
|
// Unique ID of the version - XOR of all delta hashes - guarantees that Versions with equal (unordered) sets of Deltas have the same ID.
|
|
|
readonly hash: Buffer;
|
|
@@ -53,12 +56,12 @@ export class Version {
|
|
|
readonly size: number;
|
|
|
|
|
|
// DO NOT USE constructor directly - instead use VersionRegistry.createVersion.
|
|
|
- constructor(parents: Array<[Version, Delta]>, embeddings: EmbeddedVersionMap, hash: Buffer, size: number) {
|
|
|
+ constructor(parents: Array<ParentLink>, hash: Buffer, size: number) {
|
|
|
this.parents = parents;
|
|
|
for (const [parent,delta] of parents) {
|
|
|
parent.children.push([this, delta]);
|
|
|
}
|
|
|
- this.embeddings = embeddings;
|
|
|
+ // this.embeddings = embeddings;
|
|
|
this.hash = hash;
|
|
|
this.size = size;
|
|
|
}
|
|
@@ -68,6 +71,7 @@ export class Version {
|
|
|
*[Symbol.iterator](): Iterator<Delta> {
|
|
|
let current: Version = 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;
|
|
@@ -75,7 +79,8 @@ export class Version {
|
|
|
}
|
|
|
|
|
|
*iterPrimitiveDeltas(): Iterable<Delta> {
|
|
|
- for (const d of this) {
|
|
|
+ const executionOrder = [...this].reverse();
|
|
|
+ for (const d of executionOrder) {
|
|
|
yield* d.iterPrimitiveDeltas();
|
|
|
}
|
|
|
}
|
|
@@ -114,16 +119,17 @@ export class Version {
|
|
|
return findDFS<Version,VersionLink>(this, otherVersion, getNeighbors);
|
|
|
}
|
|
|
|
|
|
- getEmbedded(id: string): EmbeddedVersion | undefined {
|
|
|
- return this.embeddings.get(id);
|
|
|
- }
|
|
|
-
|
|
|
- getEmbeddedOrThrow(id: string): EmbeddedVersion {
|
|
|
- const result = this.embeddings.get(id);
|
|
|
- if (result === undefined) {
|
|
|
- throw new Error("No such embedding: " + id);
|
|
|
+ // Is the given Delta overridden in this version? Then this function returns the Delta that replaces it.
|
|
|
+ findOverride<T extends Delta>(embedKey: string, delta: T): T | undefined {
|
|
|
+ if (this.parents.length > 0) {
|
|
|
+ const [parentVersion, _, map] = this.parents[0];
|
|
|
+ const embedding = map.get(embedKey);
|
|
|
+ if (embedding === undefined) {
|
|
|
+ throw new Error("Wrong embedKey: " + embedKey);
|
|
|
+ }
|
|
|
+ const found = embedding.overriddenDeltas.get(delta) as T;
|
|
|
+ return found || parentVersion.findOverride<T>(embedKey, delta);
|
|
|
}
|
|
|
- return result;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -131,25 +137,25 @@ const initialHash = Buffer.alloc(32); // all zeros
|
|
|
|
|
|
export class InitialVersion extends Version {
|
|
|
private static instance: InitialVersion = new InitialVersion(); // singleton pattern
|
|
|
- private static embedded: EmbeddedVersion = {embedded: InitialVersion.instance, overriddenDeltas: new Map()};
|
|
|
+ // private static embedded: EmbeddedVersion = {embedded: InitialVersion.instance, overriddenDeltas: new Map()};
|
|
|
|
|
|
private constructor() {
|
|
|
- super([], new Map(), initialHash, 0);
|
|
|
+ super([], initialHash, 0);
|
|
|
}
|
|
|
|
|
|
static getInstance(): InitialVersion {
|
|
|
return InitialVersion.instance;
|
|
|
}
|
|
|
|
|
|
- // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
|
|
|
- getEmbedded(id: string): EmbeddedVersion| undefined {
|
|
|
- return InitialVersion.embedded;
|
|
|
- }
|
|
|
+ // // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
|
|
|
+ // getEmbedded(id: string): EmbeddedVersion| undefined {
|
|
|
+ // return InitialVersion.embedded;
|
|
|
+ // }
|
|
|
|
|
|
- // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
|
|
|
- getEmbeddedOrThrow(id: string): EmbeddedVersion {
|
|
|
- return InitialVersion.embedded;
|
|
|
- }
|
|
|
+ // // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
|
|
|
+ // getEmbeddedOrThrow(id: string): EmbeddedVersion {
|
|
|
+ // return InitialVersion.embedded;
|
|
|
+ // }
|
|
|
}
|
|
|
|
|
|
// The initial, empty version.
|
|
@@ -198,36 +204,36 @@ export class VersionRegistry {
|
|
|
throw new Error("Delta " + delta.getDescription() + " conflicts with " + conflictsWith.getDescription());
|
|
|
}
|
|
|
|
|
|
- const primitives = [...delta.iterPrimitiveDeltas()];
|
|
|
-
|
|
|
- for (const [guestId, {embedded, overriddenDeltas}] of embeddings.entries()) {
|
|
|
- const parentEmbedding = parent.getEmbedded(guestId);
|
|
|
- // Check pre-condition 3:
|
|
|
- if (parentEmbedding === undefined) {
|
|
|
- throw new Error("Attempted to create illegal version: parent version does not embed '" + guestId + "'");
|
|
|
- }
|
|
|
- const {embedded: parentEmbedded} = parentEmbedding;
|
|
|
- // Check pre-condition 4:
|
|
|
- const found = embedded.parents.find(([parentOfEmbedded]) => (parentOfEmbedded === parentEmbedded));
|
|
|
- if (found === undefined) {
|
|
|
- throw new Error("Attempted to create illegal version that embeds a guest, whose parent is not embedded by the parent of the to-be-created version.");
|
|
|
- }
|
|
|
- else {
|
|
|
- // Check pre-condition 5:
|
|
|
- // console.log("primitives:", primitives);
|
|
|
- const [parentOfEmbedded, embeddedDelta] = found;
|
|
|
- for (const embeddedPrimitive of embeddedDelta.iterPrimitiveDeltas()) {
|
|
|
- // console.log("embeddedPrimitive:", embeddedPrimitive);
|
|
|
- const haveOriginal = primitives.includes(embeddedPrimitive);
|
|
|
- const overridden = overriddenDeltas.get(embeddedPrimitive);
|
|
|
- const haveOverridden = ((overridden !== undefined) && primitives.includes(overridden));
|
|
|
- // console.log(haveOriginal, haveOverridden)
|
|
|
- if (!haveOriginal && !haveOverridden || (haveOriginal && haveOverridden)) { // logical XOR
|
|
|
- throw new Error("Attempt to create illegal version: must contain all deltas (some possibly overridden) of all embedded versions.");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ // const primitives = [...delta.iterPrimitiveDeltas()];
|
|
|
+
|
|
|
+ // for (const [guestId, {embedded, overriddenDeltas}] of embeddings.entries()) {
|
|
|
+ // const parentEmbedding = parent.getEmbedded(guestId);
|
|
|
+ // // Check pre-condition 3:
|
|
|
+ // if (parentEmbedding === undefined) {
|
|
|
+ // throw new Error("Attempted to create illegal version: parent version does not embed '" + guestId + "'");
|
|
|
+ // }
|
|
|
+ // const {embedded: parentEmbedded} = parentEmbedding;
|
|
|
+ // // Check pre-condition 4:
|
|
|
+ // const found = embedded.parents.find(([parentOfEmbedded]) => (parentOfEmbedded === parentEmbedded));
|
|
|
+ // if (found === undefined) {
|
|
|
+ // throw new Error("Attempted to create illegal version that embeds a guest, whose parent is not embedded by the parent of the to-be-created version.");
|
|
|
+ // }
|
|
|
+ // else {
|
|
|
+ // // Check pre-condition 5:
|
|
|
+ // // console.log("primitives:", primitives);
|
|
|
+ // const [parentOfEmbedded, embeddedDelta] = found;
|
|
|
+ // for (const embeddedPrimitive of embeddedDelta.iterPrimitiveDeltas()) {
|
|
|
+ // // console.log("embeddedPrimitive:", embeddedPrimitive);
|
|
|
+ // const haveOriginal = primitives.includes(embeddedPrimitive);
|
|
|
+ // const overridden = overriddenDeltas.get(embeddedPrimitive);
|
|
|
+ // const haveOverridden = ((overridden !== undefined) && primitives.includes(overridden));
|
|
|
+ // // console.log(haveOriginal, haveOverridden)
|
|
|
+ // if (!haveOriginal && !haveOverridden || (haveOriginal && haveOverridden)) { // logical XOR
|
|
|
+ // throw new Error("Attempt to create illegal version: must contain all deltas (some possibly overridden) of all embedded versions.");
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
return this.createVersionUnsafe(parent, delta, embeddings);
|
|
|
}
|
|
|
|
|
@@ -241,12 +247,12 @@ export class VersionRegistry {
|
|
|
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]);
|
|
|
+ existingVersion.parents.push([parent, delta, embeddings]);
|
|
|
parent.children.push([existingVersion, delta]);
|
|
|
}
|
|
|
return existingVersion;
|
|
|
} else {
|
|
|
- const version = new Version([[parent, delta]], embeddings, newHash, parent.size + 1);
|
|
|
+ const version = new Version([[parent, delta, embeddings]], newHash, parent.size + 1);
|
|
|
this.putVersion(newHash, version);
|
|
|
return version;
|
|
|
}
|