@fluidframework/tree 2.61.0 → 2.62.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/CHANGELOG.md +162 -0
- package/api-report/tree.alpha.api.md +26 -21
- package/api-report/tree.beta.api.md +15 -0
- package/api-report/tree.legacy.beta.api.md +18 -0
- package/dist/alpha.d.ts +8 -8
- package/dist/api.d.ts +17 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +24 -0
- package/dist/api.js.map +1 -0
- package/dist/beta.d.ts +5 -0
- package/dist/codec/codec.d.ts +3 -5
- package/dist/codec/codec.d.ts.map +1 -1
- package/dist/codec/codec.js +9 -2
- package/dist/codec/codec.js.map +1 -1
- package/dist/codec/index.d.ts +0 -1
- package/dist/codec/index.d.ts.map +1 -1
- package/dist/codec/index.js +1 -3
- package/dist/codec/index.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/rebase/index.d.ts +1 -1
- package/dist/core/rebase/index.d.ts.map +1 -1
- package/dist/core/rebase/index.js +2 -1
- package/dist/core/rebase/index.js.map +1 -1
- package/dist/core/rebase/utils.d.ts +10 -0
- package/dist/core/rebase/utils.d.ts.map +1 -1
- package/dist/core/rebase/utils.js +20 -1
- package/dist/core/rebase/utils.js.map +1 -1
- package/dist/core/tree/detachedFieldIndex.js +1 -1
- package/dist/core/tree/detachedFieldIndex.js.map +1 -1
- package/dist/external-utilities/index.d.ts +1 -1
- package/dist/external-utilities/index.d.ts.map +1 -1
- package/dist/external-utilities/index.js +1 -2
- package/dist/external-utilities/index.js.map +1 -1
- package/dist/external-utilities/typeboxValidator.d.ts +0 -13
- package/dist/external-utilities/typeboxValidator.d.ts.map +1 -1
- package/dist/external-utilities/typeboxValidator.js +3 -5
- package/dist/external-utilities/typeboxValidator.js.map +1 -1
- package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +2 -0
- package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
- package/dist/feature-libraries/flex-tree/index.d.ts +1 -0
- package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/index.js +4 -1
- package/dist/feature-libraries/flex-tree/index.js.map +1 -1
- package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/lazyNode.js +15 -8
- package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
- package/dist/feature-libraries/flex-tree/observer.d.ts +32 -0
- package/dist/feature-libraries/flex-tree/observer.d.ts.map +1 -0
- package/dist/feature-libraries/flex-tree/observer.js +33 -0
- package/dist/feature-libraries/flex-tree/observer.js.map +1 -0
- package/dist/feature-libraries/index.d.ts +1 -1
- package/dist/feature-libraries/index.d.ts.map +1 -1
- package/dist/feature-libraries/index.js +3 -1
- package/dist/feature-libraries/index.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +7 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/shared-tree/index.d.ts +2 -2
- package/dist/shared-tree/index.d.ts.map +1 -1
- package/dist/shared-tree/index.js.map +1 -1
- package/dist/shared-tree/schematizingTreeView.js +2 -2
- package/dist/shared-tree/schematizingTreeView.js.map +1 -1
- package/dist/shared-tree/sharedTree.d.ts +21 -6
- package/dist/shared-tree/sharedTree.d.ts.map +1 -1
- package/dist/shared-tree/sharedTree.js +76 -37
- package/dist/shared-tree/sharedTree.js.map +1 -1
- package/dist/shared-tree/treeAlpha.d.ts +114 -1
- package/dist/shared-tree/treeAlpha.d.ts.map +1 -1
- package/dist/shared-tree/treeAlpha.js +140 -1
- package/dist/shared-tree/treeAlpha.js.map +1 -1
- package/dist/shared-tree/treeCheckout.d.ts +13 -7
- package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
- package/dist/shared-tree/treeCheckout.js +115 -85
- package/dist/shared-tree/treeCheckout.js.map +1 -1
- package/dist/shared-tree-core/branch.d.ts +3 -0
- package/dist/shared-tree-core/branch.d.ts.map +1 -1
- package/dist/shared-tree-core/branch.js.map +1 -1
- package/dist/shared-tree-core/branchIdCodec.d.ts +11 -0
- package/dist/shared-tree-core/branchIdCodec.d.ts.map +1 -0
- package/dist/shared-tree-core/branchIdCodec.js +18 -0
- package/dist/shared-tree-core/branchIdCodec.js.map +1 -0
- package/dist/shared-tree-core/editManager.d.ts +39 -64
- package/dist/shared-tree-core/editManager.d.ts.map +1 -1
- package/dist/shared-tree-core/editManager.js +455 -295
- package/dist/shared-tree-core/editManager.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecs.d.ts +1 -1
- package/dist/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerCodecs.js +7 -96
- package/dist/shared-tree-core/editManagerCodecs.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsCommons.d.ts +17 -0
- package/dist/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -0
- package/dist/shared-tree-core/editManagerCodecsCommons.js +139 -0
- package/dist/shared-tree-core/editManagerCodecsCommons.js.map +1 -0
- package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts +16 -0
- package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -0
- package/dist/shared-tree-core/editManagerCodecsV1toV4.js +39 -0
- package/dist/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -0
- package/dist/shared-tree-core/editManagerCodecsV5.d.ts +16 -0
- package/dist/shared-tree-core/editManagerCodecsV5.d.ts.map +1 -0
- package/dist/shared-tree-core/editManagerCodecsV5.js +58 -0
- package/dist/shared-tree-core/editManagerCodecsV5.js.map +1 -0
- package/dist/shared-tree-core/{editManagerFormat.d.ts → editManagerFormatCommons.d.ts} +31 -7
- package/dist/shared-tree-core/editManagerFormatCommons.d.ts.map +1 -0
- package/dist/shared-tree-core/{editManagerFormat.js → editManagerFormatCommons.js} +13 -12
- package/dist/shared-tree-core/editManagerFormatCommons.js.map +1 -0
- package/dist/shared-tree-core/editManagerFormatV1toV4.d.ts +31 -0
- package/dist/shared-tree-core/editManagerFormatV1toV4.d.ts.map +1 -0
- package/dist/shared-tree-core/editManagerFormatV1toV4.js +24 -0
- package/dist/shared-tree-core/editManagerFormatV1toV4.js.map +1 -0
- package/dist/shared-tree-core/editManagerFormatV5.d.ts +62 -0
- package/dist/shared-tree-core/editManagerFormatV5.d.ts.map +1 -0
- package/dist/shared-tree-core/editManagerFormatV5.js +20 -0
- package/dist/shared-tree-core/editManagerFormatV5.js.map +1 -0
- package/dist/shared-tree-core/index.d.ts +3 -3
- package/dist/shared-tree-core/index.d.ts.map +1 -1
- package/dist/shared-tree-core/index.js.map +1 -1
- package/dist/shared-tree-core/messageCodecV1ToV4.d.ts +11 -0
- package/dist/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -0
- package/dist/shared-tree-core/messageCodecV1ToV4.js +59 -0
- package/dist/shared-tree-core/messageCodecV1ToV4.js.map +1 -0
- package/dist/shared-tree-core/messageCodecV5.d.ts +11 -0
- package/dist/shared-tree-core/messageCodecV5.d.ts.map +1 -0
- package/dist/shared-tree-core/messageCodecV5.js +78 -0
- package/dist/shared-tree-core/messageCodecV5.js.map +1 -0
- package/dist/shared-tree-core/messageCodecs.d.ts.map +1 -1
- package/dist/shared-tree-core/messageCodecs.js +16 -47
- package/dist/shared-tree-core/messageCodecs.js.map +1 -1
- package/dist/shared-tree-core/{messageFormat.d.ts → messageFormatV1ToV4.d.ts} +1 -1
- package/dist/shared-tree-core/messageFormatV1ToV4.d.ts.map +1 -0
- package/dist/shared-tree-core/{messageFormat.js → messageFormatV1ToV4.js} +1 -1
- package/dist/shared-tree-core/messageFormatV1ToV4.js.map +1 -0
- package/dist/shared-tree-core/messageFormatV5.d.ts +42 -0
- package/dist/shared-tree-core/messageFormatV5.d.ts.map +1 -0
- package/dist/shared-tree-core/messageFormatV5.js +20 -0
- package/dist/shared-tree-core/messageFormatV5.js.map +1 -0
- package/dist/shared-tree-core/messageTypes.d.ts +12 -2
- package/dist/shared-tree-core/messageTypes.d.ts.map +1 -1
- package/dist/shared-tree-core/messageTypes.js.map +1 -1
- package/dist/shared-tree-core/sequenceIdUtils.d.ts +1 -1
- package/dist/shared-tree-core/sequenceIdUtils.d.ts.map +1 -1
- package/dist/shared-tree-core/sequenceIdUtils.js.map +1 -1
- package/dist/shared-tree-core/sharedTreeCore.d.ts +19 -5
- package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
- package/dist/shared-tree-core/sharedTreeCore.js +182 -58
- package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
- package/dist/simple-tree/api/tree.d.ts +17 -0
- package/dist/simple-tree/api/tree.d.ts.map +1 -1
- package/dist/simple-tree/api/tree.js +2 -0
- package/dist/simple-tree/api/tree.js.map +1 -1
- package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
- package/dist/simple-tree/core/unhydratedFlexTree.js +7 -1
- package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
- package/dist/treeFactory.d.ts +38 -9
- package/dist/treeFactory.d.ts.map +1 -1
- package/dist/treeFactory.js +44 -9
- package/dist/treeFactory.js.map +1 -1
- package/lib/alpha.d.ts +8 -8
- package/lib/api.d.ts +17 -0
- package/lib/api.d.ts.map +1 -0
- package/lib/api.js +22 -0
- package/lib/api.js.map +1 -0
- package/lib/beta.d.ts +5 -0
- package/lib/codec/codec.d.ts +3 -5
- package/lib/codec/codec.d.ts.map +1 -1
- package/lib/codec/codec.js +8 -1
- package/lib/codec/codec.js.map +1 -1
- package/lib/codec/index.d.ts +0 -1
- package/lib/codec/index.d.ts.map +1 -1
- package/lib/codec/index.js +0 -1
- package/lib/codec/index.js.map +1 -1
- package/lib/core/index.d.ts +1 -1
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +1 -1
- package/lib/core/index.js.map +1 -1
- package/lib/core/rebase/index.d.ts +1 -1
- package/lib/core/rebase/index.d.ts.map +1 -1
- package/lib/core/rebase/index.js +1 -1
- package/lib/core/rebase/index.js.map +1 -1
- package/lib/core/rebase/utils.d.ts +10 -0
- package/lib/core/rebase/utils.d.ts.map +1 -1
- package/lib/core/rebase/utils.js +18 -0
- package/lib/core/rebase/utils.js.map +1 -1
- package/lib/core/tree/detachedFieldIndex.js +2 -2
- package/lib/core/tree/detachedFieldIndex.js.map +1 -1
- package/lib/external-utilities/index.d.ts +1 -1
- package/lib/external-utilities/index.d.ts.map +1 -1
- package/lib/external-utilities/index.js +1 -1
- package/lib/external-utilities/index.js.map +1 -1
- package/lib/external-utilities/typeboxValidator.d.ts +0 -13
- package/lib/external-utilities/typeboxValidator.d.ts.map +1 -1
- package/lib/external-utilities/typeboxValidator.js +1 -3
- package/lib/external-utilities/typeboxValidator.js.map +1 -1
- package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +2 -0
- package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
- package/lib/feature-libraries/flex-tree/index.d.ts +1 -0
- package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/index.js +1 -0
- package/lib/feature-libraries/flex-tree/index.js.map +1 -1
- package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/lazyNode.js +15 -8
- package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
- package/lib/feature-libraries/flex-tree/observer.d.ts +32 -0
- package/lib/feature-libraries/flex-tree/observer.d.ts.map +1 -0
- package/lib/feature-libraries/flex-tree/observer.js +40 -0
- package/lib/feature-libraries/flex-tree/observer.js.map +1 -0
- package/lib/feature-libraries/index.d.ts +1 -1
- package/lib/feature-libraries/index.d.ts.map +1 -1
- package/lib/feature-libraries/index.js +1 -1
- package/lib/feature-libraries/index.js.map +1 -1
- package/lib/index.d.ts +5 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -3
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +7 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/shared-tree/index.d.ts +2 -2
- package/lib/shared-tree/index.d.ts.map +1 -1
- package/lib/shared-tree/index.js.map +1 -1
- package/lib/shared-tree/schematizingTreeView.js +2 -2
- package/lib/shared-tree/schematizingTreeView.js.map +1 -1
- package/lib/shared-tree/sharedTree.d.ts +21 -6
- package/lib/shared-tree/sharedTree.d.ts.map +1 -1
- package/lib/shared-tree/sharedTree.js +78 -39
- package/lib/shared-tree/sharedTree.js.map +1 -1
- package/lib/shared-tree/treeAlpha.d.ts +114 -1
- package/lib/shared-tree/treeAlpha.d.ts.map +1 -1
- package/lib/shared-tree/treeAlpha.js +143 -4
- package/lib/shared-tree/treeAlpha.js.map +1 -1
- package/lib/shared-tree/treeCheckout.d.ts +13 -7
- package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
- package/lib/shared-tree/treeCheckout.js +117 -87
- package/lib/shared-tree/treeCheckout.js.map +1 -1
- package/lib/shared-tree-core/branch.d.ts +3 -0
- package/lib/shared-tree-core/branch.d.ts.map +1 -1
- package/lib/shared-tree-core/branch.js.map +1 -1
- package/lib/shared-tree-core/branchIdCodec.d.ts +11 -0
- package/lib/shared-tree-core/branchIdCodec.d.ts.map +1 -0
- package/lib/shared-tree-core/branchIdCodec.js +13 -0
- package/lib/shared-tree-core/branchIdCodec.js.map +1 -0
- package/lib/shared-tree-core/editManager.d.ts +39 -64
- package/lib/shared-tree-core/editManager.d.ts.map +1 -1
- package/lib/shared-tree-core/editManager.js +455 -295
- package/lib/shared-tree-core/editManager.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecs.d.ts +1 -1
- package/lib/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerCodecs.js +4 -93
- package/lib/shared-tree-core/editManagerCodecs.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsCommons.d.ts +17 -0
- package/lib/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -0
- package/lib/shared-tree-core/editManagerCodecsCommons.js +134 -0
- package/lib/shared-tree-core/editManagerCodecsCommons.js.map +1 -0
- package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts +16 -0
- package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -0
- package/lib/shared-tree-core/editManagerCodecsV1toV4.js +35 -0
- package/lib/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -0
- package/lib/shared-tree-core/editManagerCodecsV5.d.ts +16 -0
- package/lib/shared-tree-core/editManagerCodecsV5.d.ts.map +1 -0
- package/lib/shared-tree-core/editManagerCodecsV5.js +54 -0
- package/lib/shared-tree-core/editManagerCodecsV5.js.map +1 -0
- package/lib/shared-tree-core/{editManagerFormat.d.ts → editManagerFormatCommons.d.ts} +31 -7
- package/lib/shared-tree-core/editManagerFormatCommons.d.ts.map +1 -0
- package/lib/shared-tree-core/{editManagerFormat.js → editManagerFormatCommons.js} +10 -11
- package/lib/shared-tree-core/editManagerFormatCommons.js.map +1 -0
- package/lib/shared-tree-core/editManagerFormatV1toV4.d.ts +31 -0
- package/lib/shared-tree-core/editManagerFormatV1toV4.d.ts.map +1 -0
- package/lib/shared-tree-core/editManagerFormatV1toV4.js +20 -0
- package/lib/shared-tree-core/editManagerFormatV1toV4.js.map +1 -0
- package/lib/shared-tree-core/editManagerFormatV5.d.ts +62 -0
- package/lib/shared-tree-core/editManagerFormatV5.d.ts.map +1 -0
- package/lib/shared-tree-core/editManagerFormatV5.js +16 -0
- package/lib/shared-tree-core/editManagerFormatV5.js.map +1 -0
- package/lib/shared-tree-core/index.d.ts +3 -3
- package/lib/shared-tree-core/index.d.ts.map +1 -1
- package/lib/shared-tree-core/index.js.map +1 -1
- package/lib/shared-tree-core/messageCodecV1ToV4.d.ts +11 -0
- package/lib/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -0
- package/lib/shared-tree-core/messageCodecV1ToV4.js +55 -0
- package/lib/shared-tree-core/messageCodecV1ToV4.js.map +1 -0
- package/lib/shared-tree-core/messageCodecV5.d.ts +11 -0
- package/lib/shared-tree-core/messageCodecV5.d.ts.map +1 -0
- package/lib/shared-tree-core/messageCodecV5.js +74 -0
- package/lib/shared-tree-core/messageCodecV5.js.map +1 -0
- package/lib/shared-tree-core/messageCodecs.d.ts.map +1 -1
- package/lib/shared-tree-core/messageCodecs.js +17 -48
- package/lib/shared-tree-core/messageCodecs.js.map +1 -1
- package/lib/shared-tree-core/{messageFormat.d.ts → messageFormatV1ToV4.d.ts} +1 -1
- package/lib/shared-tree-core/messageFormatV1ToV4.d.ts.map +1 -0
- package/lib/shared-tree-core/{messageFormat.js → messageFormatV1ToV4.js} +1 -1
- package/lib/shared-tree-core/messageFormatV1ToV4.js.map +1 -0
- package/lib/shared-tree-core/messageFormatV5.d.ts +42 -0
- package/lib/shared-tree-core/messageFormatV5.d.ts.map +1 -0
- package/lib/shared-tree-core/messageFormatV5.js +16 -0
- package/lib/shared-tree-core/messageFormatV5.js.map +1 -0
- package/lib/shared-tree-core/messageTypes.d.ts +12 -2
- package/lib/shared-tree-core/messageTypes.d.ts.map +1 -1
- package/lib/shared-tree-core/messageTypes.js.map +1 -1
- package/lib/shared-tree-core/sequenceIdUtils.d.ts +1 -1
- package/lib/shared-tree-core/sequenceIdUtils.d.ts.map +1 -1
- package/lib/shared-tree-core/sequenceIdUtils.js.map +1 -1
- package/lib/shared-tree-core/sharedTreeCore.d.ts +19 -5
- package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
- package/lib/shared-tree-core/sharedTreeCore.js +183 -59
- package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
- package/lib/simple-tree/api/tree.d.ts +17 -0
- package/lib/simple-tree/api/tree.d.ts.map +1 -1
- package/lib/simple-tree/api/tree.js +2 -0
- package/lib/simple-tree/api/tree.js.map +1 -1
- package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
- package/lib/simple-tree/core/unhydratedFlexTree.js +8 -2
- package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
- package/lib/treeFactory.d.ts +38 -9
- package/lib/treeFactory.d.ts.map +1 -1
- package/lib/treeFactory.js +41 -8
- package/lib/treeFactory.js.map +1 -1
- package/package.json +25 -25
- package/src/api.ts +30 -0
- package/src/codec/codec.ts +12 -6
- package/src/codec/index.ts +0 -1
- package/src/core/index.ts +1 -0
- package/src/core/rebase/index.ts +1 -0
- package/src/core/rebase/utils.ts +27 -0
- package/src/core/tree/detachedFieldIndex.ts +2 -2
- package/src/external-utilities/index.ts +1 -1
- package/src/external-utilities/typeboxValidator.ts +1 -3
- package/src/feature-libraries/flex-tree/flexTreeTypes.ts +2 -0
- package/src/feature-libraries/flex-tree/index.ts +2 -0
- package/src/feature-libraries/flex-tree/lazyNode.ts +13 -3
- package/src/feature-libraries/flex-tree/observer.ts +64 -0
- package/src/feature-libraries/index.ts +3 -0
- package/src/index.ts +6 -4
- package/src/packageVersion.ts +1 -1
- package/src/shared-tree/index.ts +2 -0
- package/src/shared-tree/schematizingTreeView.ts +2 -2
- package/src/shared-tree/sharedTree.ts +116 -52
- package/src/shared-tree/treeAlpha.ts +309 -4
- package/src/shared-tree/treeCheckout.ts +152 -100
- package/src/shared-tree-core/branch.ts +7 -0
- package/src/shared-tree-core/branchIdCodec.ts +28 -0
- package/src/shared-tree-core/editManager.ts +729 -430
- package/src/shared-tree-core/editManagerCodecs.ts +4 -164
- package/src/shared-tree-core/editManagerCodecsCommons.ts +245 -0
- package/src/shared-tree-core/editManagerCodecsV1toV4.ts +108 -0
- package/src/shared-tree-core/editManagerCodecsV5.ts +156 -0
- package/src/shared-tree-core/{editManagerFormat.ts → editManagerFormatCommons.ts} +17 -13
- package/src/shared-tree-core/editManagerFormatV1toV4.ts +42 -0
- package/src/shared-tree-core/editManagerFormatV5.ts +35 -0
- package/src/shared-tree-core/index.ts +3 -1
- package/src/shared-tree-core/messageCodecV1ToV4.ts +104 -0
- package/src/shared-tree-core/messageCodecV5.ts +131 -0
- package/src/shared-tree-core/messageCodecs.ts +16 -85
- package/src/shared-tree-core/messageFormatV5.ts +50 -0
- package/src/shared-tree-core/messageTypes.ts +15 -2
- package/src/shared-tree-core/sequenceIdUtils.ts +1 -1
- package/src/shared-tree-core/sharedTreeCore.ts +281 -85
- package/src/simple-tree/api/tree.ts +23 -0
- package/src/simple-tree/core/unhydratedFlexTree.ts +11 -2
- package/src/treeFactory.ts +48 -8
- package/dist/codec/noopValidator.d.ts +0 -13
- package/dist/codec/noopValidator.d.ts.map +0 -1
- package/dist/codec/noopValidator.js +0 -17
- package/dist/codec/noopValidator.js.map +0 -1
- package/dist/shared-tree-core/editManagerFormat.d.ts.map +0 -1
- package/dist/shared-tree-core/editManagerFormat.js.map +0 -1
- package/dist/shared-tree-core/messageFormat.d.ts.map +0 -1
- package/dist/shared-tree-core/messageFormat.js.map +0 -1
- package/docs/user-facing/schema-evolution.md +0 -309
- package/lib/codec/noopValidator.d.ts +0 -13
- package/lib/codec/noopValidator.d.ts.map +0 -1
- package/lib/codec/noopValidator.js +0 -14
- package/lib/codec/noopValidator.js.map +0 -1
- package/lib/shared-tree-core/editManagerFormat.d.ts.map +0 -1
- package/lib/shared-tree-core/editManagerFormat.js.map +0 -1
- package/lib/shared-tree-core/messageFormat.d.ts.map +0 -1
- package/lib/shared-tree-core/messageFormat.js.map +0 -1
- package/src/codec/noopValidator.ts +0 -18
- /package/src/shared-tree-core/{messageFormat.ts → messageFormatV1ToV4.ts} +0 -0
|
@@ -21,14 +21,19 @@ import {
|
|
|
21
21
|
} from "../core/index.js";
|
|
22
22
|
import { type Mutable, brand, getOrCreate, mapIterable } from "../util/index.js";
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
SharedTreeBranch,
|
|
26
|
+
type BranchTrimmingEvents,
|
|
27
|
+
onForkTransitive,
|
|
28
|
+
type BranchId,
|
|
29
|
+
} from "./branch.js";
|
|
25
30
|
import type {
|
|
26
31
|
Commit,
|
|
27
32
|
SeqNumber,
|
|
28
33
|
SequenceId,
|
|
29
34
|
SequencedCommit,
|
|
30
35
|
SummarySessionBranch,
|
|
31
|
-
} from "./
|
|
36
|
+
} from "./editManagerFormatCommons.js";
|
|
32
37
|
import {
|
|
33
38
|
getUpperBoundOfPreviousSequenceId,
|
|
34
39
|
equalSequenceIds,
|
|
@@ -40,6 +45,7 @@ import {
|
|
|
40
45
|
TelemetryEventBatcher,
|
|
41
46
|
type ITelemetryLoggerExt,
|
|
42
47
|
} from "@fluidframework/telemetry-utils/internal";
|
|
48
|
+
import type { Listenable } from "@fluidframework/core-interfaces";
|
|
43
49
|
|
|
44
50
|
export const minimumPossibleSequenceNumber: SeqNumber = brand(Number.MIN_SAFE_INTEGER);
|
|
45
51
|
const minimumPossibleSequenceId: SequenceId = {
|
|
@@ -71,42 +77,12 @@ export class EditManager<
|
|
|
71
77
|
> {
|
|
72
78
|
private readonly _events = createEmitter<BranchTrimmingEvents>();
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
private readonly trunk: SharedTreeBranch<TEditor, TChangeset>;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Records extra data associated with commits in the {@link trunk}.
|
|
79
|
-
* This does not include an entry for the {@link trunkBase}.
|
|
80
|
-
*/
|
|
81
|
-
private readonly trunkMetadata = new Map<
|
|
82
|
-
RevisionTag,
|
|
83
|
-
{ sequenceId: SequenceId; sessionId: SessionId }
|
|
84
|
-
>();
|
|
85
|
-
/**
|
|
86
|
-
* A map from a sequence id to the commit in the {@link trunk} which has that sequence id.
|
|
87
|
-
* This also includes an entry for the {@link trunkBase} which always has the lowest key in the map.
|
|
88
|
-
*/
|
|
89
|
-
private readonly sequenceMap = new BTree<SequenceId, GraphCommit<TChangeset>>(
|
|
90
|
-
undefined,
|
|
91
|
-
sequenceIdComparator,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Branches are maintained to represent the local change list that the issuing client had
|
|
96
|
-
* at the time of submitting the latest known edit on the branch.
|
|
97
|
-
* This means the head commit of each branch is always in its original (non-rebased) form.
|
|
98
|
-
*/
|
|
99
|
-
private readonly peerLocalBranches: Map<SessionId, SharedTreeBranch<TEditor, TChangeset>> =
|
|
100
|
-
new Map();
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* This branch holds the changes made by this client which have not yet been confirmed as sequenced changes.
|
|
104
|
-
*/
|
|
105
|
-
public readonly localBranch: SharedTreeBranch<TEditor, TChangeset>;
|
|
80
|
+
private readonly sharedBranches = new Map<BranchId, SharedBranch<TEditor, TChangeset>>();
|
|
106
81
|
|
|
107
82
|
/**
|
|
108
|
-
* Tracks where on the trunk all registered branches are based.
|
|
109
|
-
*
|
|
83
|
+
* Tracks where on the trunk of the main branch all registered branches are based.
|
|
84
|
+
* Each key is the sequence id of a commit on the trunk,
|
|
85
|
+
* and the value is the set of all branches who have that commit as their common ancestor with the trunk.
|
|
110
86
|
*
|
|
111
87
|
* @remarks
|
|
112
88
|
* This does not include the local branch.
|
|
@@ -132,12 +108,6 @@ export class EditManager<
|
|
|
132
108
|
*/
|
|
133
109
|
private trunkBase: GraphCommit<TChangeset>;
|
|
134
110
|
|
|
135
|
-
/**
|
|
136
|
-
* The list of commits (from oldest to most recent) that are on the local branch but not on the trunk.
|
|
137
|
-
* When a local commit is sequenced, the first commit in this list shifted onto the tip of the trunk.
|
|
138
|
-
*/
|
|
139
|
-
private readonly localCommits: GraphCommit<TChangeset>[] = [];
|
|
140
|
-
|
|
141
111
|
private readonly telemetryEventBatcher:
|
|
142
112
|
| TelemetryEventBatcher<keyof RebaseStatsWithDuration>
|
|
143
113
|
| undefined;
|
|
@@ -145,18 +115,20 @@ export class EditManager<
|
|
|
145
115
|
/**
|
|
146
116
|
* @param changeFamily - the change family of changes on the trunk and local branch
|
|
147
117
|
* @param localSessionId - the id of the local session that will be used for local commits
|
|
118
|
+
* @param mintRevisionTag - a function which generates globally unique revision tags
|
|
119
|
+
* @param onSharedBranchCreated - called when a new shared branch is created. This is not called for the main branch.
|
|
148
120
|
*/
|
|
149
121
|
public constructor(
|
|
150
122
|
public readonly changeFamily: TChangeFamily,
|
|
151
123
|
public readonly localSessionId: SessionId,
|
|
152
124
|
private readonly mintRevisionTag: () => RevisionTag,
|
|
125
|
+
private readonly onSharedBranchCreated?: (branchId: BranchId) => void,
|
|
153
126
|
logger?: ITelemetryLoggerExt,
|
|
154
127
|
) {
|
|
155
128
|
this.trunkBase = {
|
|
156
129
|
revision: rootRevision,
|
|
157
130
|
change: changeFamily.rebaser.compose([]),
|
|
158
131
|
};
|
|
159
|
-
this.sequenceMap.set(minimumPossibleSequenceId, this.trunkBase);
|
|
160
132
|
|
|
161
133
|
if (logger !== undefined) {
|
|
162
134
|
this.telemetryEventBatcher = new TelemetryEventBatcher(
|
|
@@ -169,39 +141,23 @@ export class EditManager<
|
|
|
169
141
|
);
|
|
170
142
|
}
|
|
171
143
|
|
|
172
|
-
|
|
144
|
+
const mainTrunk = new SharedTreeBranch(
|
|
173
145
|
this.trunkBase,
|
|
174
146
|
changeFamily,
|
|
175
147
|
mintRevisionTag,
|
|
176
148
|
this._events,
|
|
177
149
|
this.telemetryEventBatcher,
|
|
178
150
|
);
|
|
179
|
-
this.localBranch = new SharedTreeBranch(
|
|
180
|
-
this.trunk.getHead(),
|
|
181
|
-
changeFamily,
|
|
182
|
-
mintRevisionTag,
|
|
183
|
-
this._events,
|
|
184
|
-
this.telemetryEventBatcher,
|
|
185
|
-
);
|
|
186
151
|
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
for (const commit of event.newCommits) {
|
|
190
|
-
this.localCommits.push(commit);
|
|
191
|
-
}
|
|
192
|
-
} else {
|
|
193
|
-
this.localCommits.length = 0;
|
|
194
|
-
findCommonAncestor(
|
|
195
|
-
[this.localBranch.getHead(), this.localCommits],
|
|
196
|
-
this.trunk.getHead(),
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
152
|
+
this.createAndAddSharedBranch("main", undefined, undefined, mainTrunk);
|
|
153
|
+
}
|
|
200
154
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
155
|
+
public getLocalBranch(branchId: BranchId): SharedTreeBranch<TEditor, TChangeset> {
|
|
156
|
+
return this.getSharedBranch(branchId).localBranch;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private getSharedBranch(branchId: BranchId): SharedBranch<TEditor, TChangeset> {
|
|
160
|
+
return this.sharedBranches.get(branchId) ?? fail(0xc56 /* Branch does not exist */);
|
|
205
161
|
}
|
|
206
162
|
|
|
207
163
|
/**
|
|
@@ -224,13 +180,13 @@ export class EditManager<
|
|
|
224
180
|
const offAfterRebase = branch.events.on("afterChange", (args) => {
|
|
225
181
|
if (args.type === "rebase") {
|
|
226
182
|
this.trackBranch(branch);
|
|
227
|
-
this.
|
|
183
|
+
this.trimHistory();
|
|
228
184
|
}
|
|
229
185
|
});
|
|
230
186
|
// When the branch is disposed, update our branch set and trim the trunk
|
|
231
187
|
const offDispose = branch.events.on("dispose", () => {
|
|
232
188
|
this.untrackBranch(branch);
|
|
233
|
-
this.
|
|
189
|
+
this.trimHistory();
|
|
234
190
|
offBeforeRebase();
|
|
235
191
|
offAfterRebase();
|
|
236
192
|
offDispose();
|
|
@@ -238,10 +194,11 @@ export class EditManager<
|
|
|
238
194
|
}
|
|
239
195
|
|
|
240
196
|
private trackBranch(b: SharedTreeBranch<TEditor, TChangeset>): void {
|
|
197
|
+
const main = this.getSharedBranch("main");
|
|
241
198
|
const trunkCommit =
|
|
242
|
-
findCommonAncestor(
|
|
243
|
-
fail(0xad2 /* Expected branch to be related to
|
|
244
|
-
const sequenceId =
|
|
199
|
+
findCommonAncestor(main.trunk.getHead(), b.getHead()) ??
|
|
200
|
+
fail(0xad2 /* Expected branch to be related to main */);
|
|
201
|
+
const sequenceId = main.getCommitSequenceId(trunkCommit.revision);
|
|
245
202
|
const branches = getOrCreate(this.trunkBranches, sequenceId, () => new Set());
|
|
246
203
|
|
|
247
204
|
assert(!branches.has(b), 0x670 /* Branch was registered more than once */);
|
|
@@ -249,10 +206,11 @@ export class EditManager<
|
|
|
249
206
|
}
|
|
250
207
|
|
|
251
208
|
private untrackBranch(b: SharedTreeBranch<TEditor, TChangeset>): void {
|
|
209
|
+
const main = this.getSharedBranch("main");
|
|
252
210
|
const trunkCommit =
|
|
253
|
-
findCommonAncestor(
|
|
254
|
-
fail(0xad3 /* Expected branch to be related to
|
|
255
|
-
const sequenceId =
|
|
211
|
+
findCommonAncestor(main.trunk.getHead(), b.getHead()) ??
|
|
212
|
+
fail(0xad3 /* Expected branch to be related to main */);
|
|
213
|
+
const sequenceId = main.getCommitSequenceId(trunkCommit.revision);
|
|
256
214
|
const branches =
|
|
257
215
|
this.trunkBranches.get(sequenceId) ?? fail(0xad4 /* Expected branch to be tracked */);
|
|
258
216
|
|
|
@@ -263,60 +221,20 @@ export class EditManager<
|
|
|
263
221
|
}
|
|
264
222
|
|
|
265
223
|
/**
|
|
266
|
-
*
|
|
267
|
-
* @param sequenceId - The sequence id of the new trunk commit
|
|
268
|
-
* @remarks This method is a performance optimization for the scenario where this client receives its own change back after sequencing.
|
|
269
|
-
* The normal (not optimized) process in this case would be to apply the new sequenced commit to the trunk and then rebase the local branch over the trunk.
|
|
270
|
-
* The first commit will be "the same" (as in, it will have the same revision) as the commit that was just sequenced, so the rebase will be a no-op.
|
|
271
|
-
* Because the rebase is a no-op, we can skip it entirely and simply remove the commit from the local branch and append it to the trunk.
|
|
272
|
-
* Avoiding the overhead of the rebase process, even when it's a no-op, has real measured performance benefits and is worth the added complexity here.
|
|
224
|
+
* Return the sequenced number of the latest sequenced change.
|
|
273
225
|
*/
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const previousSequenceId = this.getCommitSequenceId(this.trunk.getHead());
|
|
284
|
-
this.pushGraphCommitToTrunk(sequenceId, firstLocalCommit, this.localSessionId);
|
|
285
|
-
|
|
286
|
-
// Next, we need to update the sequence IDs that our local branches (user's branches, not peer branches) are associated with.
|
|
287
|
-
// In particular, if a local branch is based on the previous trunk head (the branch's first ancestor in the trunk is the commit that was the head before we pushed the new commit)
|
|
288
|
-
// and also branches off of the local branch (it has an ancestor that is part of the local branch), it needs to have its sequence number advanced to be that of the new trunk head.
|
|
289
|
-
// Intuitively, this makes sense because:
|
|
290
|
-
// 1. The trunk's head just advanced forward by some (sequence) amount.
|
|
291
|
-
// 2. The local branch is always rebased to be branching off of the head of the trunk (not literally in this case, because of the optimization, but in effect).
|
|
292
|
-
// 3. Therefore, the entire local branch just advanced forward by some (sequence) amount, and any commits downstream of it which track the sequence numbers of their base commits on the trunk should also advance.
|
|
293
|
-
// This update is not necessarily required for all local branches, since some may have fallen behind the local branch and are based on older trunk commits (such branches do not need updating).
|
|
294
|
-
const currentBranches = this.trunkBranches.get(previousSequenceId);
|
|
295
|
-
if (currentBranches !== undefined) {
|
|
296
|
-
const newBranches = getOrCreate(this.trunkBranches, sequenceId, () => new Set());
|
|
297
|
-
for (const branch of currentBranches) {
|
|
298
|
-
// Check every branch associated with the old sequence ID and advance it if it is based on the local branch (specifically, on the local branch as it was before we pushed its first commit to the trunk).
|
|
299
|
-
// We validate this by checking if the branch's head is a descendant of the local commit that we just pushed.
|
|
300
|
-
if (findAncestor(branch.getHead(), (c) => c === firstLocalCommit) !== undefined) {
|
|
301
|
-
newBranches.add(branch);
|
|
302
|
-
currentBranches.delete(branch);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
// Clean up our trunk branches map by removing any empty sets.
|
|
306
|
-
if (currentBranches.size === 0) {
|
|
307
|
-
this.trunkBranches.delete(previousSequenceId);
|
|
308
|
-
}
|
|
309
|
-
if (newBranches.size === 0) {
|
|
310
|
-
this.trunkBranches.delete(sequenceId);
|
|
226
|
+
public getLatestSequenceNumber(): SeqNumber | undefined {
|
|
227
|
+
let maxSequenceNumber: SeqNumber | undefined;
|
|
228
|
+
for (const branch of this.sharedBranches.values()) {
|
|
229
|
+
const branchMax = branch.getCommitSequenceId(
|
|
230
|
+
branch.trunk.getHead().revision,
|
|
231
|
+
).sequenceNumber;
|
|
232
|
+
if (maxSequenceNumber === undefined || maxSequenceNumber < branchMax) {
|
|
233
|
+
maxSequenceNumber = branchMax;
|
|
311
234
|
}
|
|
312
235
|
}
|
|
313
|
-
}
|
|
314
236
|
|
|
315
|
-
|
|
316
|
-
* Return the sequence number at which the given commit was sequenced on the trunk, or undefined if the commit is not part of the trunk.
|
|
317
|
-
*/
|
|
318
|
-
public getSequenceNumber(trunkCommit: GraphCommit<TChangeset>): SeqNumber | undefined {
|
|
319
|
-
return this.trunkMetadata.get(trunkCommit.revision)?.sequenceId.sequenceNumber;
|
|
237
|
+
return maxSequenceNumber;
|
|
320
238
|
}
|
|
321
239
|
|
|
322
240
|
/**
|
|
@@ -328,7 +246,7 @@ export class EditManager<
|
|
|
328
246
|
*/
|
|
329
247
|
public advanceMinimumSequenceNumber(
|
|
330
248
|
minimumSequenceNumber: SeqNumber,
|
|
331
|
-
|
|
249
|
+
trimHistory = true,
|
|
332
250
|
): void {
|
|
333
251
|
if (minimumSequenceNumber === this.minimumSequenceNumber) {
|
|
334
252
|
return;
|
|
@@ -340,8 +258,8 @@ export class EditManager<
|
|
|
340
258
|
);
|
|
341
259
|
|
|
342
260
|
this.minimumSequenceNumber = minimumSequenceNumber;
|
|
343
|
-
if (
|
|
344
|
-
this.
|
|
261
|
+
if (trimHistory) {
|
|
262
|
+
this.trimHistory();
|
|
345
263
|
}
|
|
346
264
|
}
|
|
347
265
|
|
|
@@ -349,7 +267,7 @@ export class EditManager<
|
|
|
349
267
|
* Examines the latest known minimum sequence number and the trunk bases of any registered branches to determine
|
|
350
268
|
* if any commits on the trunk are unreferenced and unneeded for future computation; those found are evicted from the trunk.
|
|
351
269
|
*/
|
|
352
|
-
private
|
|
270
|
+
private trimHistory(): void {
|
|
353
271
|
/** The sequence id of the most recent commit on the trunk that will be trimmed */
|
|
354
272
|
let trunkTailSequenceId: SequenceId = {
|
|
355
273
|
sequenceNumber: this.minimumSequenceNumber,
|
|
@@ -369,180 +287,98 @@ export class EditManager<
|
|
|
369
287
|
);
|
|
370
288
|
}
|
|
371
289
|
|
|
372
|
-
const
|
|
290
|
+
const mainBranch = this.getSharedBranch("main");
|
|
291
|
+
|
|
292
|
+
const [sequenceId, latestEvicted] = mainBranch.getClosestTrunkCommit(
|
|
373
293
|
maxSequenceId(
|
|
374
294
|
trunkTailSequenceId,
|
|
375
|
-
|
|
295
|
+
mainBranch.sequenceIdToCommit.minKey() ?? minimumPossibleSequenceId,
|
|
376
296
|
),
|
|
377
297
|
);
|
|
378
298
|
|
|
379
299
|
// Don't do any work if the commit found by the search is already the tail of the trunk
|
|
380
|
-
if (latestEvicted
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
for (const [, branch] of this.peerLocalBranches) {
|
|
384
|
-
branch.rebaseOnto(this.trunk, latestEvicted);
|
|
385
|
-
}
|
|
300
|
+
if (latestEvicted === this.trunkBase) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
386
303
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
304
|
+
// This mutation is a performance hack. If commits are truly immutable, then changing the trunk's tail requires
|
|
305
|
+
// regenerating the entire commit graph. Instead, we can simply chop off the tail like this if we're certain
|
|
306
|
+
// that there are no outstanding references to any of the commits being removed (other than the references via
|
|
307
|
+
// the trunk). The peer branches have been rebased to the head of the trunk, the local branch is already rebased
|
|
308
|
+
// to the head of the trunk, and all other branches are tracked by `trunkBranches` and known to be ahead of or at
|
|
309
|
+
// `newTrunkBase`. Therefore, no branches should have unique references to any of the commits being evicted here.
|
|
310
|
+
// We mutate the most recent of the evicted commits to become the new trunk base. That way, any other commits that
|
|
311
|
+
// have parent pointers to the latest evicted commit will stay linked, even though that it is no longer part of the trunk.
|
|
312
|
+
const newTrunkBase = latestEvicted as Mutable<typeof latestEvicted>;
|
|
313
|
+
|
|
314
|
+
// collect the revisions that will be trimmed to send as part of the branch trimmed event
|
|
315
|
+
const trimmedCommits = getPathFromBase(newTrunkBase, this.trunkBase);
|
|
316
|
+
const trimmedRevisions = trimmedCommits.map((c) => c.revision);
|
|
317
|
+
|
|
318
|
+
// The minimum sequence number informs us that all peer branches are at least caught up to the tail commit,
|
|
319
|
+
// so rebase them accordingly. This is necessary to prevent peer branches from referencing any evicted commits.
|
|
320
|
+
mainBranch.trimHistory(latestEvicted, sequenceId);
|
|
321
|
+
|
|
322
|
+
// Only the last trimmed commit, which is the new trunk base, should remain accessible.
|
|
323
|
+
for (const commit of trimmedCommits.slice(0, -1)) {
|
|
324
|
+
Reflect.defineProperty(commit, "change", {
|
|
325
|
+
get: () =>
|
|
326
|
+
assert(false, 0xa5e /* Should not access 'change' property of an evicted commit */),
|
|
327
|
+
});
|
|
328
|
+
Reflect.defineProperty(commit, "revision", {
|
|
329
|
+
get: () =>
|
|
413
330
|
assert(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
false,
|
|
422
|
-
0xa5e /* Should not access 'change' property of an evicted commit */,
|
|
423
|
-
),
|
|
424
|
-
});
|
|
425
|
-
Reflect.defineProperty(commit, "revision", {
|
|
426
|
-
get: () =>
|
|
427
|
-
assert(
|
|
428
|
-
false,
|
|
429
|
-
0xa5f /* Should not access 'revision' property of an evicted commit */,
|
|
430
|
-
),
|
|
431
|
-
});
|
|
432
|
-
Reflect.defineProperty(commit, "parent", {
|
|
433
|
-
get: () =>
|
|
434
|
-
assert(
|
|
435
|
-
false,
|
|
436
|
-
0xa60 /* Should not access 'parent' property of an evicted commit */,
|
|
437
|
-
),
|
|
438
|
-
});
|
|
439
|
-
return { delete: true };
|
|
440
|
-
}
|
|
331
|
+
false,
|
|
332
|
+
0xa5f /* Should not access 'revision' property of an evicted commit */,
|
|
333
|
+
),
|
|
334
|
+
});
|
|
335
|
+
Reflect.defineProperty(commit, "parent", {
|
|
336
|
+
get: () =>
|
|
337
|
+
assert(false, 0xa60 /* Should not access 'parent' property of an evicted commit */),
|
|
441
338
|
});
|
|
339
|
+
}
|
|
442
340
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
0x744 /* The size of the sequenceMap must have one element more than the trunk */,
|
|
447
|
-
);
|
|
448
|
-
assert(
|
|
449
|
-
this.trunkMetadata.size === trunkSize,
|
|
450
|
-
0x745 /* The size of the trunkMetadata must be the same as the trunk */,
|
|
451
|
-
);
|
|
341
|
+
// Dropping the parent field removes (transitively) all references to the evicted commits so they can be garbage collected.
|
|
342
|
+
delete newTrunkBase.parent;
|
|
343
|
+
this.trunkBase = newTrunkBase;
|
|
452
344
|
|
|
453
|
-
|
|
454
|
-
}
|
|
345
|
+
this._events.emit("ancestryTrimmed", trimmedRevisions);
|
|
455
346
|
}
|
|
456
347
|
|
|
457
348
|
public isEmpty(): boolean {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
349
|
+
for (const branch of this.sharedBranches.values()) {
|
|
350
|
+
if (!branch.isEmpty(this.trunkBase)) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return this.minimumSequenceNumber === minimumPossibleSequenceNumber;
|
|
464
356
|
}
|
|
465
357
|
|
|
466
358
|
public getSummaryData(): SummaryData<TChangeset> {
|
|
467
|
-
// The assert below is acceptable at present because summarization only ever occurs on a client with no
|
|
468
|
-
// local/in-flight changes.
|
|
469
|
-
// In the future we may wish to relax this constraint. For that to work, the current implementation of
|
|
470
|
-
// `EditManager` would have to be amended in one of two ways:
|
|
471
|
-
// A) Changes made by the local session should be represented by a branch in `EditManager.branches`.
|
|
472
|
-
// B) The contents of such a branch should be computed on demand based on the trunk.
|
|
473
|
-
// Note that option (A) would be a simple change to `addSequencedChanges` whereas (B) would likely require
|
|
474
|
-
// rebasing trunk changes over the inverse of trunk changes.
|
|
475
|
-
assert(
|
|
476
|
-
this.localBranch.getHead() === this.trunk.getHead(),
|
|
477
|
-
0x428 /* Clients with local changes cannot be used to generate summaries */,
|
|
478
|
-
);
|
|
479
|
-
|
|
480
359
|
// Trimming the trunk before serializing ensures that the trunk data in the summary is as minimal as possible.
|
|
481
|
-
this.
|
|
482
|
-
|
|
483
|
-
let oldestCommitInCollabWindow = this.getClosestTrunkCommit(this.minimumSequenceNumber)[1];
|
|
484
|
-
assert(
|
|
485
|
-
oldestCommitInCollabWindow.parent !== undefined ||
|
|
486
|
-
oldestCommitInCollabWindow === this.trunkBase,
|
|
487
|
-
0x8c7 /* Expected oldest commit in collab window to have a parent or be the trunk base */,
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
// Path construction is exclusive, so we need to use the parent of the oldest commit in the window if it exists
|
|
491
|
-
oldestCommitInCollabWindow =
|
|
492
|
-
oldestCommitInCollabWindow.parent ?? oldestCommitInCollabWindow;
|
|
360
|
+
this.trimHistory();
|
|
493
361
|
|
|
494
|
-
const
|
|
495
|
-
(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
362
|
+
const minSeqNumberToSummarize: SequenceId = {
|
|
363
|
+
sequenceNumber: brand(this.minimumSequenceNumber + 1),
|
|
364
|
+
};
|
|
365
|
+
let minBaseSeqId: SequenceId = minSeqNumberToSummarize;
|
|
366
|
+
const mainBranch = this.getSharedBranch("main");
|
|
367
|
+
const branches = new Map<BranchId, SharedBranchSummaryData<TChangeset>>();
|
|
368
|
+
for (const [branchId, branch] of this.sharedBranches) {
|
|
369
|
+
if (branchId !== "main") {
|
|
370
|
+
const branchSummary = branch.getSummaryData(
|
|
371
|
+
minSeqNumberToSummarize,
|
|
372
|
+
this.trunkBase.revision,
|
|
499
373
|
);
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
};
|
|
509
|
-
if (metadata.sequenceId.indexInBatch !== undefined) {
|
|
510
|
-
commit.indexInBatch = metadata.sequenceId.indexInBatch;
|
|
511
|
-
}
|
|
512
|
-
return commit;
|
|
513
|
-
},
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
const peerLocalBranches = new Map<SessionId, SummarySessionBranch<TChangeset>>(
|
|
517
|
-
mapIterable(this.peerLocalBranches.entries(), ([sessionId, branch]) => {
|
|
518
|
-
const branchPath: GraphCommit<TChangeset>[] = [];
|
|
519
|
-
const ancestor =
|
|
520
|
-
findCommonAncestor([branch.getHead(), branchPath], this.trunk.getHead()) ??
|
|
521
|
-
fail(0xad6 /* Expected branch to be based on trunk */);
|
|
522
|
-
|
|
523
|
-
const base = ancestor === this.trunkBase ? rootRevision : ancestor.revision;
|
|
524
|
-
return [
|
|
525
|
-
sessionId,
|
|
526
|
-
{
|
|
527
|
-
base,
|
|
528
|
-
commits: branchPath.map((c) => {
|
|
529
|
-
assert(
|
|
530
|
-
c !== this.trunkBase,
|
|
531
|
-
0xa62 /* Serialized branch should not include the trunk base */,
|
|
532
|
-
);
|
|
533
|
-
const commit: Commit<TChangeset> = {
|
|
534
|
-
change: c.change,
|
|
535
|
-
revision: c.revision,
|
|
536
|
-
sessionId,
|
|
537
|
-
};
|
|
538
|
-
return commit;
|
|
539
|
-
}),
|
|
540
|
-
},
|
|
541
|
-
];
|
|
542
|
-
}),
|
|
543
|
-
);
|
|
544
|
-
|
|
545
|
-
return { trunk, peerLocalBranches };
|
|
374
|
+
branches.set(branchId, branchSummary);
|
|
375
|
+
assert(branchSummary.base !== undefined, 0xc57 /* Branch summary must have a base */);
|
|
376
|
+
const baseSequenceId = mainBranch.getCommitSequenceId(branchSummary.base);
|
|
377
|
+
minBaseSeqId = minSequenceId(minBaseSeqId, baseSequenceId);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const mainSummary = mainBranch.getSummaryData(minBaseSeqId, this.trunkBase.revision);
|
|
381
|
+
return { main: mainSummary, branches, originator: this.localSessionId };
|
|
546
382
|
}
|
|
547
383
|
|
|
548
384
|
public loadSummaryData(data: SummaryData<TChangeset>): void {
|
|
@@ -554,76 +390,38 @@ export class EditManager<
|
|
|
554
390
|
// when hydrating the peer branches below
|
|
555
391
|
const trunkRevisionCache = new Map<RevisionTag, GraphCommit<TChangeset>>();
|
|
556
392
|
trunkRevisionCache.set(this.trunkBase.revision, this.trunkBase);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
this.
|
|
570
|
-
|
|
571
|
-
sequenceId,
|
|
572
|
-
sessionId: c.sessionId,
|
|
573
|
-
});
|
|
574
|
-
trunkRevisionCache.set(c.revision, commit);
|
|
575
|
-
return commit;
|
|
576
|
-
}, this.trunkBase),
|
|
577
|
-
);
|
|
578
|
-
|
|
579
|
-
this.localBranch.setHead(this.trunk.getHead());
|
|
580
|
-
|
|
581
|
-
for (const [sessionId, branch] of data.peerLocalBranches) {
|
|
582
|
-
const commit =
|
|
583
|
-
trunkRevisionCache.get(branch.base) ??
|
|
584
|
-
fail(0xad7 /* Expected summary branch to be based off of a revision in the trunk */);
|
|
585
|
-
|
|
586
|
-
this.peerLocalBranches.set(
|
|
587
|
-
sessionId,
|
|
588
|
-
new SharedTreeBranch(
|
|
589
|
-
branch.commits.reduce(mintCommit, commit),
|
|
590
|
-
this.changeFamily,
|
|
591
|
-
this.mintRevisionTag,
|
|
592
|
-
),
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
private getCommitSequenceId(trunkCommitOrTrunkBase: GraphCommit<TChangeset>): SequenceId {
|
|
598
|
-
const id = this.trunkMetadata.get(trunkCommitOrTrunkBase.revision)?.sequenceId;
|
|
599
|
-
if (id === undefined) {
|
|
600
|
-
assert(
|
|
601
|
-
trunkCommitOrTrunkBase === this.trunkBase,
|
|
602
|
-
0xa63 /* Commit must be either be on the trunk or be the trunk base */,
|
|
603
|
-
);
|
|
604
|
-
return minimumPossibleSequenceId;
|
|
393
|
+
const mainBranch =
|
|
394
|
+
this.sharedBranches.get("main") ?? fail(0xc58 /* Main branch must exist */);
|
|
395
|
+
mainBranch.loadSummaryData(data.main, trunkRevisionCache);
|
|
396
|
+
if (data.branches !== undefined) {
|
|
397
|
+
for (const [branchId, branchData] of data.branches) {
|
|
398
|
+
const branch = this.createSharedBranch(
|
|
399
|
+
branchId,
|
|
400
|
+
branchData.session,
|
|
401
|
+
mainBranch,
|
|
402
|
+
mainBranch.trunk.fork(),
|
|
403
|
+
);
|
|
404
|
+
branch.loadSummaryData(branchData, trunkRevisionCache);
|
|
405
|
+
this.addSharedBranch(branchId, branch);
|
|
406
|
+
}
|
|
605
407
|
}
|
|
606
|
-
return id;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
public getTrunkChanges(): readonly TChangeset[] {
|
|
610
|
-
return this.getTrunkCommits().map((c) => c.change);
|
|
611
408
|
}
|
|
612
409
|
|
|
613
|
-
public
|
|
614
|
-
return
|
|
410
|
+
public getTrunkHead(branchId: BranchId): GraphCommit<TChangeset> {
|
|
411
|
+
return this.getSharedBranch(branchId).trunk.getHead();
|
|
615
412
|
}
|
|
616
413
|
|
|
617
|
-
public
|
|
618
|
-
return this.
|
|
414
|
+
public getTrunkChanges(branchId: BranchId): TChangeset[] {
|
|
415
|
+
return this.getTrunkCommits(branchId).map((c) => c.change);
|
|
619
416
|
}
|
|
620
417
|
|
|
621
|
-
public
|
|
622
|
-
return this.
|
|
418
|
+
public getTrunkCommits(branchId: BranchId): GraphCommit<TChangeset>[] {
|
|
419
|
+
return getPathFromBase(this.getTrunkHead(branchId), this.trunkBase);
|
|
623
420
|
}
|
|
624
421
|
|
|
625
|
-
public getLocalCommits(): readonly GraphCommit<TChangeset>[] {
|
|
626
|
-
|
|
422
|
+
public getLocalCommits(branchId: BranchId): readonly GraphCommit<TChangeset>[] {
|
|
423
|
+
const branch = this.getSharedBranch(branchId);
|
|
424
|
+
return branch.getLocalCommits();
|
|
627
425
|
}
|
|
628
426
|
|
|
629
427
|
/**
|
|
@@ -636,15 +434,98 @@ export class EditManager<
|
|
|
636
434
|
*/
|
|
637
435
|
public getLongestBranchLength(): number {
|
|
638
436
|
let max = 0;
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
const branchPath = getPathFromBase(branch.getHead(), trunkHead);
|
|
642
|
-
if (branchPath.length > max) {
|
|
643
|
-
max = branchPath.length;
|
|
644
|
-
}
|
|
437
|
+
for (const branch of this.sharedBranches.values()) {
|
|
438
|
+
max = Math.max(max, branch.getLongestBranchLength());
|
|
645
439
|
}
|
|
646
|
-
|
|
647
|
-
|
|
440
|
+
return max;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
public sequenceBranchCreation(
|
|
444
|
+
sessionId: SessionId,
|
|
445
|
+
referenceSequenceNumber: SeqNumber,
|
|
446
|
+
branchId: BranchId,
|
|
447
|
+
): void {
|
|
448
|
+
if (sessionId === this.localSessionId) {
|
|
449
|
+
assert(this.sharedBranches.has(branchId), 0xc59 /* Expected branch to already exist */);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const mainBranch = this.getSharedBranch("main");
|
|
454
|
+
const branchTrunk = mainBranch.rebasePeer(sessionId, referenceSequenceNumber).fork();
|
|
455
|
+
this.createAndAddSharedBranch(branchId, sessionId, mainBranch, branchTrunk);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
public addNewBranch(branchId: BranchId): void {
|
|
459
|
+
const main = this.getSharedBranch("main") ?? fail(0xc5a /* Main branch must exist */);
|
|
460
|
+
this.createAndAddSharedBranch(
|
|
461
|
+
branchId,
|
|
462
|
+
this.localSessionId,
|
|
463
|
+
main,
|
|
464
|
+
this.getLocalBranch("main").fork(),
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
public getSharedBranchIds(): BranchId[] {
|
|
469
|
+
return Array.from(this.sharedBranches.keys());
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
public removeBranch(branchId: BranchId): void {
|
|
473
|
+
assert(branchId !== "main", 0xc5b /* Cannot remove main branch */);
|
|
474
|
+
const hadBranch = this.sharedBranches.delete(branchId);
|
|
475
|
+
assert(hadBranch, 0xc5c /* Expected branch to exist */);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private createAndAddSharedBranch(
|
|
479
|
+
branchId: BranchId,
|
|
480
|
+
sessionId: SessionId | undefined,
|
|
481
|
+
parent: SharedBranch<TEditor, TChangeset> | undefined,
|
|
482
|
+
branch: SharedTreeBranch<TEditor, TChangeset>,
|
|
483
|
+
): SharedBranch<TEditor, TChangeset> {
|
|
484
|
+
const sharedBranch = this.createSharedBranch(branchId, sessionId, parent, branch);
|
|
485
|
+
this.addSharedBranch(branchId, sharedBranch);
|
|
486
|
+
return sharedBranch;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private addSharedBranch(
|
|
490
|
+
branchId: BranchId,
|
|
491
|
+
branch: SharedBranch<TEditor, TChangeset>,
|
|
492
|
+
): void {
|
|
493
|
+
assert(
|
|
494
|
+
!this.sharedBranches.has(branchId),
|
|
495
|
+
0xc5d /* A branch with this ID already exists */,
|
|
496
|
+
);
|
|
497
|
+
this.sharedBranches.set(branchId, branch);
|
|
498
|
+
|
|
499
|
+
// Track all forks of the local branch for purposes of trunk eviction. Unlike the local branch, they have
|
|
500
|
+
// an unknown lifetime and rebase frequency, so we can not make any assumptions about which trunk commits
|
|
501
|
+
// they require and therefore we monitor them explicitly.
|
|
502
|
+
onForkTransitive(branch.localBranch, (fork) => this.registerBranch(fork));
|
|
503
|
+
|
|
504
|
+
if (branchId !== "main") {
|
|
505
|
+
this.registerBranch(branch.localBranch);
|
|
506
|
+
this.onSharedBranchCreated?.(branchId);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private createSharedBranch(
|
|
511
|
+
branchId: BranchId,
|
|
512
|
+
sessionId: SessionId | undefined,
|
|
513
|
+
parent: SharedBranch<TEditor, TChangeset> | undefined,
|
|
514
|
+
branch: SharedTreeBranch<TEditor, TChangeset>,
|
|
515
|
+
): SharedBranch<TEditor, TChangeset> {
|
|
516
|
+
const sharedBranch = new SharedBranch(
|
|
517
|
+
parent,
|
|
518
|
+
branch,
|
|
519
|
+
branchId,
|
|
520
|
+
sessionId,
|
|
521
|
+
minimumPossibleSequenceId,
|
|
522
|
+
this.changeFamily,
|
|
523
|
+
this.mintRevisionTag,
|
|
524
|
+
this._events,
|
|
525
|
+
this.telemetryEventBatcher,
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
return sharedBranch;
|
|
648
529
|
}
|
|
649
530
|
|
|
650
531
|
/* eslint-disable jsdoc/check-indentation */
|
|
@@ -663,43 +544,214 @@ export class EditManager<
|
|
|
663
544
|
sessionId: SessionId,
|
|
664
545
|
sequenceNumber: SeqNumber,
|
|
665
546
|
referenceSequenceNumber: SeqNumber,
|
|
547
|
+
branchId: BranchId,
|
|
666
548
|
): void {
|
|
667
549
|
assert(newCommits.length > 0, 0xad8 /* Expected at least one sequenced change */);
|
|
668
550
|
assert(
|
|
669
551
|
sequenceNumber > this.minimumSequenceNumber,
|
|
670
552
|
0x713 /* Expected change sequence number to exceed the last known minimum sequence number */,
|
|
671
553
|
);
|
|
672
|
-
assert(
|
|
673
|
-
sequenceNumber >= // This is ">=", not ">" because changes in the same batch will have the same sequence number
|
|
674
|
-
(this.sequenceMap.maxKey()?.sequenceNumber ?? minimumPossibleSequenceNumber),
|
|
675
|
-
0xa64 /* Attempted to sequence change with an outdated sequence number */,
|
|
676
|
-
);
|
|
677
554
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
555
|
+
const branch = this.getSharedBranch(branchId);
|
|
556
|
+
|
|
557
|
+
const onSequenceLocalCommit = (
|
|
558
|
+
commit: GraphCommit<TChangeset>,
|
|
559
|
+
sequenceId: SequenceId,
|
|
560
|
+
previousSequenceId: SequenceId,
|
|
561
|
+
): void => {
|
|
562
|
+
// Next, we need to update the sequence IDs that our local branches (user's branches, not peer branches) are associated with.
|
|
563
|
+
// In particular, if a local branch is based on the previous trunk head (the branch's first ancestor in the trunk is the commit that was the head before we pushed the new commit)
|
|
564
|
+
// and also branches off of the local branch (it has an ancestor that is part of the local branch), it needs to have its sequence number advanced to be that of the new trunk head.
|
|
565
|
+
// Intuitively, this makes sense because:
|
|
566
|
+
// 1. The trunk's head just advanced forward by some (sequence) amount.
|
|
567
|
+
// 2. The local branch is always rebased to be branching off of the head of the trunk (not literally in this case, because of the optimization, but in effect).
|
|
568
|
+
// 3. Therefore, the entire local branch just advanced forward by some (sequence) amount, and any commits downstream of it which track the sequence numbers of their base commits on the trunk should also advance.
|
|
569
|
+
// This update is not necessarily required for all local branches, since some may have fallen behind the local branch and are based on older trunk commits (such branches do not need updating).
|
|
570
|
+
const currentBranches = this.trunkBranches.get(previousSequenceId);
|
|
571
|
+
if (currentBranches !== undefined) {
|
|
572
|
+
const newBranches = getOrCreate(this.trunkBranches, sequenceId, () => new Set());
|
|
573
|
+
for (const forkedBranch of currentBranches) {
|
|
574
|
+
// Check every branch associated with the old sequence ID and advance it if it is based on the local branch (specifically, on the local branch as it was before we pushed its first commit to the trunk).
|
|
575
|
+
// We validate this by checking if the branch's head is a descendant of the local commit that we just pushed.
|
|
576
|
+
if (findAncestor(forkedBranch.getHead(), (c) => c === commit) !== undefined) {
|
|
577
|
+
newBranches.add(forkedBranch);
|
|
578
|
+
currentBranches.delete(forkedBranch);
|
|
692
579
|
}
|
|
693
|
-
|
|
580
|
+
}
|
|
581
|
+
// Clean up our trunk branches map by removing any empty sets.
|
|
582
|
+
if (currentBranches.size === 0) {
|
|
583
|
+
this.trunkBranches.delete(previousSequenceId);
|
|
584
|
+
}
|
|
585
|
+
if (newBranches.size === 0) {
|
|
586
|
+
this.trunkBranches.delete(sequenceId);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const areLocalCommits = sessionId === this.localSessionId;
|
|
592
|
+
branch.addSequencedChanges(
|
|
593
|
+
newCommits,
|
|
594
|
+
sessionId,
|
|
595
|
+
sequenceNumber,
|
|
596
|
+
areLocalCommits,
|
|
597
|
+
referenceSequenceNumber,
|
|
598
|
+
onSequenceLocalCommit,
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
public findLocalCommit(
|
|
603
|
+
branchId: BranchId,
|
|
604
|
+
revision: RevisionTag,
|
|
605
|
+
): [commit: GraphCommit<TChangeset>, commitsAfter: GraphCommit<TChangeset>[]] {
|
|
606
|
+
const commits: GraphCommit<TChangeset>[] = [];
|
|
607
|
+
const commit = findAncestor(
|
|
608
|
+
[this.getSharedBranch(branchId).localBranch.getHead(), commits],
|
|
609
|
+
(c) => c.revision === revision,
|
|
610
|
+
);
|
|
611
|
+
assert(commit !== undefined, 0x599 /* Expected local branch to contain revision */);
|
|
612
|
+
return [commit, commits];
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* The in-memory data that summaries contain
|
|
618
|
+
*/
|
|
619
|
+
export interface SummaryData<TChangeset> {
|
|
620
|
+
readonly originator?: SessionId;
|
|
621
|
+
readonly main: SharedBranchSummaryData<TChangeset>;
|
|
622
|
+
readonly branches?: ReadonlyMap<BranchId, SharedBranchSummaryData<TChangeset>>;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export interface SharedBranchSummaryData<TChangeset> {
|
|
626
|
+
readonly id?: BranchId;
|
|
627
|
+
readonly name?: string;
|
|
628
|
+
readonly session?: SessionId;
|
|
629
|
+
readonly author?: string;
|
|
630
|
+
readonly base?: RevisionTag;
|
|
631
|
+
readonly trunk: readonly SequencedCommit<TChangeset>[];
|
|
632
|
+
readonly peerLocalBranches: ReadonlyMap<SessionId, SummarySessionBranch<TChangeset>>;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Gets the path from the base of a branch to its head.
|
|
637
|
+
*/
|
|
638
|
+
function getPathFromBase<TCommit extends { parent?: TCommit }>(
|
|
639
|
+
branchHead: TCommit,
|
|
640
|
+
baseBranchHead: TCommit,
|
|
641
|
+
): TCommit[] {
|
|
642
|
+
const path: TCommit[] = [];
|
|
643
|
+
assert(
|
|
644
|
+
findCommonAncestor([branchHead, path], baseBranchHead) !== undefined,
|
|
645
|
+
0x573 /* Expected branches to be related */,
|
|
646
|
+
);
|
|
647
|
+
return path;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
class SharedBranch<TEditor extends ChangeFamilyEditor, TChangeset> {
|
|
651
|
+
/**
|
|
652
|
+
* This branch holds the changes made by this client which have not yet been confirmed as sequenced changes.
|
|
653
|
+
*/
|
|
654
|
+
public readonly localBranch: SharedTreeBranch<TEditor, TChangeset>;
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Branches are maintained to represent the local change list that the issuing client had
|
|
658
|
+
* at the time of submitting the latest known edit on the branch.
|
|
659
|
+
* This means the head commit of each branch is always in its original (non-rebased) form.
|
|
660
|
+
*/
|
|
661
|
+
private readonly peerLocalBranches: Map<SessionId, SharedTreeBranch<TEditor, TChangeset>> =
|
|
662
|
+
new Map();
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* A map from a sequence id to the commit which has that sequence id.
|
|
666
|
+
* This also includes an entry for the {@link trunkBase} which always has the lowest key in the map.
|
|
667
|
+
*/
|
|
668
|
+
public readonly sequenceIdToCommit = new BTree<SequenceId, GraphCommit<TChangeset>>(
|
|
669
|
+
undefined,
|
|
670
|
+
sequenceIdComparator,
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* The list of commits (from oldest to most recent) that are on the local branch but not on the trunk.
|
|
675
|
+
* When a local commit is sequenced, the first commit in this list shifted onto the tip of the trunk.
|
|
676
|
+
*/
|
|
677
|
+
private readonly localCommits: GraphCommit<TChangeset>[] = [];
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Records extra data associated with sequenced commits.
|
|
681
|
+
* This does not include an entry for the {@link trunkBase}.
|
|
682
|
+
*/
|
|
683
|
+
private readonly commitMetadata = new Map<
|
|
684
|
+
RevisionTag,
|
|
685
|
+
{ sequenceId: SequenceId; sessionId: SessionId }
|
|
686
|
+
>();
|
|
687
|
+
|
|
688
|
+
public constructor(
|
|
689
|
+
public readonly parentBranch: SharedBranch<TEditor, TChangeset> | undefined,
|
|
690
|
+
public readonly trunk: SharedTreeBranch<TEditor, TChangeset>,
|
|
691
|
+
private readonly id: BranchId,
|
|
692
|
+
private readonly sessionId: SessionId | undefined,
|
|
693
|
+
baseCommitSequenceId: SequenceId,
|
|
694
|
+
private readonly changeFamily: ChangeFamily<TEditor, TChangeset>,
|
|
695
|
+
private readonly mintRevisionTag: () => RevisionTag,
|
|
696
|
+
branchTrimmer: Listenable<BranchTrimmingEvents>,
|
|
697
|
+
telemetryEventBatcher: TelemetryEventBatcher<keyof RebaseStatsWithDuration> | undefined,
|
|
698
|
+
) {
|
|
699
|
+
this.localBranch = new SharedTreeBranch(
|
|
700
|
+
this.trunk.getHead(),
|
|
701
|
+
changeFamily,
|
|
702
|
+
mintRevisionTag,
|
|
703
|
+
branchTrimmer,
|
|
704
|
+
telemetryEventBatcher,
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
this.sequenceIdToCommit.set(baseCommitSequenceId, this.trunk.getHead());
|
|
708
|
+
|
|
709
|
+
this.localBranch.events.on("afterChange", (event) => {
|
|
710
|
+
if (event.type === "append") {
|
|
711
|
+
for (const commit of event.newCommits) {
|
|
712
|
+
this.localCommits.push(commit);
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
this.localCommits.length = 0;
|
|
716
|
+
findCommonAncestor(
|
|
717
|
+
[this.localBranch.getHead(), this.localCommits],
|
|
718
|
+
this.trunk.getHead(),
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
public addSequencedChanges(
|
|
725
|
+
newCommits: readonly GraphCommit<TChangeset>[],
|
|
726
|
+
sessionId: SessionId,
|
|
727
|
+
sequenceNumber: SeqNumber,
|
|
728
|
+
areLocalCommits: boolean,
|
|
729
|
+
referenceSequenceNumber: SeqNumber,
|
|
730
|
+
onSequenceLocalCommit: OnSequenceCommit<TChangeset>,
|
|
731
|
+
): void {
|
|
732
|
+
assert(
|
|
733
|
+
sequenceNumber >= // This is ">=", not ">" because changes in the same batch will have the same sequence number
|
|
734
|
+
(this.sequenceIdToCommit.maxKey()?.sequenceNumber ?? minimumPossibleSequenceNumber),
|
|
735
|
+
0xa64 /* Attempted to sequence change with an outdated sequence number */,
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
const batchSize = this.getBatchSize(sequenceNumber);
|
|
739
|
+
// The sequence id for the next commit to be processed in the bunch.
|
|
740
|
+
let nextSequenceId =
|
|
741
|
+
batchSize === 0
|
|
742
|
+
? {
|
|
743
|
+
sequenceNumber,
|
|
744
|
+
}
|
|
745
|
+
: {
|
|
694
746
|
sequenceNumber,
|
|
695
|
-
indexInBatch:
|
|
747
|
+
indexInBatch: batchSize,
|
|
696
748
|
};
|
|
697
749
|
|
|
698
750
|
// Local changes, i.e., changes from this client are applied by fast forwarding the local branch commit onto
|
|
699
751
|
// the trunk.
|
|
700
|
-
if (
|
|
752
|
+
if (areLocalCommits) {
|
|
701
753
|
for (const _ of newCommits) {
|
|
702
|
-
this.
|
|
754
|
+
this.sequenceLocalCommit(nextSequenceId, sessionId, onSequenceLocalCommit);
|
|
703
755
|
nextSequenceId = getNextSequenceId(nextSequenceId);
|
|
704
756
|
}
|
|
705
757
|
return;
|
|
@@ -710,13 +762,7 @@ export class EditManager<
|
|
|
710
762
|
// Get the revision that the remote change is based on and rebase that peer local branch over the part of the
|
|
711
763
|
// trunk up to the base revision. This will be a no-op if the sending client has not advanced since the last
|
|
712
764
|
// time we received an edit from it
|
|
713
|
-
const
|
|
714
|
-
const peerLocalBranch = getOrCreate(
|
|
715
|
-
this.peerLocalBranches,
|
|
716
|
-
sessionId,
|
|
717
|
-
() => new SharedTreeBranch(baseRevisionInTrunk, this.changeFamily, this.mintRevisionTag),
|
|
718
|
-
);
|
|
719
|
-
peerLocalBranch.rebaseOnto(this.trunk, baseRevisionInTrunk);
|
|
765
|
+
const peerLocalBranch = this.rebasePeer(sessionId, referenceSequenceNumber);
|
|
720
766
|
|
|
721
767
|
// Step 2 - Append the changes to the peer branch and rebase the changes to the tip of the trunk.
|
|
722
768
|
if (peerLocalBranch.getHead() === this.trunk.getHead()) {
|
|
@@ -737,8 +783,8 @@ export class EditManager<
|
|
|
737
783
|
// If the merge resulted in any changes to the trunk, update the sequence map and trunk metadata
|
|
738
784
|
// with the rebased commits.
|
|
739
785
|
for (const sourceCommit of result.sourceCommits) {
|
|
740
|
-
this.
|
|
741
|
-
this.
|
|
786
|
+
this.sequenceIdToCommit.set(nextSequenceId, sourceCommit);
|
|
787
|
+
this.commitMetadata.set(sourceCommit.revision, {
|
|
742
788
|
sequenceId: nextSequenceId,
|
|
743
789
|
sessionId,
|
|
744
790
|
});
|
|
@@ -751,32 +797,125 @@ export class EditManager<
|
|
|
751
797
|
this.localBranch.rebaseOnto(this.trunk);
|
|
752
798
|
}
|
|
753
799
|
|
|
754
|
-
public
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
[this.localBranch.getHead(), commits],
|
|
760
|
-
(c) => c.revision === revision,
|
|
800
|
+
public isEmpty(baseCommit: GraphCommit<TChangeset>): boolean {
|
|
801
|
+
return (
|
|
802
|
+
this.trunk.getHead() === baseCommit &&
|
|
803
|
+
this.peerLocalBranches.size === 0 &&
|
|
804
|
+
this.localBranch.getHead() === this.trunk.getHead()
|
|
761
805
|
);
|
|
762
|
-
assert(commit !== undefined, 0x599 /* Expected local branch to contain revision */);
|
|
763
|
-
return [commit, commits];
|
|
764
806
|
}
|
|
765
807
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
808
|
+
public rebasePeer(
|
|
809
|
+
sessionId: SessionId,
|
|
810
|
+
referenceSequenceNumber: SeqNumber,
|
|
811
|
+
): SharedTreeBranch<TEditor, TChangeset> {
|
|
812
|
+
const [, baseRevisionInTrunk] = this.getClosestTrunkCommit(referenceSequenceNumber);
|
|
813
|
+
const peerLocalBranch = getOrCreate(
|
|
814
|
+
this.peerLocalBranches,
|
|
815
|
+
sessionId,
|
|
816
|
+
() => new SharedTreeBranch(baseRevisionInTrunk, this.changeFamily, this.mintRevisionTag),
|
|
817
|
+
);
|
|
818
|
+
peerLocalBranch.rebaseOnto(this.trunk, baseRevisionInTrunk);
|
|
819
|
+
return peerLocalBranch;
|
|
769
820
|
}
|
|
770
821
|
|
|
771
|
-
|
|
822
|
+
public getPeerBranchOrTrunk(sessionId: SessionId): SharedTreeBranch<TEditor, TChangeset> {
|
|
823
|
+
return this.peerLocalBranches.get(sessionId) ?? this.trunk;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Gets the length of the longest branch maintained by this `SharedBranch`.
|
|
828
|
+
* This may be the length of a peer branch or the local branch.
|
|
829
|
+
*
|
|
830
|
+
* @remarks
|
|
831
|
+
* The length is counted from the lowest common ancestor with the trunk such that a fully sequenced branch would
|
|
832
|
+
* have length zero.
|
|
833
|
+
*/
|
|
834
|
+
public getLongestBranchLength(): number {
|
|
835
|
+
let max = 0;
|
|
836
|
+
const trunkHead = this.trunk.getHead();
|
|
837
|
+
for (const branch of this.peerLocalBranches.values()) {
|
|
838
|
+
const branchPath = getPathFromBase(branch.getHead(), trunkHead);
|
|
839
|
+
if (branchPath.length > max) {
|
|
840
|
+
max = branchPath.length;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
const localPath = getPathFromBase(this.localBranch.getHead(), trunkHead);
|
|
844
|
+
return Math.max(max, localPath.length);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
public trimHistory(newBase: GraphCommit<TChangeset>, sequenceId: SequenceId): void {
|
|
848
|
+
this.rebasePeers(newBase);
|
|
849
|
+
|
|
850
|
+
this.sequenceIdToCommit.editRange(
|
|
851
|
+
minimumPossibleSequenceId,
|
|
852
|
+
sequenceId,
|
|
853
|
+
true,
|
|
854
|
+
(s, commit) => {
|
|
855
|
+
// Cleanup look-aside data for each evicted commit
|
|
856
|
+
this.commitMetadata.delete(commit.revision);
|
|
857
|
+
// Delete all evicted commits from `sequenceMap` except for the latest one, which is the new `trunkBase`
|
|
858
|
+
if (equalSequenceIds(s, sequenceId)) {
|
|
859
|
+
assert(
|
|
860
|
+
commit === newBase,
|
|
861
|
+
0x729 /* Expected last evicted commit to be new trunk base */,
|
|
862
|
+
);
|
|
863
|
+
} else {
|
|
864
|
+
return { delete: true };
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
const trunkSize = getPathFromBase(this.trunk.getHead(), newBase).length;
|
|
870
|
+
assert(
|
|
871
|
+
this.sequenceIdToCommit.size === trunkSize + 1,
|
|
872
|
+
0x744 /* The size of the sequenceMap must have one element more than the trunk */,
|
|
873
|
+
);
|
|
874
|
+
assert(
|
|
875
|
+
this.commitMetadata.size === trunkSize,
|
|
876
|
+
0x745 /* The size of the trunkMetadata must be the same as the trunk */,
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
private rebasePeers(commit: GraphCommit<TChangeset>): void {
|
|
881
|
+
for (const [, branch] of this.peerLocalBranches) {
|
|
882
|
+
branch.rebaseOnto(this.trunk, commit);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// The metadata for new trunk base revision needs to be deleted before modifying it.
|
|
886
|
+
this.commitMetadata.delete(commit.revision);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
public getLocalCommits(): readonly GraphCommit<TChangeset>[] {
|
|
890
|
+
return this.localCommits;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Promote the oldest un-sequenced commit on the local branch to the head of the trunk.
|
|
895
|
+
* @param sequenceId - The sequence id of the new trunk commit
|
|
896
|
+
* @remarks This method is a performance optimization for the scenario where this client receives its own change back after sequencing.
|
|
897
|
+
* The normal (not optimized) process in this case would be to apply the new sequenced commit to the trunk and then rebase the local branch over the trunk.
|
|
898
|
+
* The first commit will be "the same" (as in, it will have the same revision) as the commit that was just sequenced, so the rebase will be a no-op.
|
|
899
|
+
* Because the rebase is a no-op, we can skip it entirely and simply remove the commit from the local branch and append it to the trunk.
|
|
900
|
+
* Avoiding the overhead of the rebase process, even when it's a no-op, has real measured performance benefits and is worth the added complexity here.
|
|
901
|
+
*/
|
|
902
|
+
private sequenceLocalCommit(
|
|
772
903
|
sequenceId: SequenceId,
|
|
773
|
-
graphCommit: GraphCommit<TChangeset>,
|
|
774
904
|
sessionId: SessionId,
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
this.
|
|
905
|
+
onSequenceLocalCommit: OnSequenceCommit<TChangeset>,
|
|
906
|
+
): GraphCommit<TChangeset> {
|
|
907
|
+
// First, push the local commit to the trunk.
|
|
908
|
+
// We are mutating our `localCommits` cache here,but there is no need to actually change the `localBranch` itself because it will simply catch up later if/when it next rebases.
|
|
909
|
+
const firstLocalCommit = this.localCommits.shift();
|
|
910
|
+
assert(
|
|
911
|
+
firstLocalCommit !== undefined,
|
|
912
|
+
0x6b5 /* Received a sequenced change from the local session despite having no local changes */,
|
|
913
|
+
);
|
|
914
|
+
|
|
915
|
+
const prevSequenceId = this.getCommitSequenceId(this.trunk.getHead().revision);
|
|
916
|
+
this.pushGraphCommitToTrunk(sequenceId, firstLocalCommit, sessionId);
|
|
917
|
+
onSequenceLocalCommit(firstLocalCommit, sequenceId, prevSequenceId);
|
|
918
|
+
return firstLocalCommit;
|
|
780
919
|
}
|
|
781
920
|
|
|
782
921
|
/**
|
|
@@ -785,9 +924,9 @@ export class EditManager<
|
|
|
785
924
|
* @remarks Fails if there is no eligible commit.
|
|
786
925
|
* @returns the closest commit and its sequence id
|
|
787
926
|
*/
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
927
|
+
public getClosestTrunkCommit(searchBy: SeqNumber): [SequenceId, GraphCommit<TChangeset>];
|
|
928
|
+
public getClosestTrunkCommit(searchBy: SequenceId): [SequenceId, GraphCommit<TChangeset>];
|
|
929
|
+
public getClosestTrunkCommit(
|
|
791
930
|
searchBy: SeqNumber | SequenceId,
|
|
792
931
|
): [SequenceId, GraphCommit<TChangeset>] {
|
|
793
932
|
const sequenceId: SequenceId =
|
|
@@ -801,12 +940,45 @@ export class EditManager<
|
|
|
801
940
|
}
|
|
802
941
|
: searchBy;
|
|
803
942
|
|
|
804
|
-
const commit = this.
|
|
943
|
+
const commit = this.sequenceIdToCommit.getPairOrNextLower(sequenceId);
|
|
805
944
|
assert(commit !== undefined, 0x746 /* sequence id has been evicted */);
|
|
806
945
|
return commit;
|
|
807
946
|
}
|
|
808
947
|
|
|
809
|
-
private
|
|
948
|
+
private pushGraphCommitToTrunk(
|
|
949
|
+
sequenceId: SequenceId,
|
|
950
|
+
graphCommit: GraphCommit<TChangeset>,
|
|
951
|
+
sessionId: SessionId,
|
|
952
|
+
): void {
|
|
953
|
+
this.trunk.setHead(graphCommit);
|
|
954
|
+
this.registerSequencedCommit(sequenceId, sessionId, graphCommit);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
private pushCommitToTrunk(sequenceId: SequenceId, commit: Commit<TChangeset>): void {
|
|
958
|
+
const mintedCommit = mintCommit(this.trunk.getHead(), commit);
|
|
959
|
+
this.pushGraphCommitToTrunk(sequenceId, mintedCommit, commit.sessionId);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
private registerSequencedCommit(
|
|
963
|
+
sequenceId: SequenceId,
|
|
964
|
+
sessionId: SessionId,
|
|
965
|
+
commit: GraphCommit<TChangeset>,
|
|
966
|
+
): void {
|
|
967
|
+
this.sequenceIdToCommit.set(sequenceId, commit);
|
|
968
|
+
this.commitMetadata.set(commit.revision, { sequenceId, sessionId });
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
public getCommitSequenceId(commitRevision: RevisionTag): SequenceId {
|
|
972
|
+
const id = this.commitMetadata.get(commitRevision)?.sequenceId;
|
|
973
|
+
if (id === undefined) {
|
|
974
|
+
return minimumPossibleSequenceId;
|
|
975
|
+
}
|
|
976
|
+
return id;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// TODO: Document that this is to handle receiving separate commits with the same sequence ID,
|
|
980
|
+
// as a batch of changes are not guaranteed to be processed as one bunch.
|
|
981
|
+
private getBatchSize(sequenceNumber: SeqNumber): number {
|
|
810
982
|
const startSequenceId: SequenceId = {
|
|
811
983
|
sequenceNumber,
|
|
812
984
|
};
|
|
@@ -814,29 +986,156 @@ export class EditManager<
|
|
|
814
986
|
sequenceNumber: brand((sequenceNumber as number) + 1),
|
|
815
987
|
};
|
|
816
988
|
|
|
817
|
-
return this.
|
|
989
|
+
return this.sequenceIdToCommit.getRange(startSequenceId, endSequenceId, false).length;
|
|
818
990
|
}
|
|
819
|
-
}
|
|
820
991
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
992
|
+
public getSummaryData(
|
|
993
|
+
minSeqNumberToSummarize: SequenceId,
|
|
994
|
+
trunkBaseRevision: RevisionTag,
|
|
995
|
+
): SharedBranchSummaryData<TChangeset> {
|
|
996
|
+
// The assert below is acceptable at present because summarization only ever occurs on a client with no
|
|
997
|
+
// local/in-flight changes.
|
|
998
|
+
// In the future we may wish to relax this constraint. For that to work, the current implementation of
|
|
999
|
+
// `EditManager` would have to be amended in one of two ways:
|
|
1000
|
+
// A) Changes made by the local session should be represented by a branch in `EditManager.branches`.
|
|
1001
|
+
// B) The contents of such a branch should be computed on demand based on the trunk.
|
|
1002
|
+
// Note that option (A) would be a simple change to `addSequencedChanges` whereas (B) would likely require
|
|
1003
|
+
// rebasing trunk changes over the inverse of trunk changes.
|
|
1004
|
+
assert(
|
|
1005
|
+
this.localBranch.getHead() === this.trunk.getHead(),
|
|
1006
|
+
0xc5e /* Clients with local changes cannot be used to generate summaries */,
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
let parentHead: GraphCommit<TChangeset>;
|
|
1010
|
+
if (this.parentBranch === undefined) {
|
|
1011
|
+
const oldestCommitInCollabWindow =
|
|
1012
|
+
this.getClosestTrunkCommit(minSeqNumberToSummarize)[1];
|
|
1013
|
+
// Path construction is exclusive, so we need to use the parent of the oldest commit in the window if it exists
|
|
1014
|
+
parentHead = oldestCommitInCollabWindow.parent ?? oldestCommitInCollabWindow;
|
|
1015
|
+
} else {
|
|
1016
|
+
parentHead = this.parentBranch.trunk.getHead();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const childBranchTrunkCommits: GraphCommit<TChangeset>[] = [];
|
|
1020
|
+
const forkPointFromMainTrunk = findCommonAncestor(
|
|
1021
|
+
[this.trunk.getHead(), childBranchTrunkCommits],
|
|
1022
|
+
parentHead,
|
|
1023
|
+
);
|
|
1024
|
+
assert(
|
|
1025
|
+
forkPointFromMainTrunk !== undefined,
|
|
1026
|
+
0xc5f /* Expected child branch to be based on main branch */,
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
const trunk = childBranchTrunkCommits.map((c) => {
|
|
1030
|
+
const metadata =
|
|
1031
|
+
this.commitMetadata.get(c.revision) ??
|
|
1032
|
+
fail(0xad5 /* Expected metadata for trunk commit */);
|
|
1033
|
+
const commit: SequencedCommit<TChangeset> = {
|
|
1034
|
+
change: c.change,
|
|
1035
|
+
revision: c.revision,
|
|
1036
|
+
sequenceNumber: metadata.sequenceId.sequenceNumber,
|
|
1037
|
+
sessionId: metadata.sessionId,
|
|
1038
|
+
};
|
|
1039
|
+
if (metadata.sequenceId.indexInBatch !== undefined) {
|
|
1040
|
+
commit.indexInBatch = metadata.sequenceId.indexInBatch;
|
|
1041
|
+
}
|
|
1042
|
+
return commit;
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
const peerLocalBranches = new Map<SessionId, SummarySessionBranch<TChangeset>>(
|
|
1046
|
+
mapIterable(this.peerLocalBranches.entries(), ([sessionId, branch]) => {
|
|
1047
|
+
const branchPath: GraphCommit<TChangeset>[] = [];
|
|
1048
|
+
const ancestor =
|
|
1049
|
+
findCommonAncestor([branch.getHead(), branchPath], this.trunk.getHead()) ??
|
|
1050
|
+
fail(0xad6 /* Expected branch to be based on trunk */);
|
|
1051
|
+
|
|
1052
|
+
const base =
|
|
1053
|
+
ancestor.revision === trunkBaseRevision ? rootRevision : ancestor.revision;
|
|
1054
|
+
return [
|
|
1055
|
+
sessionId,
|
|
1056
|
+
{
|
|
1057
|
+
base,
|
|
1058
|
+
commits: branchPath.map((c) => {
|
|
1059
|
+
const commit: Commit<TChangeset> = {
|
|
1060
|
+
change: c.change,
|
|
1061
|
+
revision: c.revision,
|
|
1062
|
+
sessionId,
|
|
1063
|
+
};
|
|
1064
|
+
return commit;
|
|
1065
|
+
}),
|
|
1066
|
+
},
|
|
1067
|
+
];
|
|
1068
|
+
}),
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1071
|
+
const trunkBase =
|
|
1072
|
+
this.parentBranch === undefined ? undefined : forkPointFromMainTrunk.revision;
|
|
1073
|
+
return { trunk, peerLocalBranches, base: trunkBase, id: this.id, session: this.sessionId };
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
public loadSummaryData(
|
|
1077
|
+
data: SharedBranchSummaryData<TChangeset>,
|
|
1078
|
+
trunkRevisionCache: Map<RevisionTag, GraphCommit<TChangeset>>,
|
|
1079
|
+
): void {
|
|
1080
|
+
assert(
|
|
1081
|
+
(this.parentBranch === undefined) === (data.base === undefined),
|
|
1082
|
+
0xc60 /* Expected branch base to match presence of parent branch */,
|
|
1083
|
+
);
|
|
1084
|
+
const parentTrunkBase =
|
|
1085
|
+
trunkRevisionCache.get(data.base ?? rootRevision) ??
|
|
1086
|
+
fail(0xc61 /* Expected base revision to be in trunk cache */);
|
|
1087
|
+
this.trunk.setHead(
|
|
1088
|
+
data.trunk.reduce((base, c) => {
|
|
1089
|
+
const sequenceId: SequenceId =
|
|
1090
|
+
c.indexInBatch === undefined
|
|
1091
|
+
? {
|
|
1092
|
+
sequenceNumber: c.sequenceNumber,
|
|
1093
|
+
}
|
|
1094
|
+
: {
|
|
1095
|
+
sequenceNumber: c.sequenceNumber,
|
|
1096
|
+
indexInBatch: c.indexInBatch,
|
|
1097
|
+
};
|
|
1098
|
+
const commit = mintCommit(base, c);
|
|
1099
|
+
this.sequenceIdToCommit.set(sequenceId, commit);
|
|
1100
|
+
this.commitMetadata.set(c.revision, {
|
|
1101
|
+
sequenceId,
|
|
1102
|
+
sessionId: c.sessionId,
|
|
1103
|
+
});
|
|
1104
|
+
trunkRevisionCache.set(c.revision, commit);
|
|
1105
|
+
return commit;
|
|
1106
|
+
}, parentTrunkBase),
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
this.localBranch.setHead(this.trunk.getHead());
|
|
1110
|
+
|
|
1111
|
+
for (const [sessionId, branch] of data.peerLocalBranches) {
|
|
1112
|
+
const commit =
|
|
1113
|
+
trunkRevisionCache.get(branch.base) ??
|
|
1114
|
+
fail(0xad7 /* Expected summary branch to be based off of a revision in the trunk */);
|
|
1115
|
+
|
|
1116
|
+
this.peerLocalBranches.set(
|
|
1117
|
+
sessionId,
|
|
1118
|
+
new SharedTreeBranch(
|
|
1119
|
+
branch.commits.reduce(mintCommit, commit),
|
|
1120
|
+
this.changeFamily,
|
|
1121
|
+
this.mintRevisionTag,
|
|
1122
|
+
),
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
827
1126
|
}
|
|
828
1127
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const path: TCommit[] = [];
|
|
837
|
-
assert(
|
|
838
|
-
findCommonAncestor([branchHead, path], baseBranchHead) !== undefined,
|
|
839
|
-
0x573 /* Expected branches to be related */,
|
|
840
|
-
);
|
|
841
|
-
return path;
|
|
1128
|
+
// Returns the sequence id for the next commit to be processed in the bunch. Since all the commits have the
|
|
1129
|
+
// same sequence number, only the index in the batch needs to be incremented.
|
|
1130
|
+
function getNextSequenceId(sequenceId: SequenceId): SequenceId {
|
|
1131
|
+
return {
|
|
1132
|
+
sequenceNumber: sequenceId.sequenceNumber,
|
|
1133
|
+
indexInBatch: (sequenceId.indexInBatch ?? 0) + 1,
|
|
1134
|
+
};
|
|
842
1135
|
}
|
|
1136
|
+
|
|
1137
|
+
type OnSequenceCommit<TChangeset> = (
|
|
1138
|
+
commit: GraphCommit<TChangeset>,
|
|
1139
|
+
sequenceId: SequenceId,
|
|
1140
|
+
prevSequenceId: SequenceId,
|
|
1141
|
+
) => void;
|