version.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.VersionRegistry = exports.Version = void 0;
  4. const util_1 = require("util"); // NodeJS library
  5. const buffer_1 = require("buffer"); // NodeJS library
  6. const dfs_1 = require("./util/dfs");
  7. const permutations_1 = require("./util/permutations");
  8. const buffer_xor_1 = require("./util/buffer_xor");
  9. // not exported -> use VersionRegistry to create versions
  10. class Version {
  11. // DO NOT USE constructor directly - instead use VersionRegistry.createVersion.
  12. constructor(parents, hash, size, embeddings) {
  13. this.children = []; // reverse parents
  14. this.reverseEmbeddings = new Map(); // (host) versions in which this (guest) version is embedded.
  15. this.implicitSelfEmbedding = {
  16. version: this,
  17. overridings: new Map(), // no overrides
  18. };
  19. this.parents = parents;
  20. this.hash = hash;
  21. this.size = size;
  22. this.embeddings = embeddings(this);
  23. }
  24. // Returns iterator that yields all deltas of this version, from recent to early.
  25. // Or put more precisely: a delta's dependencies will be yielded AFTER the delta itself.
  26. *[Symbol.iterator]() {
  27. let current = this;
  28. while (current.parents.length !== 0) {
  29. // 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.
  30. const [parent, delta] = current.parents[0];
  31. yield delta;
  32. current = parent;
  33. }
  34. }
  35. *iterPrimitiveDeltas() {
  36. const executionOrder = [...this].reverse();
  37. for (const d of executionOrder) {
  38. yield* d.iterPrimitiveDeltas();
  39. }
  40. }
  41. [util_1.inspect.custom](depth, options) {
  42. return "Version{" + [...this].map(d => (0, util_1.inspect)(d, options)).join(",") + "}";
  43. }
  44. isSubSetOf(otherVersion) {
  45. // current implementation is probably quite slow
  46. for (const delta of this) {
  47. if (!otherVersion.contains(delta)) {
  48. return false;
  49. }
  50. }
  51. return true;
  52. }
  53. contains(delta) {
  54. for (const d of this) {
  55. if (d === delta) {
  56. return true;
  57. }
  58. }
  59. return false;
  60. }
  61. containsPrimitive(delta) {
  62. for (const d of this) {
  63. for (const p of d.iterPrimitiveDeltas()) {
  64. if (p === delta) {
  65. return true;
  66. }
  67. }
  68. }
  69. return false;
  70. }
  71. // Returns a sequence of Delta's to be undone/redone to go from this version to the 'otherVersion'
  72. // Returned sequence is empty if otherVersion === this
  73. // Returns undefined when there is no path (which is impossible if both versions are part of the same VersionRegistry).
  74. // TODO: implement Breadth-First Search (BFS) for better performance.
  75. findPathTo(otherVersion) {
  76. const getNeighbors = (v) => {
  77. const parentVersions = v.parents.map(([parentVersion, delta]) => [['p', delta, parentVersion], parentVersion]);
  78. const childVersions = v.children.map(([childVersion, delta]) => [['c', delta, childVersion], childVersion]);
  79. // heuristic: look in newer or older versions first?
  80. if (v.size < otherVersion.size) {
  81. // make the common case fast: most of the time,
  82. // we just want to advance to the next version (i.e., after a user edit)
  83. return [...childVersions, ...parentVersions];
  84. }
  85. else {
  86. return [...parentVersions, ...childVersions];
  87. }
  88. };
  89. // console.time("findDFS")
  90. const result = (0, dfs_1.findDFS)(this, otherVersion, getNeighbors);
  91. // console.timeEnd("findDFS");
  92. // if (result)
  93. // console.log("findPath:", result.map(([linkType])=>linkType));
  94. return result;
  95. }
  96. // Like findPathTo, but only searches 'down' (younger versions).
  97. findDescendant(otherVersion) {
  98. const getNeighbors = (v) => {
  99. const result = v.children.map(([childVersion, delta]) => [['c', delta, childVersion], childVersion]);
  100. return result;
  101. };
  102. return (0, dfs_1.findDFS)(this, otherVersion, getNeighbors);
  103. }
  104. getEmbedding(key) {
  105. return this.embeddings.get(key) || this.implicitSelfEmbedding;
  106. }
  107. getReverseEmbeddings(key) {
  108. return this.reverseEmbeddings.get(key) || [this];
  109. }
  110. // Serialize a path of Deltas from a Version in alreadyHave, to fully reconstruct this version.
  111. serialize(alreadyHave = new Set()) {
  112. const deltas = [];
  113. const versions = [];
  114. this.serializeInternal(new Set(alreadyHave), new Set(), deltas, versions);
  115. return {
  116. externalDependencies: [...alreadyHave].map(v => v.hash.toString('hex')),
  117. deltas,
  118. versions,
  119. };
  120. }
  121. serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions) {
  122. if (alreadyHaveVersions.has(this)) {
  123. return;
  124. }
  125. alreadyHaveVersions.add(this);
  126. const embeddings = new Array();
  127. for (const [guestId, { version, overridings }] of this.embeddings) {
  128. version.serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions);
  129. const ovr = {};
  130. for (const [key, val] of overridings.entries()) {
  131. ovr[key.hash.toString('hex')] = val.hash.toString('hex');
  132. }
  133. embeddings.push({ guestId, v: version.hash.toString('hex'), ovr });
  134. }
  135. if (this.parents.length > 0) {
  136. const [parentVersion, delta] = this.parents[0];
  137. parentVersion.serializeInternal(alreadyHaveVersions, alreadyHaveDeltas, deltas, versions);
  138. function visitDelta(delta) {
  139. for (const d of delta.getDependencies()) {
  140. visitDelta(d);
  141. }
  142. if (!alreadyHaveDeltas.has(delta)) {
  143. deltas.push(delta.serialize());
  144. alreadyHaveDeltas.add(delta);
  145. }
  146. }
  147. visitDelta(delta);
  148. versions.push({
  149. id: this.hash.toString('hex'),
  150. parent: parentVersion.hash.toString('hex'),
  151. delta: delta.hash.toString('hex'),
  152. embeddings,
  153. });
  154. }
  155. }
  156. }
  157. exports.Version = Version;
  158. const initialHash = buffer_1.Buffer.alloc(32); // all zeros
  159. function isConflicting(deltaA, deltaB) {
  160. // for performance, iterate over the delta that has the least conflicts:
  161. if (deltaA.conflictsWith.length < deltaB.conflictsWith.length) {
  162. return deltaA.conflictsWith.some(([d]) => d === deltaB);
  163. }
  164. else {
  165. return deltaB.conflictsWith.some(([d]) => d === deltaA);
  166. }
  167. }
  168. class VersionRegistry {
  169. constructor() {
  170. this.initialVersion = new Version([], initialHash, 0, () => new Map());
  171. // Maps version ID (as string, because a Buffer cannot be a map key) to Version
  172. this.versionMap = new Map([
  173. [initialHash.toString('hex'), this.initialVersion], // the initial version, always already there
  174. ]);
  175. }
  176. lookupOptional(hash) {
  177. return this.versionMap.get(hash.toString('hex'));
  178. }
  179. lookup(hash) {
  180. const hex = hash.toString('hex');
  181. const version = this.versionMap.get(hex);
  182. if (version === undefined) {
  183. throw new Error("no such version: " + hex);
  184. }
  185. return version;
  186. }
  187. putVersion(hash, version) {
  188. this.versionMap.set(hash.toString('hex'), version);
  189. }
  190. // Idempotent
  191. // Pre-condition 1: all of the dependencies of delta must exist in parent.
  192. // Pre-condition 2: delta must be non-conflicting with any delta in parent.
  193. // Pre-condition 3: if the to-be-created ("host") version embeds other ("guest") versions,
  194. // then all of the guest versions' deltas must exist in the host version, or be explicitly overridden.
  195. createVersion(parent, delta, embeddings = () => new Map()) {
  196. // Check pre-condition 1:
  197. for (const [dep] of delta.getDependencies()) {
  198. if (!parent.contains(dep)) {
  199. throw new Error("createVersion: precondition failed: Missing dependency: " + dep.description);
  200. }
  201. }
  202. // Check pre-condition 2:
  203. for (const [conflictingDelta] of delta.conflictsWith) {
  204. if (parent.contains(conflictingDelta)) {
  205. throw new Error("createVersion: precondition failed: Delta " + delta.description + " conflicts with " + conflictingDelta.description);
  206. }
  207. }
  208. const newVersion = this.createVersionUnsafe(parent, delta, embeddings);
  209. // Check pre-condition 3:
  210. const primitiveDeltas = [...delta.iterPrimitiveDeltas()];
  211. for (const [guestId, { version: guest, overridings }] of newVersion.embeddings.entries()) {
  212. const { version: guestParent } = parent.getEmbedding(guestId);
  213. const guestDiff = guestParent.findDescendant(guest);
  214. for (const [_, guestDelta] of guestDiff) {
  215. for (const guestPDelta of guestDelta.iterPrimitiveDeltas()) {
  216. if (!primitiveDeltas.includes(overridings.get(guestPDelta) || guestPDelta)) {
  217. throw new Error("createVersion: precondition failed: Guest's primitive delta " + guestPDelta.description + " does not occur in host, nor is it overridden.");
  218. }
  219. }
  220. }
  221. }
  222. return newVersion;
  223. }
  224. // Faster than createVersion, but does not check pre-conditions.
  225. // Idempotent
  226. createVersionUnsafe(parent, delta, embeddings = () => new Map()) {
  227. const newHash = (0, buffer_xor_1.bufferXOR)(parent.hash, delta.hash);
  228. // TODO: include embeddings in hash digest.
  229. const existingVersion = this.lookupOptional(newHash);
  230. if (existingVersion !== undefined) {
  231. // this Version already exists
  232. const havePath = existingVersion.parents.some(([parentVersion, delta]) => parentVersion === parent);
  233. if (!havePath) {
  234. // but the path is new (there can be multiple 'paths' to the same version, because of commutation of deltas)
  235. existingVersion.parents.push([parent, delta]);
  236. parent.children.push([existingVersion, delta]);
  237. }
  238. for (const [guestId, { version, overridings }] of embeddings(existingVersion)) {
  239. const found = existingVersion.embeddings.get(guestId);
  240. if (!found) {
  241. existingVersion.embeddings.set(guestId, { version, overridings });
  242. // throw new Error("Assertion failed: created version already exists, but does not embed '" + guestId + "'");
  243. }
  244. else {
  245. const { version: v, overridings: o } = found;
  246. if (v !== version) {
  247. throw new Error("Assertion failed: created version already exists and embeds a differrent version '" + guestId + "'");
  248. }
  249. // Merge overridings:
  250. for (const [guestDelta, hostDelta] of overridings.entries()) {
  251. const alreadyHostDelta = o.get(guestDelta) || hostDelta;
  252. if (hostDelta !== alreadyHostDelta) {
  253. throw new Error("Assertion failed: created version already exists BUT overrides delta '" + guestDelta.description + "' with another delta.");
  254. }
  255. o.set(guestDelta, hostDelta);
  256. }
  257. }
  258. }
  259. return existingVersion;
  260. }
  261. else {
  262. const newVersion = new Version([[parent, delta]], newHash, parent.size + 1, embeddings);
  263. // Create reverse parent links:
  264. for (const [parent, delta] of newVersion.parents) {
  265. parent.children.push([newVersion, delta]);
  266. }
  267. // Create reverse embedding:
  268. for (const [key, embedding] of newVersion.embeddings.entries()) {
  269. const reverse = embedding.version.reverseEmbeddings.get(key);
  270. if (reverse !== undefined) {
  271. reverse.push(newVersion);
  272. }
  273. else {
  274. embedding.version.reverseEmbeddings.set(key, [newVersion]);
  275. }
  276. }
  277. this.putVersion(newHash, newVersion);
  278. return newVersion;
  279. }
  280. }
  281. // Mostly used for testing purposes.
  282. // Order of deltas should be recent -> early
  283. // Or put more precisely: a delta's dependencies should occur AFTER the delta in the array.
  284. quickVersion(deltas) {
  285. return deltas.reduceRight((parentVersion, delta) => this.createVersion(parentVersion, delta), this.initialVersion);
  286. }
  287. // Get the version whose deltas are a subset of all given versions AKA the 'largest common ancestor'.
  288. getIntersection(versions) {
  289. // treat special case first:
  290. if (versions.length === 0) {
  291. return this.initialVersion;
  292. }
  293. // sort versions (out place) from few deltas to many (FASTEST):
  294. const sortedVersions = versions.slice().sort((versionA, versionB) => versionA.size - versionB.size);
  295. const intersection = [];
  296. for (const delta of sortedVersions[0]) {
  297. let allVersionsHaveIt = true;
  298. for (let i = 1; i < sortedVersions.length; i++) {
  299. let thisVersionHasIt = false;
  300. for (const otherDelta of sortedVersions[i]) {
  301. if (delta === otherDelta) {
  302. thisVersionHasIt = true;
  303. break;
  304. }
  305. }
  306. if (!thisVersionHasIt) {
  307. allVersionsHaveIt = false;
  308. break;
  309. }
  310. }
  311. if (allVersionsHaveIt) {
  312. intersection.push(delta);
  313. }
  314. }
  315. return this.quickVersion(intersection);
  316. }
  317. getLCAInternal(versionA, versionB) {
  318. const ancestorsA = [versionA];
  319. const ancestorsB = [versionB];
  320. while (true) {
  321. const a = ancestorsA[ancestorsA.length - 1];
  322. // @ts-ignore: TypeScript doesn't know about 'findLast' method yet.
  323. if (ancestorsB.findLast(v => v === a)) {
  324. return a;
  325. }
  326. if (a.parents.length > 0) {
  327. const [parent] = a.parents[0];
  328. ancestorsA.push(parent);
  329. }
  330. const b = ancestorsB[ancestorsB.length - 1];
  331. // @ts-ignore: TypeScript doesn't know about 'findLast' method yet.
  332. if (ancestorsA.findLast(v => v === b)) {
  333. return b;
  334. }
  335. if (b.parents.length > 0) {
  336. const [parent] = b.parents[0];
  337. ancestorsB.push(parent);
  338. }
  339. }
  340. }
  341. getLCA(versions) {
  342. if (versions.length === 0) {
  343. return this.initialVersion;
  344. }
  345. return versions.reduce((a, b) => this.getLCAInternal(a, b));
  346. }
  347. // Idempotent
  348. // Of the union of all deltas of the versions given, compute the maximal left-closed conflict-free subsets.
  349. // These are the subsets to which no delta can be added without introducing a conflict or missing dependency.
  350. merge(versions, debugNames) {
  351. function printDebug(...args) {
  352. if (debugNames !== undefined) {
  353. for (const [i, arg] of args.entries()) {
  354. try {
  355. const name = debugNames(arg);
  356. if (name !== undefined) {
  357. args[i] = name;
  358. }
  359. }
  360. catch (e) { }
  361. }
  362. console.log(...args);
  363. }
  364. }
  365. const lca = this.getLCA(versions);
  366. // ATTENTION: Constructing 'diff' must be made recursive (guest could also be a host)
  367. function visitEmbeddings(currentHost, nextHost) {
  368. const guestDeltaMap = new Map();
  369. for (const [guestId, { version: nextGuest, overridings }] of nextHost.embeddings.entries()) {
  370. const currentGuest = currentHost.getEmbedding(guestId).version;
  371. const guestPath = currentGuest.findDescendant(nextGuest);
  372. if (guestPath.length > 1) {
  373. throw new Error("Did not expect guestPath to be longer than one delta");
  374. }
  375. if (guestPath.length === 1) {
  376. const guestDelta = guestPath[0][1];
  377. if (currentHost === currentGuest && nextHost === nextGuest) {
  378. // prevent infinite recursion in case of self-embedding:
  379. guestDeltaMap.set(guestId, [[guestDelta, guestDeltaMap], overridings]);
  380. }
  381. else {
  382. const recursiveGuestDeltaMap = visitEmbeddings(currentGuest, nextGuest);
  383. guestDeltaMap.set(guestId, [[guestDelta, recursiveGuestDeltaMap], overridings]);
  384. }
  385. }
  386. else {
  387. guestDeltaMap.set(guestId, [null, new Map()]);
  388. }
  389. }
  390. return guestDeltaMap;
  391. }
  392. let diff = [];
  393. for (const v of versions) {
  394. const path = lca.findDescendant(v);
  395. // all deltas on path from lca to 'v':
  396. let currentVersion = lca;
  397. for (const [_, delta, nextVersion] of path) {
  398. const guestDeltaMap = visitEmbeddings(currentVersion, nextVersion);
  399. // The following condition may be wrong, check this later:
  400. if (!diff.some(([d]) => d === delta)) {
  401. diff.push([delta, guestDeltaMap]);
  402. }
  403. currentVersion = nextVersion;
  404. }
  405. }
  406. // Now we're ready to actually start merging...
  407. const result = [];
  408. // Recursively attempts to add deltas from 'deltasToTry' to 'startVersion'
  409. // When nothing can be added anymore without introducing a conflict, we have a 'maximal version' (a result).
  410. // It's possible that there is more than one 'maximal version'.
  411. // Precondition: We assume that any single delta of 'deltasToTry' will not be conflicting with 'startVersion'.
  412. const depthFirst = (startVersion, candidates, depth) => {
  413. function printIndent(...args) {
  414. printDebug(" ".repeat(depth), ...args);
  415. }
  416. // printIndent("deltasToTry=", ...candidates.map(([d])=>d));
  417. let couldNotRecurse = true;
  418. for (const [delta, guestDeltaMap] of candidates) {
  419. const haveMissingDependency = delta.getDependencies().some(([dependency]) => !startVersion.contains(dependency));
  420. if (haveMissingDependency) {
  421. printIndent("missing dependency, trying next delta");
  422. continue; // skip this delta, but keep it in deltasToTry (its missing dependency may be in deltasToTry)
  423. }
  424. // ATTENTION: Constructing embeddings must be made recursive (guest could also be a host)
  425. // 'delta' can be added
  426. // printIndent("including", delta, " => new version: ", delta, ...startVersion);
  427. const createMergedVersionRecursive = (startVersion, [delta, guestDeltaMap]) => {
  428. const embeddings = new Map();
  429. const selfEmbeddingKeys = new Set();
  430. for (const [guestId, [guestDelta, overridings]] of guestDeltaMap.entries()) {
  431. const { version: guestStartVersion } = startVersion.getEmbedding(guestId);
  432. // console.log(guestId, "guestStartVersion: ", guestStartVersion);
  433. let nextGuestVersion;
  434. if (guestDelta === null) {
  435. nextGuestVersion = guestStartVersion;
  436. }
  437. else {
  438. if (guestStartVersion === startVersion && guestDelta[0] === delta && guestDelta[1] === guestDeltaMap) {
  439. selfEmbeddingKeys.add(guestId);
  440. continue;
  441. }
  442. else {
  443. nextGuestVersion = createMergedVersionRecursive(guestStartVersion, guestDelta);
  444. }
  445. }
  446. embeddings.set(guestId, { version: nextGuestVersion, overridings });
  447. }
  448. // printIndent("Creating version (", delta, ...[...startVersion].map(d => d), ") with embeddings", embeddings);
  449. const nextVersion = this.createVersion(startVersion, delta, newVersion => {
  450. // add self-embeddings:
  451. for (const guestId of selfEmbeddingKeys) {
  452. embeddings.set(guestId, { version: newVersion, overridings: new Map() });
  453. }
  454. return embeddings;
  455. });
  456. // printIndent("Created version (", ...[...nextVersion].map(d => d), ") with embeddings", embeddings);
  457. return nextVersion;
  458. };
  459. const nextVersion = createMergedVersionRecursive(startVersion, [delta, guestDeltaMap]);
  460. // current delta does not have to be included again in next loop iterations
  461. candidates = candidates.filter(([d]) => d !== delta);
  462. // const [conflicting, nonConflicting] = _.partition(deltasToTry, ([d]) => isConflicting(d, delta));
  463. const nonConflicting = candidates.filter(([candidate]) => !isConflicting(candidate, delta));
  464. // if (conflicting.length > 0)
  465. // printIndent("will be skipped (conflicts with", delta, "):", ...conflicting);
  466. depthFirst(nextVersion, nonConflicting, depth + 1);
  467. couldNotRecurse = false;
  468. if (nonConflicting.length === candidates.length) {
  469. // all deltas from deltasToTry were included -> no need to try alternatives
  470. break;
  471. }
  472. }
  473. if (couldNotRecurse) {
  474. // possibly have a new maximal version
  475. if (!result.some(v => startVersion.isSubSetOf(v))) {
  476. // printIndent("new result");
  477. result.push(startVersion);
  478. }
  479. }
  480. };
  481. depthFirst(lca, diff, 0);
  482. // printDebug("result of merge:", ..._.flatten(result.map(v => [...v, ","])));
  483. return result;
  484. }
  485. // Idempotent
  486. // Calls the merge-function for every permutation of 'versions'
  487. // This is useful for didactic purposes: a path is created from every input to at least one output.
  488. // Of course it won't scale to many inputs.
  489. crazyMerge(versions, debugNames) {
  490. let result;
  491. for (const v of (0, permutations_1.permutations)(versions)) {
  492. // whatever the permutation, result will always be the same:
  493. result = this.merge(v, debugNames);
  494. }
  495. return result;
  496. }
  497. }
  498. exports.VersionRegistry = VersionRegistry;
  499. //# sourceMappingURL=version.js.map