addons/isl/src/dag/__tests__/dag.test.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
b69ab318// @lint-ignore-every SPELL
b69ab319
b69ab3110import type {CommitInfo, CommitPhaseType, Hash, SuccessorInfo} from '../../types';
b69ab3111import type {SetLike} from '../set';
b69ab3112
b69ab3113import {BaseDag} from '../base_dag';
b69ab3114import {Dag, DagCommitInfo, REBASE_SUCC_PREFIX} from '../dag';
b69ab3115import {HashSet} from '../set';
b69ab3116
b69ab3117const toInfo = (info: Partial<CommitInfo>): DagCommitInfo => DagCommitInfo.fromCommitInfo(info);
b69ab3118const DRAFT: CommitPhaseType = 'draft';
b69ab3119const PUBLIC: CommitPhaseType = 'public';
b69ab3120
b69ab3121describe('Dag', () => {
b69ab3122 // Dummy info.
b69ab3123 const date = new Date(42);
b69ab3124 const info: CommitInfo = {
b69ab3125 title: '',
b69ab3126 hash: '',
b69ab3127 parents: [],
b69ab3128 grandparents: [],
b69ab3129 phase: DRAFT,
b69ab3130 isDot: false,
b69ab3131 author: '',
b69ab3132 date,
b69ab3133 description: '',
b69ab3134 bookmarks: [],
b69ab3135 remoteBookmarks: [],
b69ab3136 totalFileCount: 0,
b69ab3137 filePathsSample: [],
b69ab3138 maxCommonPathPrefix: '',
b69ab3139 };
b69ab3140
b69ab3141 describe('basic queries', () => {
b69ab3142 const dagAbc = new BaseDag().add([
b69ab3143 {hash: 'a', parents: ['z'], grandparents: []},
b69ab3144 {hash: 'b', parents: ['a'], grandparents: []},
b69ab3145 {hash: 'c', parents: ['b', 'a'], grandparents: []},
b69ab3146 ]);
b69ab3147
b69ab3148 it('maintains parent<->child mappings', () => {
b69ab3149 const dag = dagAbc;
b69ab3150 expect(dag.parentHashes('a')).toEqual([]);
b69ab3151 expect(dag.parentHashes('b')).toEqual(['a']);
b69ab3152 expect(dag.parentHashes('c')).toEqual(['b', 'a']);
b69ab3153 expect(dag.parentHashes('d')).toEqual([]);
b69ab3154 expect(dag.parentHashes('z')).toEqual([]);
b69ab3155 expect(dag.childHashes('a').toArray()).toEqual(['b', 'c']);
b69ab3156 expect(dag.childHashes('b').toArray()).toEqual(['c']);
b69ab3157 expect(dag.childHashes('c').toArray()).toEqual([]);
b69ab3158 expect(dag.childHashes('d').toArray()).toEqual([]);
b69ab3159 expect(dag.childHashes('z').toArray()).toEqual([]);
b69ab3160 });
b69ab3161
b69ab3162 it('maintains parent<->child mappings after remove()', () => {
b69ab3163 const dag = dagAbc.remove(['b']);
b69ab3164 expect(dag.parentHashes('c')).toEqual(['a']);
b69ab3165 expect(dag.childHashes('a').toArray()).toEqual(['c']);
b69ab3166 expect(dag.parentHashes('b')).toEqual([]);
b69ab3167 expect(dag.childHashes('b').toArray()).toEqual([]);
b69ab3168 });
b69ab3169
b69ab3170 it('removes conflicted commits', () => {
b69ab3171 const dag = dagAbc.add([{hash: 'c', parents: [], grandparents: []}]);
b69ab3172 expect(dag.parentHashes('c')).toEqual([]);
b69ab3173 expect(dag.childHashes('b').toArray()).toEqual([]);
b69ab3174 });
b69ab3175
b69ab3176 it('supports replaceWith()', () => {
b69ab3177 const dag = new Dag()
b69ab3178 .add(
b69ab3179 [
b69ab3180 {...info, hash: 'a', parents: ['z']},
b69ab3181 {...info, hash: 'b', parents: ['a']},
b69ab3182 {...info, hash: 'c', parents: ['b', 'a']},
b69ab3183 ].map(toInfo),
b69ab3184 )
b69ab3185 .replaceWith(['c', 'd'], (h, _c) =>
b69ab3186 toInfo({
b69ab3187 ...info,
b69ab3188 hash: h,
b69ab3189 parents: ['b'],
b69ab3190 }),
b69ab3191 ).commitDag;
b69ab3192 expect(dag.parentHashes('c')).toEqual(['b']);
b69ab3193 expect(dag.parentHashes('d')).toEqual(['b']);
b69ab3194 expect(dag.childHashes('a').toArray()).toEqual(['b']);
b69ab3195 expect(dag.childHashes('b').toArray()).toEqual(['c', 'd']);
b69ab3196 });
b69ab3197 });
b69ab3198
b69ab3199 describe('high-level queries', () => {
b69ab31100 /**
b69ab31101 * A--B--C--F
b69ab31102 * \ /
b69ab31103 * D-E--G
b69ab31104 */
b69ab31105 const dag = new Dag().add(
b69ab31106 [
b69ab31107 {...info, hash: 'a', parents: []},
b69ab31108 {...info, hash: 'b', parents: ['a']},
b69ab31109 {...info, hash: 'c', parents: ['b']},
b69ab31110 {...info, hash: 'd', parents: ['b']},
b69ab31111 {...info, hash: 'e', parents: ['d']},
b69ab31112 {...info, hash: 'f', parents: ['c', 'e']},
b69ab31113 {...info, hash: 'g', parents: ['e']},
b69ab31114 ].map(toInfo),
b69ab31115 );
b69ab31116
b69ab31117 it('parents()', () => {
b69ab31118 expect(dag.parents('f').toSortedArray()).toEqual(['c', 'e']);
b69ab31119 expect(dag.parents(['b', 'c', 'f']).toSortedArray()).toEqual(['a', 'b', 'c', 'e']);
b69ab31120 });
b69ab31121
b69ab31122 it('children()', () => {
b69ab31123 expect(dag.children('b').toSortedArray()).toEqual(['c', 'd']);
b69ab31124 expect(dag.children(['a', 'b', 'd']).toSortedArray()).toEqual(['b', 'c', 'd', 'e']);
b69ab31125 });
b69ab31126
b69ab31127 it('ancestors()', () => {
b69ab31128 expect(dag.ancestors('c').toSortedArray()).toEqual(['a', 'b', 'c']);
b69ab31129 expect(dag.ancestors('f').toSortedArray()).toEqual(['a', 'b', 'c', 'd', 'e', 'f']);
b69ab31130 expect(dag.ancestors('g').toSortedArray()).toEqual(['a', 'b', 'd', 'e', 'g']);
b69ab31131 expect(dag.ancestors('f', {within: ['a', 'c', 'd', 'e']}).toSortedArray()).toEqual([
b69ab31132 'c',
b69ab31133 'd',
b69ab31134 'e',
b69ab31135 'f',
b69ab31136 ]);
b69ab31137 });
b69ab31138
b69ab31139 it('descendants()', () => {
b69ab31140 expect(dag.descendants('a').toSortedArray()).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g']);
b69ab31141 expect(dag.descendants('c').toSortedArray()).toEqual(['c', 'f']);
b69ab31142 expect(dag.descendants('d').toSortedArray()).toEqual(['d', 'e', 'f', 'g']);
b69ab31143 expect(dag.descendants('b', {within: ['c', 'd', 'g']}).toSortedArray()).toEqual([
b69ab31144 'b',
b69ab31145 'c',
b69ab31146 'd',
b69ab31147 ]);
b69ab31148 });
b69ab31149
b69ab31150 it('heads()', () => {
b69ab31151 expect(dag.heads(['a', 'b', 'c']).toSortedArray()).toEqual(['c']);
b69ab31152 expect(dag.heads(['d', 'e', 'g']).toSortedArray()).toEqual(['g']);
b69ab31153 expect(dag.heads(['e', 'f', 'g']).toSortedArray()).toEqual(['f', 'g']);
b69ab31154 expect(dag.heads(['c', 'e', 'f']).toSortedArray()).toEqual(['f']);
b69ab31155 });
b69ab31156
b69ab31157 it('roots()', () => {
b69ab31158 expect(dag.roots(['a', 'b', 'c']).toSortedArray()).toEqual(['a']);
b69ab31159 expect(dag.roots(['d', 'e', 'g']).toSortedArray()).toEqual(['d']);
b69ab31160 expect(dag.roots(['e', 'f', 'g']).toSortedArray()).toEqual(['e']);
b69ab31161 expect(dag.roots(['c', 'e', 'f']).toSortedArray()).toEqual(['c', 'e']);
b69ab31162 });
b69ab31163
b69ab31164 it('range()', () => {
b69ab31165 expect(dag.range('a', 'c').toSortedArray()).toEqual(['a', 'b', 'c']);
b69ab31166 expect(dag.range('a', 'f').toSortedArray()).toEqual(['a', 'b', 'c', 'd', 'e', 'f']);
b69ab31167 expect(dag.range('b', 'g').toSortedArray()).toEqual(['b', 'd', 'e', 'g']);
b69ab31168 expect(dag.range(['a', 'b'], ['a', 'b']).toSortedArray()).toEqual(['a', 'b']);
b69ab31169 });
b69ab31170
b69ab31171 it('gca()', () => {
b69ab31172 expect(dag.gca('f', 'g').toSortedArray()).toEqual(['e']);
b69ab31173 expect(dag.gca('f', 'e').toSortedArray()).toEqual(['e']);
b69ab31174 expect(dag.gca('c', 'e').toSortedArray()).toEqual(['b']);
b69ab31175 });
b69ab31176
b69ab31177 it('isAncestor()', () => {
b69ab31178 expect(dag.isAncestor('a', 'a')).toBe(true);
b69ab31179 expect(dag.isAncestor('b', 'g')).toBe(true);
b69ab31180 expect(dag.isAncestor('d', 'f')).toBe(true);
b69ab31181 expect(dag.isAncestor('c', 'g')).toBe(false);
b69ab31182 expect(dag.isAncestor('g', 'a')).toBe(false);
b69ab31183 });
b69ab31184
b69ab31185 it('supports present()', () => {
b69ab31186 expect(dag.present(['a', 'x']).toSortedArray()).toEqual(['a']);
b69ab31187 });
b69ab31188
b69ab31189 it('does not infinite loop on cyclic graphs', () => {
b69ab31190 const dag = new BaseDag().add([
b69ab31191 {hash: 'a', parents: ['b'], grandparents: []},
b69ab31192 {hash: 'b', parents: ['c'], grandparents: []},
b69ab31193 {hash: 'c', parents: ['a'], grandparents: []},
b69ab31194 ]);
b69ab31195 expect(dag.ancestors('b').toSortedArray()).toEqual(['a', 'b', 'c']);
b69ab31196 expect(dag.descendants('b').toSortedArray()).toEqual(['a', 'b', 'c']);
b69ab31197 expect(dag.isAncestor('a', 'c')).toBe(true);
b69ab31198 expect(dag.isAncestor('c', 'a')).toBe(true);
b69ab31199 });
b69ab31200
b69ab31201 it('preserves order for ancestors() or descendants()', () => {
b69ab31202 // a--b--c
b69ab31203 const dag = new BaseDag().add([
b69ab31204 {...info, hash: 'a', parents: []},
b69ab31205 {...info, hash: 'b', parents: ['a']},
b69ab31206 {...info, hash: 'c', parents: ['b']},
b69ab31207 ]);
b69ab31208 expect(dag.ancestors('c').toArray()).toEqual(['c', 'b', 'a']);
b69ab31209 expect(dag.ancestors('c').reverse().toArray()).toEqual(['a', 'b', 'c']);
b69ab31210 expect(dag.descendants('a').toArray()).toEqual(['a', 'b', 'c']);
b69ab31211 });
b69ab31212 });
b69ab31213
b69ab31214 describe('sort', () => {
b69ab31215 // a--b--c--d---e--f
b69ab31216 // \ /
b69ab31217 // y--x--w--v--u
b69ab31218 const dag = new BaseDag().add(
b69ab31219 Object.entries({
b69ab31220 a: '',
b69ab31221 b: 'a',
b69ab31222 c: 'b',
b69ab31223 d: 'c',
b69ab31224 e: 'd v',
b69ab31225 f: 'e',
b69ab31226 y: '',
b69ab31227 x: 'y b',
b69ab31228 w: 'x',
b69ab31229 v: 'w',
b69ab31230 u: 'v',
b69ab31231 }).map(([hash, v]) => {
b69ab31232 return {hash, parents: v.split(' '), grandparents: []};
b69ab31233 }),
b69ab31234 );
b69ab31235
b69ab31236 it('asc', () => {
b69ab31237 const sort = (s: SetLike) => dag.sortAsc(s).join(' ');
b69ab31238 // sort all
b69ab31239 expect(sort(dag)).toBe('a b c d y x w v e f u');
b69ab31240 // sort subset
b69ab31241 expect(sort(['a', 'f', 'b', 'c', 'e', 'd'])).toBe('a b c d e f');
b69ab31242 expect(sort(['y', 'u', 'w', 'v', 'x'])).toBe('y x w v u');
b69ab31243 expect(sort(['a', 'f', 'd'])).toBe('a d f');
b69ab31244 expect(sort(['u', 'y', 'w'])).toBe('y w u');
b69ab31245 expect(sort(['w', 'f', 'y'])).toBe('y w f');
b69ab31246 expect(sort(['x', 'y', 'f', 'e'])).toBe('y x e f');
b69ab31247 // custom sort function
b69ab31248 expect(dag.sortAsc(['d', 'v'], {compare: a => (a.hash === 'v' ? -1 : 1)})).toEqual([
b69ab31249 'v',
b69ab31250 'd',
b69ab31251 ]);
b69ab31252 });
b69ab31253
b69ab31254 it('desc', () => {
b69ab31255 const sort = (s: SetLike) => [...dag.sortDesc(s)].join(' ');
b69ab31256 // sort all
b69ab31257 expect(sort(dag)).toBe('u f e v w x y d c b a');
b69ab31258 // sort subset
b69ab31259 expect(sort(['a', 'f', 'b', 'c', 'e', 'd'])).toBe('f e d c b a');
b69ab31260 expect(sort(['y', 'u', 'w', 'v', 'x'])).toBe('u v w x y');
b69ab31261 expect(sort(['w', 'f', 'y'])).toBe('f w y');
b69ab31262 expect(sort(['x', 'y', 'f', 'e'])).toBe('f e x y');
b69ab31263 // custom sort function
b69ab31264 expect(dag.sortDesc(['c', 'x'], {compare: a => (a.hash === 'x' ? -1 : 1)})).toEqual([
b69ab31265 'x',
b69ab31266 'c',
b69ab31267 ]);
b69ab31268 });
b69ab31269 });
b69ab31270
b69ab31271 describe('resolve', () => {
b69ab31272 const dag = new Dag().add(
b69ab31273 [
b69ab31274 {...info, hash: 'abc'},
b69ab31275 {...info, hash: 'abd', bookmarks: ['foo', 'bar']},
b69ab31276 {...info, hash: 'acc', remoteBookmarks: ['remote/foo', 'remote/main']},
b69ab31277 {...info, hash: 'add', isDot: true},
b69ab31278 {...info, hash: 'aee', remoteBookmarks: ['remote/bar'], bookmarks: ['baz']},
b69ab31279 {...info, hash: 'aff'},
b69ab31280 ].map(toInfo),
b69ab31281 );
b69ab31282
b69ab31283 it('supports "."', () => {
b69ab31284 expect(dag.resolve('.')?.hash).toBe('add');
b69ab31285 });
b69ab31286
b69ab31287 it('supports hashes', () => {
b69ab31288 for (const h of ['abc', 'abd', 'aff']) {
b69ab31289 expect(dag.resolve(h)?.hash).toBe(h);
b69ab31290 }
b69ab31291 });
b69ab31292
b69ab31293 it('supports bookmarks', () => {
b69ab31294 expect(dag.resolve('foo')?.hash).toBe('abd');
b69ab31295 expect(dag.resolve('bar')?.hash).toBe('abd');
b69ab31296 expect(dag.resolve('baz')?.hash).toBe('aee');
b69ab31297 });
b69ab31298
b69ab31299 it('supports remotenames', () => {
b69ab31300 expect(dag.resolve('remote/foo')?.hash).toBe('acc');
b69ab31301 expect(dag.resolve('remote/main')?.hash).toBe('acc');
b69ab31302 expect(dag.resolve('remote/bar')?.hash).toBe('aee');
b69ab31303 });
b69ab31304
b69ab31305 it('supports hoisted remotenames', () => {
b69ab31306 expect(dag.resolve('main')?.hash).toBe('acc');
b69ab31307 });
b69ab31308
b69ab31309 it('supports hash prefix', () => {
b69ab31310 expect(dag.resolve('af')?.hash).toBe('aff');
b69ab31311 expect(dag.resolve('ac')?.hash).toBe('acc');
b69ab31312 expect(dag.resolve('ab')?.hash).toBe(undefined); // ambiguous
b69ab31313 });
b69ab31314
b69ab31315 it('considers priorities between bookmarks and hashes', () => {
b69ab31316 const dag = new Dag().add(
b69ab31317 [
b69ab31318 {...info, hash: 'foo'},
b69ab31319 {...info, hash: 'bar', bookmarks: ['foo']},
b69ab31320 {...info, hash: 'baz', bookmarks: ['fo']},
b69ab31321 ].map(toInfo),
b69ab31322 );
b69ab31323 // full hash > bookmark
b69ab31324 expect(dag.resolve('foo')?.hash).toBe('foo');
b69ab31325 // bookmark > prefix
b69ab31326 expect(dag.resolve('fo')?.hash).toBe('baz');
b69ab31327 });
b69ab31328
b69ab31329 it('moves "." with edits', () => {
b69ab31330 const dag1 = dag.replaceWith(['add', 'abc'], (h, c) => {
b69ab31331 return c?.set('isDot', h === 'abc');
b69ab31332 });
b69ab31333 expect(dag1.remove('abc').resolve('.')?.hash).toBe(undefined);
b69ab31334 });
b69ab31335
b69ab31336 it('resolves hoisted name when conflicted bookmark is removed', () => {
b69ab31337 // foo: abd (bookmark); acc (hoisted remote bookmark)
b69ab31338 // removing 'abc' will make foo resolve to acc.
b69ab31339 expect(dag.remove('abd').resolve('foo')?.hash).toBe('acc');
b69ab31340 });
b69ab31341
b69ab31342 it('hash prefix works when ambiguous hashes are removed', () => {
b69ab31343 // 'ab' prefix: abc abd
b69ab31344 // removing 'abc' will make 'ab' resolve to 'abd'.
b69ab31345 expect(dag.remove('abc').resolve('ab')?.hash).toBe('abd');
b69ab31346 });
b69ab31347 });
b69ab31348
b69ab31349 describe('mutation', () => {
b69ab31350 // mutation: a-->a1-->a2-->a3
b69ab31351 // dag: a1 a2 b.
b69ab31352 const dag = new Dag()
b69ab31353 .add(
b69ab31354 [
b69ab31355 {...info, hash: 'a', successorInfo: {hash: 'a1', type: ''}},
b69ab31356 {...info, hash: 'a1'},
b69ab31357 {...info, hash: 'a2', closestPredecessors: ['a1']},
b69ab31358 {...info, hash: 'a3', closestPredecessors: ['a2']},
b69ab31359 {...info, hash: 'b', closestPredecessors: ['b0']},
b69ab31360 ].map(toInfo),
b69ab31361 )
b69ab31362 .remove(['a', 'a3']);
b69ab31363
b69ab31364 it('followSuccessors()', () => {
b69ab31365 expect(dag.followSuccessors(['a', 'b']).toSortedArray()).toEqual(['a2', 'b']);
b69ab31366 expect(dag.followSuccessors(['a3']).toSortedArray()).toEqual(['a3']);
b69ab31367 expect(dag.followSuccessors(['a1', 'a2']).toSortedArray()).toEqual(['a2']);
b69ab31368 });
b69ab31369
b69ab31370 it('successors()', () => {
b69ab31371 expect(dag.successors(['a', 'b']).toSortedArray()).toEqual(['a', 'a1', 'a2', 'b']);
b69ab31372 expect(dag.successors(['a1', 'a2']).toSortedArray()).toEqual(['a1', 'a2']);
b69ab31373 });
b69ab31374
b69ab31375 it('predecessors()', () => {
b69ab31376 expect(dag.predecessors(['a', 'b']).toSortedArray()).toEqual(['b']);
b69ab31377 expect(dag.predecessors(['a2']).toSortedArray()).toEqual(['a1', 'a2']);
b69ab31378 expect(dag.predecessors(['a3']).toSortedArray()).toEqual(['a1', 'a2']);
b69ab31379 });
b69ab31380
b69ab31381 it('picks stack top when following a split', () => {
b69ab31382 // mutation: a->b a->c a->d
b69ab31383 // dag: a b--d--c.
b69ab31384 const dag = new Dag()
b69ab31385 .add(
b69ab31386 [
b69ab31387 {...info, hash: 'a'},
b69ab31388 {...info, hash: 'b', closestPredecessors: ['a']},
b69ab31389 {...info, hash: 'c', closestPredecessors: ['a'], parents: ['d']},
b69ab31390 {...info, hash: 'd', closestPredecessors: ['a'], parents: ['b']},
b69ab31391 ].map(toInfo),
b69ab31392 )
b69ab31393 .remove(['a']);
b69ab31394 // not ['d'] or ['b', 'c', 'd']
b69ab31395 expect(dag.followSuccessors('a').toSortedArray()).toEqual(['c']);
b69ab31396 });
b69ab31397 });
b69ab31398
b69ab31399 describe('rebase', () => {
b69ab31400 const succ = (h: Hash): Hash => `${REBASE_SUCC_PREFIX}${h}`;
b69ab31401
b69ab31402 it('can break linear stack', () => {
b69ab31403 // a--b--c rebase -r c -d a
b69ab31404 let dag = new Dag().add(
b69ab31405 [
b69ab31406 {...info, hash: 'a', parents: []},
b69ab31407 {...info, hash: 'b', parents: ['a']},
b69ab31408 {...info, hash: 'c', parents: ['b']},
b69ab31409 ].map(toInfo),
b69ab31410 );
b69ab31411 dag = dag.rebase(['c'], 'a');
b69ab31412 expect(dag.parentHashes('c')).toEqual(['a']);
b69ab31413 });
b69ab31414
b69ab31415 it('skips already rebased branches', () => {
b69ab31416 // a--------b rebase -r c+d+e+f -d b
b69ab31417 // \ \ e f should not be touched.
b69ab31418 // c--d e--f
b69ab31419 let dag = new Dag().add(
b69ab31420 [
b69ab31421 {...info, hash: 'a', parents: [], phase: PUBLIC},
b69ab31422 {...info, hash: 'b', parents: ['a'], phase: PUBLIC},
b69ab31423 {...info, hash: 'c', parents: ['a'], phase: DRAFT},
b69ab31424 {...info, hash: 'd', parents: ['c'], phase: DRAFT},
b69ab31425 {...info, hash: 'e', parents: ['b'], phase: DRAFT},
b69ab31426 {...info, hash: 'f', parents: ['e'], phase: DRAFT},
b69ab31427 ].map(toInfo),
b69ab31428 );
b69ab31429 dag = dag.rebase(['c', 'd', 'e', 'f'], 'b');
b69ab31430
b69ab31431 // e and f should not be touched
b69ab31432 expect(dag.get('e')?.date).toEqual(date);
b69ab31433 expect(dag.get('f')?.date).toEqual(date);
b69ab31434
b69ab31435 // c and d are touched
b69ab31436 expect(dag.get('c')?.date).not.toEqual(date);
b69ab31437 expect(dag.get('d')?.date).not.toEqual(date);
b69ab31438
b69ab31439 // check b--e--f and b--c--d
b69ab31440 expect(dag.parentHashes('f')).toEqual(['e']);
b69ab31441 expect(dag.parentHashes('e')).toEqual(['b']);
b69ab31442 expect(dag.parentHashes('d')).toEqual(['c']);
b69ab31443 expect(dag.parentHashes('c')).toEqual(['b']);
b69ab31444 });
b69ab31445
b69ab31446 it('handles orphaned commits', () => {
b69ab31447 // a--b z; rebase -r a -d z; result:
b69ab31448 // a(pred)--b z--a(succ).
b69ab31449 let dag = new Dag().add(
b69ab31450 [
b69ab31451 {...info, hash: 'z', parents: [], phase: PUBLIC},
b69ab31452 {...info, hash: 'a', parents: [], phase: DRAFT},
b69ab31453 {...info, hash: 'b', parents: ['a'], phase: DRAFT},
b69ab31454 ].map(toInfo),
b69ab31455 );
b69ab31456 dag = dag.rebase(['a'], 'z');
b69ab31457
b69ab31458 // check z--a(succ)
b69ab31459 expect(dag.parentHashes(succ('a'))).toEqual(['z']);
b69ab31460 expect(dag.get(succ('a'))?.date).not.toEqual(date);
b69ab31461
b69ab31462 // check a(pred)--b
b69ab31463 expect(dag.parentHashes('b')).toEqual(['a']);
b69ab31464 expect(dag.parentHashes('a')).toEqual([]);
b69ab31465 expect(dag.get('a')?.date).toEqual(date);
b69ab31466 expect(dag.get('b')?.date).toEqual(date);
b69ab31467 });
b69ab31468
b69ab31469 it('handles non-continuous selection', () => {
b69ab31470 // a--b--c--d--e--f z; rebase b+c+e+f to z; result:
b69ab31471 // a--b(pred)--c(pred)--d; z--b(succ)--c(succ)--e--f
b69ab31472 let dag = new Dag().add(
b69ab31473 [
b69ab31474 {...info, hash: 'a', parents: []},
b69ab31475 {...info, hash: 'b', parents: ['a']},
b69ab31476 {...info, hash: 'c', parents: ['b']},
b69ab31477 {...info, hash: 'd', parents: ['c']}, // not rebasing
b69ab31478 {...info, hash: 'e', parents: ['d']},
b69ab31479 {...info, hash: 'f', parents: ['e']},
b69ab31480 {...info, hash: 'z', parents: []},
b69ab31481 ].map(toInfo),
b69ab31482 );
b69ab31483 dag = dag.rebase(['b', 'c', 'e', 'f'], 'z');
b69ab31484
b69ab31485 // check z--b(succ)--c(succ)--e--f
b69ab31486 expect(dag.parentHashes('f')).toEqual(['e']);
b69ab31487 expect(dag.parentHashes('e')).toEqual([succ('c')]);
b69ab31488 expect(dag.parentHashes(succ('c'))).toEqual([succ('b')]);
b69ab31489 expect(dag.parentHashes(succ('b'))).toEqual(['z']);
b69ab31490
b69ab31491 // check a--b(pred)--c(pred)--c--d
b69ab31492 expect(dag.parentHashes('c')).toEqual(['b']);
b69ab31493 expect(dag.parentHashes('b')).toEqual(['a']);
b69ab31494 expect(dag.childHashes('d').toArray()).toEqual([]);
b69ab31495
b69ab31496 // succ and pred info
b69ab31497 expect(dag.get('b')?.successorInfo?.hash).toEqual(succ('b'));
b69ab31498 expect(dag.get('c')?.successorInfo?.hash).toEqual(succ('c'));
b69ab31499 expect(dag.get(succ('b'))?.closestPredecessors).toEqual(['b']);
b69ab31500 expect(dag.get(succ('c'))?.closestPredecessors).toEqual(['c']);
b69ab31501
b69ab31502 // orphaned and obsoleted b--c--d are not touched
b69ab31503 expect(dag.get('b')?.date).toEqual(date);
b69ab31504 expect(dag.get('c')?.date).toEqual(date);
b69ab31505 expect(dag.get('d')?.date).toEqual(date);
b69ab31506 });
b69ab31507
b69ab31508 it('cleans up obsoleted commits', () => {
b69ab31509 // a--b--c--f rebase -r f -d z
b69ab31510 // \ / b, c, d, e are obsoleted
b69ab31511 // -d--e- b is head
b69ab31512 // z check: c, d, e are removed
b69ab31513 const successorInfo: SuccessorInfo = {hash: 'z', type: 'rewrite'};
b69ab31514 let dag = new Dag().add(
b69ab31515 [
b69ab31516 {...info, hash: 'z', parents: [], phase: PUBLIC},
b69ab31517 {...info, hash: 'a', parents: [], phase: DRAFT},
b69ab31518 {...info, hash: 'b', parents: ['a'], phase: DRAFT, date, successorInfo, isDot: true},
b69ab31519 {...info, hash: 'c', parents: ['b'], phase: DRAFT, date, successorInfo},
b69ab31520 {...info, hash: 'd', parents: ['a'], phase: DRAFT, date, successorInfo},
b69ab31521 {...info, hash: 'e', parents: ['d'], phase: DRAFT, date, successorInfo},
b69ab31522 {...info, hash: 'f', parents: ['c', 'e'], phase: DRAFT, date},
b69ab31523 ].map(toInfo),
b69ab31524 );
b69ab31525 dag = dag.rebase(['f'], 'z');
b69ab31526 expect(['b', 'c', 'd', 'e'].filter(h => dag.has(h))).toEqual(['b']);
b69ab31527 });
b69ab31528 });
b69ab31529
b69ab31530 describe('connect public commits', () => {
b69ab31531 it('connect with grandparents info', () => {
b69ab31532 // z-w x y => z-x-y
b69ab31533 // \
b69ab31534 // w
b69ab31535 const dag = new Dag().add(
b69ab31536 [
b69ab31537 {...info, hash: 'z', phase: PUBLIC},
b69ab31538 {...info, hash: 'x', phase: PUBLIC, parents: ['a'], grandparents: ['z']},
b69ab31539 {...info, hash: 'y', phase: PUBLIC, parents: ['b'], grandparents: ['x']},
b69ab31540 {...info, hash: 'w', phase: PUBLIC, parents: ['z']},
b69ab31541 ].map(toInfo),
b69ab31542 );
b69ab31543 expect(dag.children('z').toSortedArray()).toEqual(['w', 'x']);
b69ab31544 expect(dag.children('x').toSortedArray()).toEqual(['y']);
b69ab31545 expect(dag.children('y').toSortedArray()).toEqual([]);
b69ab31546 expect(dag.children('w').toSortedArray()).toEqual([]);
b69ab31547 });
b69ab31548
b69ab31549 it('forceConnectPublic() without grandparents info', () => {
b69ab31550 // z-w x y => z-x-y
b69ab31551 // \
b69ab31552 // w
b69ab31553 const dag = new Dag()
b69ab31554 .add(
b69ab31555 [
b69ab31556 {...info, hash: 'z', phase: PUBLIC, date: new Date(1)},
b69ab31557 {...info, hash: 'x', phase: PUBLIC, date: new Date(2)},
b69ab31558 {...info, hash: 'y', phase: PUBLIC, date: new Date(3)},
b69ab31559 {...info, hash: 'w', phase: PUBLIC, date: new Date(3), parents: ['z']},
b69ab31560 ].map(toInfo),
b69ab31561 )
b69ab31562 .maybeForceConnectPublic();
b69ab31563 // Without grandparents info, fallback to chronological connections.
b69ab31564 expect(dag.children('z').toSortedArray()).toEqual(['w', 'x']);
b69ab31565 expect(dag.children('x').toSortedArray()).toEqual(['y']);
b69ab31566 expect(dag.children('y').toSortedArray()).toEqual([]);
b69ab31567 // w is not a root so it does not need fix.
b69ab31568 expect(dag.children('w').toSortedArray()).toEqual([]);
b69ab31569 });
b69ab31570 });
b69ab31571
b69ab31572 it('renders to ASCII text', () => {
b69ab31573 // a--b--c
b69ab31574 // / \
b69ab31575 // z d
b69ab31576 const dag = new Dag().add(
b69ab31577 [
b69ab31578 {...info, phase: PUBLIC, hash: 'a', parents: []},
b69ab31579 {...info, phase: PUBLIC, hash: 'z', parents: []},
b69ab31580 {...info, phase: PUBLIC, hash: 'b', parents: ['a', 'z']},
b69ab31581 {...info, phase: PUBLIC, hash: 'c', parents: ['b']},
b69ab31582 {...info, phase: DRAFT, hash: 'd', parents: ['c']},
b69ab31583 ].map(toInfo),
b69ab31584 );
b69ab31585
b69ab31586 expect(dag.renderAscii()).toMatchInlineSnapshot(`
b69ab31587 "
b69ab31588 o d
b69ab31589 │
b69ab31590 ╭─╯
b69ab31591 o c
b69ab31592 │
b69ab31593 o b
b69ab31594 │
b69ab31595 ├─╮
b69ab31596 o │ a
b69ab31597 │
b69ab31598 o z"
b69ab31599 `);
b69ab31600
b69ab31601 // Render a subset.
b69ab31602 // [a, c] subset: edge is dashed.
b69ab31603 expect(dag.renderAscii(['a', 'c'])).toMatchInlineSnapshot(`
b69ab31604 "
b69ab31605 o c
b69ab31606 │
b69ab31607 :
b69ab31608 o a"
b69ab31609 `);
b69ab31610 // [b, d] subset: indents "d" (draft), and "b" has an "~".
b69ab31611 expect(dag.renderAscii(['b', 'd'])).toMatchInlineSnapshot(`
b69ab31612 "
b69ab31613 o d
b69ab31614 │
b69ab31615 ╭─╯
b69ab31616 :
b69ab31617 o b
b69ab31618 │
b69ab31619 │
b69ab31620 ~"
b69ab31621 `);
b69ab31622 });
b69ab31623});
b69ab31624
b69ab31625describe('HashSet', () => {
b69ab31626 it('preserves order on intersect', () => {
b69ab31627 const a = HashSet.fromHashes(['c', 'b', 'e', 'd', 'a', 'f']);
b69ab31628 expect(a.intersect(['d', 'b']).toArray()).toEqual(['b', 'd']);
b69ab31629 expect(a.intersect(['b', 'd']).toArray()).toEqual(['b', 'd']);
b69ab31630 });
b69ab31631
b69ab31632 it('preserves order on substract', () => {
b69ab31633 const a = HashSet.fromHashes(['c', 'b', 'e', 'd', 'a', 'f']);
b69ab31634 expect(a.subtract(['d', 'b']).toArray()).toEqual(['c', 'e', 'a', 'f']);
b69ab31635 expect(a.subtract(['b', 'd']).toArray()).toEqual(['c', 'e', 'a', 'f']);
b69ab31636 expect(a.subtract(['f', 'c', 'e']).toArray()).toEqual(['b', 'd', 'a']);
b69ab31637 });
b69ab31638
b69ab31639 it('preserves order on union', () => {
b69ab31640 const a = HashSet.fromHashes(['d', 'a', 'c']);
b69ab31641 expect(a.union(['x', 'z', 'y']).toArray()).toEqual(['d', 'a', 'c', 'x', 'z', 'y']);
b69ab31642 expect(a.union(['y', 'z', 'x']).toArray()).toEqual(['d', 'a', 'c', 'y', 'z', 'x']);
b69ab31643
b69ab31644 expect(a.union(['a', 'd']).toArray()).toEqual(['d', 'a', 'c']);
b69ab31645 expect(a.union(['d', 'a']).toArray()).toEqual(['d', 'a', 'c']);
b69ab31646
b69ab31647 expect(a.union(['a', 'b']).toArray()).toEqual(['d', 'a', 'c', 'b']);
b69ab31648 expect(a.union(['f', 'c', 'a', 'd', 'e']).toArray()).toEqual(['d', 'a', 'c', 'f', 'e']);
b69ab31649 });
b69ab31650});