@dotinc/ogre 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.tap/processinfo/{4bb3fbc0-322c-47fe-99ca-5a2029006dab.json → 2426d9f3-699c-4fb9-94b2-9ec9c3eac102.json} +23 -23
- package/.tap/processinfo/{999e3026-ccc7-47b1-bfbd-82a08fbfebb2.json → 45f9dbea-d45b-4370-ad84-bacbe0f7923b.json} +7 -7
- package/.tap/processinfo/{46e1fc09-045e-4621-a147-8e17ab127537.json → 6a2329ae-15e9-4041-98f8-205645f535f5.json} +7 -7
- package/.tap/processinfo/{9e618086-85e0-4b56-855b-6e7251bd1b83.json → 7073c462-24dd-4066-8ee7-610cc0a4cdc6.json} +23 -23
- package/.tap/processinfo/{0edfcd98-724a-4657-9a64-db2d38f2bc26.json → 8ce3d798-c3e7-49ba-a002-477d6aed67d1.json} +11 -11
- package/.tap/processinfo/a33fc811-9cba-46ee-8577-3d68cb1ae96f.json +246 -0
- package/.tap/processinfo/{afde0f56-0117-4d71-a370-ed7621252dcc.json → cc694d82-e679-4b5d-8cf9-decd8029a65a.json} +7 -7
- package/.tap/test-results/src/branch.test.ts.tap +6 -6
- package/.tap/test-results/src/checkout.test.ts.tap +9 -9
- package/.tap/test-results/src/commit.test.ts.tap +18 -18
- package/.tap/test-results/src/merge.test.ts.tap +2 -2
- package/.tap/test-results/src/repository.test.ts.tap +111 -26
- package/.tap/test-results/src/tag.test.ts.tap +3 -3
- package/.tap/test-results/src/utils.test.ts.tap +41 -8
- package/CHANGELOG.md +17 -0
- package/lib/commit.d.ts +2 -2
- package/lib/repository.d.ts +19 -7
- package/lib/repository.js +84 -5
- package/lib/utils.d.ts +12 -1
- package/lib/utils.js +40 -8
- package/package.json +4 -2
- package/src/commit.ts +9 -9
- package/src/repository.test.ts +359 -5
- package/src/repository.ts +118 -13
- package/src/utils.test.ts +88 -1
- package/src/utils.ts +48 -10
- package/.tap/processinfo/ae7dad2b-5163-4c3f-8d16-e25e7696c657.json +0 -242
package/src/repository.test.ts
CHANGED
|
@@ -10,9 +10,8 @@ import {
|
|
|
10
10
|
testAuthor,
|
|
11
11
|
updateHeaderData,
|
|
12
12
|
} from "./test.utils";
|
|
13
|
-
import {
|
|
14
|
-
import { compare
|
|
15
|
-
import { printChange, printChangeLog } from "./utils";
|
|
13
|
+
import { Reference } from "./interfaces";
|
|
14
|
+
import { compare } from "fast-json-patch";
|
|
16
15
|
|
|
17
16
|
test("diff is ok", async (t) => {
|
|
18
17
|
const [repo, obj] = await getBaseline();
|
|
@@ -137,10 +136,14 @@ test("history", async (t) => {
|
|
|
137
136
|
const history = repo.getHistory();
|
|
138
137
|
|
|
139
138
|
const obj2 = {};
|
|
140
|
-
// @ts-ignore
|
|
141
139
|
const repo2 = new Repository(obj2, { history });
|
|
142
140
|
|
|
143
|
-
t.
|
|
141
|
+
t.matchOnlyStrict(
|
|
142
|
+
obj,
|
|
143
|
+
obj2,
|
|
144
|
+
"restored object does not equal last version.",
|
|
145
|
+
);
|
|
146
|
+
t.not(obj, obj2, "should not be the js ref");
|
|
144
147
|
});
|
|
145
148
|
|
|
146
149
|
t.test("remoteRefs doesn't change on commit", async (t) => {
|
|
@@ -373,3 +376,354 @@ test("apply", async (t) => {
|
|
|
373
376
|
t.match(repo.data, targetState, "The final state does not match up");
|
|
374
377
|
});
|
|
375
378
|
});
|
|
379
|
+
|
|
380
|
+
test("pending changes - push helpers", async (t) => {
|
|
381
|
+
t.test("1 commit & 1 ref update", async (t) => {
|
|
382
|
+
const [repo, obj] = await getBaseline();
|
|
383
|
+
updateHeaderData(obj);
|
|
384
|
+
await repo.commit("header data", testAuthor);
|
|
385
|
+
addOneNested(obj);
|
|
386
|
+
await repo.commit("first nested", testAuthor);
|
|
387
|
+
repo.tag("v0.1.0");
|
|
388
|
+
const history = repo.getHistory();
|
|
389
|
+
|
|
390
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
391
|
+
|
|
392
|
+
repo2.data.description = "changed description";
|
|
393
|
+
const hash = await repo2.commit("changed desc", testAuthor);
|
|
394
|
+
const { commits } = repo2.getHistory();
|
|
395
|
+
const pendingCommit = commits.find((c) => c.hash === hash);
|
|
396
|
+
|
|
397
|
+
const pending = repo2.cherry();
|
|
398
|
+
|
|
399
|
+
t.equal(pending.commits.length, 1, "incorrect number of pending commits");
|
|
400
|
+
t.equal(pending.refs.size, 1, "incorrect number of pending ref updates");
|
|
401
|
+
t.matchOnly(pending.commits[0], pendingCommit, "wrong pending commit");
|
|
402
|
+
t.matchOnlyStrict(
|
|
403
|
+
Array.from(pending.refs.keys()),
|
|
404
|
+
["refs/heads/main"],
|
|
405
|
+
"wrong pending ref update",
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
t.test("2 commit & 1 ref update", async (t) => {
|
|
410
|
+
const [repo, obj] = await getBaseline();
|
|
411
|
+
updateHeaderData(obj);
|
|
412
|
+
await repo.commit("header data", testAuthor);
|
|
413
|
+
addOneNested(obj);
|
|
414
|
+
await repo.commit("first nested", testAuthor);
|
|
415
|
+
repo.tag("v0.1.0");
|
|
416
|
+
const history = repo.getHistory();
|
|
417
|
+
|
|
418
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
419
|
+
|
|
420
|
+
repo2.data.description = "changed description";
|
|
421
|
+
const hash1 = await repo2.commit("changed desc", testAuthor);
|
|
422
|
+
repo2.data.uuid = "uuid1";
|
|
423
|
+
const hash2 = await repo2.commit("changed uuid", testAuthor);
|
|
424
|
+
|
|
425
|
+
const { commits } = repo2.getHistory();
|
|
426
|
+
const pendingCommit1 = commits.find((c) => c.hash === hash1);
|
|
427
|
+
const pendingCommit2 = commits.find((c) => c.hash === hash2);
|
|
428
|
+
|
|
429
|
+
const pending = repo2.cherry();
|
|
430
|
+
|
|
431
|
+
t.equal(pending.commits.length, 2, "incorrect number of pending commits");
|
|
432
|
+
t.equal(pending.refs.size, 1, "incorrect number of pending ref updates");
|
|
433
|
+
t.matchOnlyStrict(
|
|
434
|
+
new Set(pending.commits),
|
|
435
|
+
new Set([pendingCommit2, pendingCommit1]),
|
|
436
|
+
"wrong pending commits",
|
|
437
|
+
);
|
|
438
|
+
t.matchOnlyStrict(
|
|
439
|
+
Array.from(pending.refs.keys()),
|
|
440
|
+
["refs/heads/main"],
|
|
441
|
+
"wrong pending ref update",
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
t.test("1 commit & 2 ref updates", async (t) => {
|
|
446
|
+
const [repo, obj] = await getBaseline();
|
|
447
|
+
updateHeaderData(obj);
|
|
448
|
+
await repo.commit("header data", testAuthor);
|
|
449
|
+
addOneNested(obj);
|
|
450
|
+
await repo.commit("first nested", testAuthor);
|
|
451
|
+
repo.tag("v0.1.0");
|
|
452
|
+
const history = repo.getHistory();
|
|
453
|
+
|
|
454
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
455
|
+
|
|
456
|
+
repo2.data.description = "changed description";
|
|
457
|
+
const hash1 = await repo2.commit("changed desc", testAuthor);
|
|
458
|
+
repo2.createBranch("branch2");
|
|
459
|
+
|
|
460
|
+
const { commits } = repo2.getHistory();
|
|
461
|
+
const pendingCommit1 = commits.find((c) => c.hash === hash1);
|
|
462
|
+
|
|
463
|
+
const pending = repo2.cherry();
|
|
464
|
+
|
|
465
|
+
t.equal(pending.commits.length, 1, "incorrect number of pending commits");
|
|
466
|
+
t.equal(pending.refs.size, 2, "incorrect number of pending ref updates");
|
|
467
|
+
t.matchOnlyStrict(
|
|
468
|
+
pending.commits,
|
|
469
|
+
[pendingCommit1],
|
|
470
|
+
"wrong pending commits",
|
|
471
|
+
);
|
|
472
|
+
t.matchOnlyStrict(
|
|
473
|
+
new Set(pending.refs.keys()),
|
|
474
|
+
new Set(["refs/heads/main", "refs/heads/branch2"]),
|
|
475
|
+
"wrong pending ref update",
|
|
476
|
+
);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
t.test("2 commit & 2 ref updates", async (t) => {
|
|
480
|
+
const [repo, obj] = await getBaseline();
|
|
481
|
+
updateHeaderData(obj);
|
|
482
|
+
await repo.commit("header data", testAuthor);
|
|
483
|
+
addOneNested(obj);
|
|
484
|
+
await repo.commit("first nested", testAuthor);
|
|
485
|
+
repo.tag("v0.1.0");
|
|
486
|
+
const history = repo.getHistory();
|
|
487
|
+
|
|
488
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
489
|
+
|
|
490
|
+
repo2.data.description = "changed description";
|
|
491
|
+
const hash1 = await repo2.commit("changed desc", testAuthor);
|
|
492
|
+
repo2.data.uuid = "uuid1";
|
|
493
|
+
const hash2 = await repo2.commit("changed uuid", testAuthor);
|
|
494
|
+
repo2.checkout("branch2", true);
|
|
495
|
+
|
|
496
|
+
const { commits } = repo2.getHistory();
|
|
497
|
+
const pendingCommit1 = commits.find((c) => c.hash === hash1);
|
|
498
|
+
const pendingCommit2 = commits.find((c) => c.hash === hash2);
|
|
499
|
+
|
|
500
|
+
const pending = repo2.cherry();
|
|
501
|
+
|
|
502
|
+
t.equal(pending.commits.length, 2, "incorrect number of pending commits");
|
|
503
|
+
t.equal(pending.refs.size, 2, "incorrect number of pending ref updates");
|
|
504
|
+
t.matchOnlyStrict(
|
|
505
|
+
new Set(pending.commits),
|
|
506
|
+
new Set([pendingCommit2, pendingCommit1]),
|
|
507
|
+
"wrong pending commits",
|
|
508
|
+
);
|
|
509
|
+
t.matchOnlyStrict(
|
|
510
|
+
new Set(pending.refs.keys()),
|
|
511
|
+
new Set(["refs/heads/main", "refs/heads/branch2"]),
|
|
512
|
+
"wrong pending ref update",
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
t.test("3 commit & 2 ref updates", async (t) => {
|
|
517
|
+
const [repo, obj] = await getBaseline();
|
|
518
|
+
updateHeaderData(obj);
|
|
519
|
+
await repo.commit("header data", testAuthor);
|
|
520
|
+
addOneNested(obj);
|
|
521
|
+
await repo.commit("first nested", testAuthor);
|
|
522
|
+
repo.tag("v0.1.0");
|
|
523
|
+
const history = repo.getHistory();
|
|
524
|
+
|
|
525
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
526
|
+
|
|
527
|
+
repo2.data.description = "changed description";
|
|
528
|
+
const hash1 = await repo2.commit("changed desc", testAuthor);
|
|
529
|
+
repo2.data.uuid = "uuid1";
|
|
530
|
+
const hash2 = await repo2.commit("changed uuid", testAuthor);
|
|
531
|
+
repo2.checkout("branch2", true);
|
|
532
|
+
|
|
533
|
+
repo2.data.nested = [{ name: "a", uuid: "thing" }];
|
|
534
|
+
const hash3 = await repo2.commit("added a thing", testAuthor);
|
|
535
|
+
|
|
536
|
+
const { commits } = repo2.getHistory();
|
|
537
|
+
const pendingCommit1 = commits.find((c) => c.hash === hash1);
|
|
538
|
+
const pendingCommit2 = commits.find((c) => c.hash === hash2);
|
|
539
|
+
const pendingCommit3 = commits.find((c) => c.hash === hash3);
|
|
540
|
+
|
|
541
|
+
const pending = repo2.cherry();
|
|
542
|
+
|
|
543
|
+
t.equal(pending.commits.length, 3, "incorrect number of pending commits");
|
|
544
|
+
t.equal(pending.refs.size, 2, "incorrect number of pending ref updates");
|
|
545
|
+
t.matchOnlyStrict(
|
|
546
|
+
new Set(pending.commits),
|
|
547
|
+
new Set([pendingCommit3, pendingCommit2, pendingCommit1]),
|
|
548
|
+
"wrong pending commits",
|
|
549
|
+
);
|
|
550
|
+
t.matchOnlyStrict(
|
|
551
|
+
new Set(pending.refs.keys()),
|
|
552
|
+
new Set(["refs/heads/main", "refs/heads/branch2"]),
|
|
553
|
+
"wrong pending ref update",
|
|
554
|
+
);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
t.test("3 commit & 3 ref updates", async (t) => {
|
|
558
|
+
const [repo, obj] = await getBaseline();
|
|
559
|
+
updateHeaderData(obj);
|
|
560
|
+
await repo.commit("header data", testAuthor);
|
|
561
|
+
addOneNested(obj);
|
|
562
|
+
await repo.commit("first nested", testAuthor);
|
|
563
|
+
repo.tag("v0.1.0");
|
|
564
|
+
const history = repo.getHistory();
|
|
565
|
+
|
|
566
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
567
|
+
|
|
568
|
+
repo2.data.description = "changed description";
|
|
569
|
+
const hash1 = await repo2.commit("changed desc", testAuthor);
|
|
570
|
+
repo2.data.uuid = "uuid1";
|
|
571
|
+
const hash2 = await repo2.commit("changed uuid", testAuthor);
|
|
572
|
+
repo2.checkout("branch2", true);
|
|
573
|
+
|
|
574
|
+
repo2.data.nested = [{ name: "a", uuid: "thing" }];
|
|
575
|
+
const hash3 = await repo2.commit("added a thing", testAuthor);
|
|
576
|
+
repo2.tag("v0.2.0");
|
|
577
|
+
|
|
578
|
+
const { commits } = repo2.getHistory();
|
|
579
|
+
const pendingCommit1 = commits.find((c) => c.hash === hash1);
|
|
580
|
+
const pendingCommit2 = commits.find((c) => c.hash === hash2);
|
|
581
|
+
const pendingCommit3 = commits.find((c) => c.hash === hash3);
|
|
582
|
+
|
|
583
|
+
const pending = repo2.cherry();
|
|
584
|
+
|
|
585
|
+
t.equal(pending.commits.length, 3, "incorrect number of pending commits");
|
|
586
|
+
t.equal(pending.refs.size, 3, "incorrect number of pending ref updates");
|
|
587
|
+
t.matchOnlyStrict(
|
|
588
|
+
new Set(pending.commits),
|
|
589
|
+
new Set([pendingCommit3, pendingCommit2, pendingCommit1]),
|
|
590
|
+
"wrong pending commits",
|
|
591
|
+
);
|
|
592
|
+
t.matchOnlyStrict(
|
|
593
|
+
new Set(pending.refs.keys()),
|
|
594
|
+
new Set(["refs/heads/main", "refs/heads/branch2", "refs/tags/v0.2.0"]),
|
|
595
|
+
"wrong pending ref update",
|
|
596
|
+
);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
t.test("after merge 1 commit & 3 ref updates", async (t) => {
|
|
600
|
+
const [repo, obj] = await getBaseline();
|
|
601
|
+
updateHeaderData(obj);
|
|
602
|
+
await repo.commit("header data", testAuthor);
|
|
603
|
+
addOneNested(obj);
|
|
604
|
+
await repo.commit("first nested", testAuthor);
|
|
605
|
+
repo.tag("v0.1.0");
|
|
606
|
+
const history = repo.getHistory();
|
|
607
|
+
|
|
608
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
609
|
+
repo2.checkout("branch2", true);
|
|
610
|
+
|
|
611
|
+
repo2.data.nested = [{ name: "a", uuid: "thing" }];
|
|
612
|
+
const hash3 = await repo2.commit("added a thing", testAuthor);
|
|
613
|
+
repo2.tag("v0.2.0");
|
|
614
|
+
|
|
615
|
+
repo2.checkout("main");
|
|
616
|
+
repo2.merge("branch2");
|
|
617
|
+
|
|
618
|
+
const { commits } = repo2.getHistory();
|
|
619
|
+
const pendingCommit3 = commits.find((c) => c.hash === hash3);
|
|
620
|
+
|
|
621
|
+
const pending = repo2.cherry();
|
|
622
|
+
|
|
623
|
+
t.equal(pending.commits.length, 1, "incorrect number of pending commits");
|
|
624
|
+
t.equal(pending.refs.size, 3, "incorrect number of pending ref updates");
|
|
625
|
+
t.matchOnlyStrict(
|
|
626
|
+
pending.commits,
|
|
627
|
+
[pendingCommit3],
|
|
628
|
+
"wrong pending commits",
|
|
629
|
+
);
|
|
630
|
+
t.matchOnlyStrict(
|
|
631
|
+
new Set(pending.refs.keys()),
|
|
632
|
+
new Set(["refs/heads/main", "refs/heads/branch2", "refs/tags/v0.2.0"]),
|
|
633
|
+
"wrong pending ref update",
|
|
634
|
+
);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
t.test("after merge 1 commit & 2 ref updates", async (t) => {
|
|
638
|
+
const [repo, obj] = await getBaseline();
|
|
639
|
+
updateHeaderData(obj);
|
|
640
|
+
await repo.commit("header data", testAuthor);
|
|
641
|
+
addOneNested(obj);
|
|
642
|
+
await repo.commit("first nested", testAuthor);
|
|
643
|
+
repo.tag("v0.1.0");
|
|
644
|
+
const history = repo.getHistory();
|
|
645
|
+
|
|
646
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
647
|
+
repo2.checkout("branch2", true);
|
|
648
|
+
|
|
649
|
+
repo2.data.nested = [{ name: "a", uuid: "thing" }];
|
|
650
|
+
const hash3 = await repo2.commit("added a thing", testAuthor);
|
|
651
|
+
|
|
652
|
+
repo2.checkout("main");
|
|
653
|
+
repo2.merge("branch2");
|
|
654
|
+
|
|
655
|
+
const { commits } = repo2.getHistory();
|
|
656
|
+
const pendingCommit3 = commits.find((c) => c.hash === hash3);
|
|
657
|
+
|
|
658
|
+
const pending = repo2.cherry();
|
|
659
|
+
|
|
660
|
+
t.equal(pending.commits.length, 1, "incorrect number of pending commits");
|
|
661
|
+
t.equal(pending.refs.size, 2, "incorrect number of pending ref updates");
|
|
662
|
+
t.matchOnlyStrict(
|
|
663
|
+
pending.commits,
|
|
664
|
+
[pendingCommit3],
|
|
665
|
+
"wrong pending commits",
|
|
666
|
+
);
|
|
667
|
+
t.matchOnlyStrict(
|
|
668
|
+
new Set(pending.refs.keys()),
|
|
669
|
+
new Set(["refs/heads/main", "refs/heads/branch2"]),
|
|
670
|
+
"wrong pending ref update",
|
|
671
|
+
);
|
|
672
|
+
});
|
|
673
|
+
t.test("no merge 1 commit & 1 ref updates", async (t) => {
|
|
674
|
+
const [repo, obj] = await getBaseline();
|
|
675
|
+
updateHeaderData(obj);
|
|
676
|
+
await repo.commit("header data", testAuthor);
|
|
677
|
+
addOneNested(obj);
|
|
678
|
+
await repo.commit("first nested", testAuthor);
|
|
679
|
+
repo.tag("v0.1.0");
|
|
680
|
+
const history = repo.getHistory();
|
|
681
|
+
|
|
682
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
683
|
+
repo2.checkout("branch2", true);
|
|
684
|
+
|
|
685
|
+
repo2.data.nested = [{ name: "a", uuid: "thing" }];
|
|
686
|
+
const hash3 = await repo2.commit("added a thing", testAuthor);
|
|
687
|
+
|
|
688
|
+
const { commits } = repo2.getHistory();
|
|
689
|
+
const pendingCommit3 = commits.find((c) => c.hash === hash3);
|
|
690
|
+
|
|
691
|
+
const pending = repo2.cherry();
|
|
692
|
+
|
|
693
|
+
t.equal(pending.commits.length, 1, "incorrect number of pending commits");
|
|
694
|
+
t.equal(pending.refs.size, 1, "incorrect number of pending ref updates");
|
|
695
|
+
t.matchOnlyStrict(
|
|
696
|
+
pending.commits,
|
|
697
|
+
[pendingCommit3],
|
|
698
|
+
"wrong pending commits",
|
|
699
|
+
);
|
|
700
|
+
t.matchOnlyStrict(
|
|
701
|
+
pending.refs.keys(),
|
|
702
|
+
["refs/heads/branch2"],
|
|
703
|
+
"wrong pending ref update",
|
|
704
|
+
);
|
|
705
|
+
});
|
|
706
|
+
t.test("no merge no commit & 1 ref updates", async (t) => {
|
|
707
|
+
const [repo, obj] = await getBaseline();
|
|
708
|
+
updateHeaderData(obj);
|
|
709
|
+
await repo.commit("header data", testAuthor);
|
|
710
|
+
addOneNested(obj);
|
|
711
|
+
await repo.commit("first nested", testAuthor);
|
|
712
|
+
repo.tag("v0.1.0");
|
|
713
|
+
const history = repo.getHistory();
|
|
714
|
+
|
|
715
|
+
const repo2 = new Repository<ComplexObject>({}, { history });
|
|
716
|
+
repo2.checkout("branch2", true);
|
|
717
|
+
|
|
718
|
+
const pending = repo2.cherry();
|
|
719
|
+
|
|
720
|
+
t.equal(pending.commits.length, 0, "incorrect number of pending commits");
|
|
721
|
+
t.equal(pending.refs.size, 1, "incorrect number of pending ref updates");
|
|
722
|
+
t.matchOnlyStrict(pending.commits, [], "wrong pending commits");
|
|
723
|
+
t.matchOnlyStrict(
|
|
724
|
+
pending.refs.keys(),
|
|
725
|
+
["refs/heads/branch2"],
|
|
726
|
+
"wrong pending ref update",
|
|
727
|
+
);
|
|
728
|
+
});
|
|
729
|
+
});
|
package/src/repository.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
createHeadRefValue,
|
|
22
22
|
getLastRefPathElement,
|
|
23
23
|
headValueRefPrefix,
|
|
24
|
+
immutableArrayCopy,
|
|
24
25
|
immutableMapCopy,
|
|
25
26
|
localHeadPathPrefix,
|
|
26
27
|
mapPath,
|
|
@@ -50,18 +51,18 @@ export interface RepositoryObject<T extends { [k: string]: any }> {
|
|
|
50
51
|
* @param shaishFrom expression (e.g. refs (branches, tags), commitSha)
|
|
51
52
|
* @param shaishTo expression (e.g. refs (branches, tags), commitSha)
|
|
52
53
|
*/
|
|
53
|
-
diff(shaishFrom: string, shaishTo?: string): Operation
|
|
54
|
+
diff(shaishFrom: string, shaishTo?: string): Array<Operation>;
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
57
|
* Returns pending changes.
|
|
57
58
|
*/
|
|
58
|
-
status(): Operation
|
|
59
|
+
status(): Array<Operation>;
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
62
|
* Applies a patch to the repository's HEAD
|
|
62
63
|
* @param patch
|
|
63
64
|
*/
|
|
64
|
-
apply(patch: Operation
|
|
65
|
+
apply(patch: Array<Operation>): void;
|
|
65
66
|
|
|
66
67
|
// It returns the reference where we are currently at
|
|
67
68
|
head(): string;
|
|
@@ -73,7 +74,7 @@ export interface RepositoryObject<T extends { [k: string]: any }> {
|
|
|
73
74
|
|
|
74
75
|
checkout(shaish: string, createBranch?: boolean): void;
|
|
75
76
|
|
|
76
|
-
logs(commits?: number): Commit
|
|
77
|
+
logs(commits?: number): Array<Commit>;
|
|
77
78
|
|
|
78
79
|
createBranch(name: string): string;
|
|
79
80
|
|
|
@@ -98,6 +99,11 @@ export interface RepositoryObject<T extends { [k: string]: any }> {
|
|
|
98
99
|
* The returned map is a readonly of remote.
|
|
99
100
|
*/
|
|
100
101
|
remote(): ReadonlyMap<string, Readonly<Reference>> | undefined;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Cherry returns the commits that are missing from upstream and the refs that have been moved since remote
|
|
105
|
+
*/
|
|
106
|
+
cherry(): { commits: Array<Commit>; refs: Map<string, Reference> };
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
/**
|
|
@@ -108,8 +114,11 @@ export class Repository<T extends { [k: PropertyKey]: any }>
|
|
|
108
114
|
{
|
|
109
115
|
constructor(obj: Partial<T>, options: RepositoryOptions<T>) {
|
|
110
116
|
// FIXME: move this to refs/remote as git would do?
|
|
111
|
-
|
|
112
117
|
this.remoteRefs = immutableMapCopy(options.history?.refs);
|
|
118
|
+
this.remoteCommits = immutableArrayCopy<Commit, string>(
|
|
119
|
+
options.history?.commits,
|
|
120
|
+
(c) => c.hash,
|
|
121
|
+
);
|
|
113
122
|
this.original = deepClone(obj);
|
|
114
123
|
// store js ref, so obj can still be modified without going through repo.data
|
|
115
124
|
this.data = obj as T;
|
|
@@ -146,10 +155,106 @@ export class Repository<T extends { [k: PropertyKey]: any }>
|
|
|
146
155
|
| ReadonlyMap<string, Readonly<Reference>>
|
|
147
156
|
| undefined;
|
|
148
157
|
|
|
158
|
+
// stores the remote state upon initialization
|
|
159
|
+
private readonly remoteCommits: ReadonlyArray<Readonly<string>> | undefined;
|
|
160
|
+
|
|
149
161
|
private observer: Observer<T>;
|
|
150
162
|
|
|
151
163
|
private readonly refs: Map<string, Reference>;
|
|
152
|
-
private readonly commits: Commit
|
|
164
|
+
private readonly commits: Array<Commit>;
|
|
165
|
+
|
|
166
|
+
cherry(): { commits: Array<Commit>; refs: Map<string, Reference> } {
|
|
167
|
+
const commits: Array<Commit> = [];
|
|
168
|
+
const refs = new Map<string, Reference>();
|
|
169
|
+
|
|
170
|
+
const collectedHashes: Array<string> = [];
|
|
171
|
+
const shouldExclude = (hash: string) =>
|
|
172
|
+
this.remoteCommits?.includes(hash) || collectedHashes.includes(hash);
|
|
173
|
+
const collect = (c: Commit) => {
|
|
174
|
+
// we can't include remote state in the pending report
|
|
175
|
+
if (shouldExclude(c.hash)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
commits.push(c);
|
|
179
|
+
collectedHashes.push(c.hash);
|
|
180
|
+
return true;
|
|
181
|
+
};
|
|
182
|
+
// collect ref updates and commits that are not present on the remote
|
|
183
|
+
for (const [key, ref] of this.refs) {
|
|
184
|
+
if (key === REFS_HEAD_KEY) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const remote = this.remoteRefs?.get(key);
|
|
188
|
+
if (!remote) {
|
|
189
|
+
// if we have no remote pair, we need to sync the ref
|
|
190
|
+
refs.set(key, ref);
|
|
191
|
+
const localCommit = this.commits.find((c) => c.hash === ref.value);
|
|
192
|
+
// if ref is not pointing to a commit move on
|
|
193
|
+
if (!localCommit) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
// map all commits to root
|
|
197
|
+
const [isAncestor, path] = mapPath(
|
|
198
|
+
this.commits,
|
|
199
|
+
localCommit,
|
|
200
|
+
undefined,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (isAncestor) {
|
|
204
|
+
for (let i = 0; i < path.length; i++) {
|
|
205
|
+
const commit = path[i];
|
|
206
|
+
collect({
|
|
207
|
+
hash: commit.hash,
|
|
208
|
+
author: commit.author,
|
|
209
|
+
changes: commit.changes,
|
|
210
|
+
message: commit.message,
|
|
211
|
+
parent: commit.parent,
|
|
212
|
+
timestamp: commit.timestamp,
|
|
213
|
+
tree: commit.tree,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (remote.value === ref.value) {
|
|
221
|
+
// early exit if remote is the same
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// local and remote refs differ
|
|
226
|
+
refs.set(key, ref);
|
|
227
|
+
const localCommit = this.commits.find((c) => c.hash === ref.value);
|
|
228
|
+
// if ref is not pointing to a commit move on
|
|
229
|
+
if (!localCommit) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
// FIXME: do we have to have the remote ref as a commit locally?
|
|
233
|
+
const remoteCommit = this.commits.find((c) => c.hash === remote.value)!;
|
|
234
|
+
const [isAncestor, path] = mapPath(
|
|
235
|
+
this.commits,
|
|
236
|
+
localCommit,
|
|
237
|
+
remoteCommit,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (isAncestor) {
|
|
241
|
+
for (let i = 0; i < path.length; i++) {
|
|
242
|
+
const commit = path[i];
|
|
243
|
+
collect({
|
|
244
|
+
hash: commit.hash,
|
|
245
|
+
author: commit.author,
|
|
246
|
+
changes: commit.changes,
|
|
247
|
+
message: commit.message,
|
|
248
|
+
parent: commit.parent,
|
|
249
|
+
timestamp: commit.timestamp,
|
|
250
|
+
tree: commit.tree,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { commits, refs };
|
|
257
|
+
}
|
|
153
258
|
|
|
154
259
|
remote(): ReadonlyMap<string, Readonly<Reference>> | undefined {
|
|
155
260
|
return this.remoteRefs;
|
|
@@ -166,8 +271,8 @@ export class Repository<T extends { [k: PropertyKey]: any }>
|
|
|
166
271
|
this.observer = observe(this.data);
|
|
167
272
|
}
|
|
168
273
|
|
|
169
|
-
apply(patch: Operation
|
|
170
|
-
const p = deepClone(patch) as Operation
|
|
274
|
+
apply(patch: Array<Operation>): JsonPatchError | undefined {
|
|
275
|
+
const p = deepClone(patch) as Array<Operation>;
|
|
171
276
|
const err = validate(p, this.data);
|
|
172
277
|
if (err) {
|
|
173
278
|
// credit goes to @NicBright
|
|
@@ -247,7 +352,7 @@ export class Repository<T extends { [k: PropertyKey]: any }>
|
|
|
247
352
|
return this.diff(commit.hash);
|
|
248
353
|
}
|
|
249
354
|
|
|
250
|
-
diff(shaishFrom: string, shaishTo?: string): Operation
|
|
355
|
+
diff(shaishFrom: string, shaishTo?: string): Array<Operation> {
|
|
251
356
|
const [cFrom] = shaishToCommit(shaishFrom, this.refs, this.commits);
|
|
252
357
|
let target: T;
|
|
253
358
|
if (shaishTo) {
|
|
@@ -393,7 +498,7 @@ export class Repository<T extends { [k: PropertyKey]: any }>
|
|
|
393
498
|
}
|
|
394
499
|
// traverse backwards and build commit tree
|
|
395
500
|
let c: Commit | undefined = commit;
|
|
396
|
-
let commitsList: Commit
|
|
501
|
+
let commitsList: Array<Commit> = [];
|
|
397
502
|
while (c !== undefined) {
|
|
398
503
|
commitsList = [c, ...commitsList];
|
|
399
504
|
c = this.commits.find((parent) => parent.hash === c?.parent);
|
|
@@ -408,8 +513,8 @@ export class Repository<T extends { [k: PropertyKey]: any }>
|
|
|
408
513
|
};
|
|
409
514
|
}
|
|
410
515
|
|
|
411
|
-
logs(numberOfCommits?: number): Commit
|
|
412
|
-
const logs: Commit
|
|
516
|
+
logs(numberOfCommits?: number): Array<Commit> {
|
|
517
|
+
const logs: Array<Commit> = [];
|
|
413
518
|
const limit = numberOfCommits ?? -1;
|
|
414
519
|
let c = this.commitAtHead();
|
|
415
520
|
let counter = 0;
|
|
@@ -458,7 +563,7 @@ export class Repository<T extends { [k: PropertyKey]: any }>
|
|
|
458
563
|
// *---*
|
|
459
564
|
// \
|
|
460
565
|
// *---*---* (master, foo)
|
|
461
|
-
const [isAncestor] = mapPath(
|
|
566
|
+
const [isAncestor] = mapPath(this.commits, srcCommit, headCommit);
|
|
462
567
|
if (isAncestor) {
|
|
463
568
|
this.moveRef(this.head(), srcCommit);
|
|
464
569
|
this.moveTo(srcCommit);
|
package/src/utils.test.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { test } from "tap";
|
|
2
|
-
import { cleanAuthor } from "./utils";
|
|
2
|
+
import { cleanAuthor, mapPath, shaishToCommit } from "./utils";
|
|
3
|
+
import { Repository } from "./repository";
|
|
4
|
+
import { ComplexObject, testAuthor } from "./test.utils";
|
|
3
5
|
|
|
4
6
|
test("author <email@domain.info>", (t) => {
|
|
5
7
|
const [name, email] = cleanAuthor("author <email@domain.info>");
|
|
@@ -52,3 +54,88 @@ test("empty author", (t) => {
|
|
|
52
54
|
);
|
|
53
55
|
t.end();
|
|
54
56
|
});
|
|
57
|
+
|
|
58
|
+
test("mapPath", async (t) => {
|
|
59
|
+
t.test("find root", async (t) => {
|
|
60
|
+
const repo = new Repository<ComplexObject>({}, {});
|
|
61
|
+
repo.data.name = "name";
|
|
62
|
+
const hash = await repo.commit("set name", testAuthor);
|
|
63
|
+
|
|
64
|
+
const { commits, refs } = repo.getHistory();
|
|
65
|
+
const [c] = shaishToCommit(hash, refs, commits);
|
|
66
|
+
const [isAncestor, path] = mapPath(commits, c);
|
|
67
|
+
t.equal(path.length, 1, "path does not contain 1 commit");
|
|
68
|
+
t.equal(isAncestor, true, "root is not ancestor of commit");
|
|
69
|
+
t.matchOnlyStrict(path[0], c, "path does not contain the right commit");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
t.test("finds full path to root", async (t) => {
|
|
73
|
+
const repo = new Repository<ComplexObject>({}, {});
|
|
74
|
+
repo.data.name = "name";
|
|
75
|
+
const h1 = await repo.commit("set name", testAuthor);
|
|
76
|
+
repo.data.description = "description";
|
|
77
|
+
const h2 = await repo.commit("set desc", testAuthor);
|
|
78
|
+
|
|
79
|
+
const { commits, refs } = repo.getHistory();
|
|
80
|
+
const [c1] = shaishToCommit(h1, refs, commits);
|
|
81
|
+
const [c2] = shaishToCommit(h2, refs, commits);
|
|
82
|
+
const [isAncestor, path] = mapPath(commits, c2);
|
|
83
|
+
|
|
84
|
+
t.equal(path.length, 2, "path does not contain 1 commit");
|
|
85
|
+
t.equal(isAncestor, true, "root is not ancestor of commit");
|
|
86
|
+
t.matchOnlyStrict(
|
|
87
|
+
path[0],
|
|
88
|
+
c2,
|
|
89
|
+
"path does not contain the right commit at 0",
|
|
90
|
+
);
|
|
91
|
+
t.matchOnlyStrict(
|
|
92
|
+
path[1],
|
|
93
|
+
c1,
|
|
94
|
+
"path does not contain the right commit at 1",
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
t.test("parent-to-child no ancestor", async (t) => {
|
|
99
|
+
const repo = new Repository<ComplexObject>({}, {});
|
|
100
|
+
repo.data.name = "name";
|
|
101
|
+
const h1 = await repo.commit("set name", testAuthor);
|
|
102
|
+
repo.data.description = "description";
|
|
103
|
+
const h2 = await repo.commit("set desc", testAuthor);
|
|
104
|
+
|
|
105
|
+
const { commits, refs } = repo.getHistory();
|
|
106
|
+
const [c1] = shaishToCommit(h1, refs, commits);
|
|
107
|
+
const [c2] = shaishToCommit(h2, refs, commits);
|
|
108
|
+
const [isAncestor, path] = mapPath(commits, c1, c2);
|
|
109
|
+
|
|
110
|
+
t.equal(path.length, 0, "path contains a commit");
|
|
111
|
+
t.equal(isAncestor, false, "child must not be an ancestor of parent");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
t.test("finds path across 2 branches", async (t) => {
|
|
115
|
+
const repo = new Repository<ComplexObject>({}, {});
|
|
116
|
+
repo.data.name = "name";
|
|
117
|
+
const h1 = await repo.commit("set name", testAuthor);
|
|
118
|
+
repo.checkout("branch", true);
|
|
119
|
+
|
|
120
|
+
repo.data.description = "description";
|
|
121
|
+
const h2 = await repo.commit("set desc", testAuthor);
|
|
122
|
+
|
|
123
|
+
const { commits, refs } = repo.getHistory();
|
|
124
|
+
const [c1] = shaishToCommit(h1, refs, commits);
|
|
125
|
+
const [c2] = shaishToCommit(h2, refs, commits);
|
|
126
|
+
const [isAncestor, path] = mapPath(commits, c2);
|
|
127
|
+
|
|
128
|
+
t.equal(path.length, 2, "path does not contain 1 commit");
|
|
129
|
+
t.equal(isAncestor, true, "root is not ancestor of commit");
|
|
130
|
+
t.matchOnlyStrict(
|
|
131
|
+
path[0],
|
|
132
|
+
c2,
|
|
133
|
+
"path does not contain the right commit at 0",
|
|
134
|
+
);
|
|
135
|
+
t.matchOnlyStrict(
|
|
136
|
+
path[1],
|
|
137
|
+
c1,
|
|
138
|
+
"path does not contain the right commit at 1",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
});
|