addons/isl/src/dag/mutation_dag.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {Hash} from '../types';
b69ab319import type {HashWithParents} from './base_dag';
b69ab3110
b69ab3111import {BaseDag} from './base_dag';
b69ab3112import {Ancestor, AncestorType} from './render';
b69ab3113import {TextRenderer} from './renderText';
b69ab3114
b69ab3115/**
b69ab3116 * Dag that tracks predecessor -> successor relationships.
b69ab3117 *
b69ab3118 * Predecessors are "ancestors". Successors are "descendants".
b69ab3119 *
b69ab3120 * This graph can contain hashes that were deleted in the main graph.
b69ab3121 * For example, amend A1 to A2 to A3 to A4. This graph will keep all
b69ab3122 * 4 hashes so it can answer questions like "I had A1, what is it now?"
b69ab3123 * (A4) when the main graph only contains A4.
b69ab3124 *
b69ab3125 * In the above example, the user can also hide A4 and review A3
b69ab3126 * (ex. undo, or hide when A3 has visible descendants). In this case,
b69ab3127 * "what A1 becomes?" should be A3. So the followSuccessor()
b69ab3128 * implementation should consider the mainDag, like:
b69ab3129 *
b69ab3130 * // Consider what is present in the mainDag.
b69ab3131 * mutationDag.heads(mutationDag.range(start, mainDag))
b69ab3132 *
b69ab3133 * not:
b69ab3134 *
b69ab3135 * // BAD: Only consider mutationDag, might return hashes
b69ab3136 * // missing in the mainDag.
b69ab3137 * mutationDag.heads(mutationDag.descendants(start))
b69ab3138 *
b69ab3139 * Note: "dag" is a lossy view for actual mutation data. For example,
b69ab3140 * it does not distinguish between:
b69ab3141 * - amending A to A1, amending A to A2 (considered "divergence")
b69ab3142 * - splitting A into A1 and A2 (not considered as a "divergence")
b69ab3143 * Be careful when it is necessary to distinguish between them.
b69ab3144 */
b69ab3145export class MutationDag extends BaseDag<HashWithParents> {
b69ab3146 constructor(baseDag?: BaseDag<HashWithParents>) {
b69ab3147 const record = baseDag?.inner;
b69ab3148 super(record);
b69ab3149 }
b69ab3150
b69ab3151 /**
b69ab3152 * Insert old->new mappings to the mutation dag.
b69ab3153 *
b69ab3154 * Note about `oldNewPairs`:
b69ab3155 * - a same 'new' can have multiple 'old's (ex. fold)
b69ab3156 * - a same 'old' can have multiple 'new's (ex. split)
b69ab3157 * So the `oldNewPairs` should not be `Map`s with unique keys.
b69ab3158 */
b69ab3159 addMutations(oldNewPairs: Iterable<[Hash, Hash]>): MutationDag {
b69ab3160 const infoMap: Map<Hash, HashWithParents> = new Map();
b69ab3161 const insert = (hash: Hash, parents: Hash[]) => {
b69ab3162 // Insert `hash` to the infoMap on demand.
b69ab3163 let info = infoMap.get(hash);
b69ab3164 if (info == null) {
b69ab3165 info = {
b69ab3166 hash,
b69ab3167 parents: this.get(hash)?.parents ?? [],
b69ab3168 grandparents: this.get(hash)?.grandparents ?? [],
b69ab3169 };
b69ab3170 infoMap.set(hash, info);
b69ab3171 }
b69ab3172 // Append parents.
b69ab3173 if (parents.length > 0) {
b69ab3174 info.parents = Array.from(new Set(info.parents.concat(parents)));
b69ab3175 }
b69ab3176 };
b69ab3177 for (const [oldHash, newHash] of oldNewPairs) {
b69ab3178 insert(newHash, [oldHash]);
b69ab3179 insert(oldHash, []);
b69ab3180 }
b69ab3181 const baseDag = this.add(infoMap.values());
b69ab3182 return new MutationDag(baseDag);
b69ab3183 }
b69ab3184
b69ab3185 /** Provided extra fields for debugging use-case. For now, this includes an ASCII graph. */
b69ab3186 getDebugState(): {rendered: Array<string>} {
b69ab3187 const renderer = new TextRenderer();
b69ab3188 const rendered = [];
b69ab3189
b69ab3190 // Render row by row. The main complexity is to figure out the "ancestors",
b69ab3191 // especially when the provided `set` is a subset of the dag.
b69ab3192 for (const hash of this.sortDesc(this, {gap: false})) {
b69ab3193 const parents = this.parentHashes(hash);
b69ab3194 const typedParents: Ancestor[] = parents.map(
b69ab3195 hash => new Ancestor({type: AncestorType.Parent, hash}),
b69ab3196 );
b69ab3197 const renderedRow = renderer.nextRow(hash, typedParents, hash);
b69ab3198 for (const line of renderedRow.trimEnd().split('\n')) {
b69ab3199 rendered.push(line);
b69ab31100 }
b69ab31101 }
b69ab31102 return {rendered};
b69ab31103 }
b69ab31104}