gitx.do 0.1.0 → 0.1.2
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/README.md +40 -353
- package/dist/do/logger.d.ts +50 -0
- package/dist/do/logger.d.ts.map +1 -0
- package/dist/do/logger.js +122 -0
- package/dist/do/logger.js.map +1 -0
- package/dist/{durable-object → do}/schema.d.ts +3 -3
- package/dist/do/schema.d.ts.map +1 -0
- package/dist/{durable-object → do}/schema.js +4 -3
- package/dist/do/schema.js.map +1 -0
- package/dist/do/types.d.ts +267 -0
- package/dist/do/types.d.ts.map +1 -0
- package/dist/do/types.js +62 -0
- package/dist/do/types.js.map +1 -0
- package/dist/index.d.ts +15 -415
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -483
- package/dist/index.js.map +1 -1
- package/package.json +13 -21
- package/dist/cli/commands/add.d.ts +0 -174
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -131
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/blame.d.ts +0 -259
- package/dist/cli/commands/blame.d.ts.map +0 -1
- package/dist/cli/commands/blame.js +0 -609
- package/dist/cli/commands/blame.js.map +0 -1
- package/dist/cli/commands/branch.d.ts +0 -249
- package/dist/cli/commands/branch.d.ts.map +0 -1
- package/dist/cli/commands/branch.js +0 -693
- package/dist/cli/commands/branch.js.map +0 -1
- package/dist/cli/commands/commit.d.ts +0 -182
- package/dist/cli/commands/commit.d.ts.map +0 -1
- package/dist/cli/commands/commit.js +0 -437
- package/dist/cli/commands/commit.js.map +0 -1
- package/dist/cli/commands/diff.d.ts +0 -464
- package/dist/cli/commands/diff.d.ts.map +0 -1
- package/dist/cli/commands/diff.js +0 -958
- package/dist/cli/commands/diff.js.map +0 -1
- package/dist/cli/commands/log.d.ts +0 -239
- package/dist/cli/commands/log.d.ts.map +0 -1
- package/dist/cli/commands/log.js +0 -535
- package/dist/cli/commands/log.js.map +0 -1
- package/dist/cli/commands/merge.d.ts +0 -106
- package/dist/cli/commands/merge.d.ts.map +0 -1
- package/dist/cli/commands/merge.js +0 -55
- package/dist/cli/commands/merge.js.map +0 -1
- package/dist/cli/commands/review.d.ts +0 -457
- package/dist/cli/commands/review.d.ts.map +0 -1
- package/dist/cli/commands/review.js +0 -533
- package/dist/cli/commands/review.js.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -269
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/status.js +0 -493
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/web.d.ts +0 -199
- package/dist/cli/commands/web.d.ts.map +0 -1
- package/dist/cli/commands/web.js +0 -696
- package/dist/cli/commands/web.js.map +0 -1
- package/dist/cli/fs-adapter.d.ts +0 -656
- package/dist/cli/fs-adapter.d.ts.map +0 -1
- package/dist/cli/fs-adapter.js +0 -1179
- package/dist/cli/fs-adapter.js.map +0 -1
- package/dist/cli/fsx-cli-adapter.d.ts +0 -359
- package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
- package/dist/cli/fsx-cli-adapter.js +0 -619
- package/dist/cli/fsx-cli-adapter.js.map +0 -1
- package/dist/cli/index.d.ts +0 -387
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -523
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/ui/components/DiffView.d.ts +0 -7
- package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
- package/dist/cli/ui/components/DiffView.js +0 -11
- package/dist/cli/ui/components/DiffView.js.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.js +0 -11
- package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.js +0 -12
- package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.js +0 -10
- package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
- package/dist/cli/ui/components/NavigationList.d.ts +0 -9
- package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
- package/dist/cli/ui/components/NavigationList.js +0 -11
- package/dist/cli/ui/components/NavigationList.js.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.js +0 -11
- package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
- package/dist/cli/ui/components/index.d.ts +0 -7
- package/dist/cli/ui/components/index.d.ts.map +0 -1
- package/dist/cli/ui/components/index.js +0 -9
- package/dist/cli/ui/components/index.js.map +0 -1
- package/dist/cli/ui/terminal-ui.d.ts +0 -52
- package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
- package/dist/cli/ui/terminal-ui.js +0 -121
- package/dist/cli/ui/terminal-ui.js.map +0 -1
- package/dist/do/BashModule.d.ts +0 -871
- package/dist/do/BashModule.d.ts.map +0 -1
- package/dist/do/BashModule.js +0 -1143
- package/dist/do/BashModule.js.map +0 -1
- package/dist/do/FsModule.d.ts +0 -601
- package/dist/do/FsModule.d.ts.map +0 -1
- package/dist/do/FsModule.js +0 -1120
- package/dist/do/FsModule.js.map +0 -1
- package/dist/do/GitModule.d.ts +0 -635
- package/dist/do/GitModule.d.ts.map +0 -1
- package/dist/do/GitModule.js +0 -781
- package/dist/do/GitModule.js.map +0 -1
- package/dist/do/GitRepoDO.d.ts +0 -281
- package/dist/do/GitRepoDO.d.ts.map +0 -1
- package/dist/do/GitRepoDO.js +0 -479
- package/dist/do/GitRepoDO.js.map +0 -1
- package/dist/do/bash-ast.d.ts +0 -246
- package/dist/do/bash-ast.d.ts.map +0 -1
- package/dist/do/bash-ast.js +0 -888
- package/dist/do/bash-ast.js.map +0 -1
- package/dist/do/container-executor.d.ts +0 -491
- package/dist/do/container-executor.d.ts.map +0 -1
- package/dist/do/container-executor.js +0 -730
- package/dist/do/container-executor.js.map +0 -1
- package/dist/do/index.d.ts +0 -53
- package/dist/do/index.d.ts.map +0 -1
- package/dist/do/index.js +0 -91
- package/dist/do/index.js.map +0 -1
- package/dist/do/tiered-storage.d.ts +0 -403
- package/dist/do/tiered-storage.d.ts.map +0 -1
- package/dist/do/tiered-storage.js +0 -689
- package/dist/do/tiered-storage.js.map +0 -1
- package/dist/do/withBash.d.ts +0 -231
- package/dist/do/withBash.d.ts.map +0 -1
- package/dist/do/withBash.js +0 -244
- package/dist/do/withBash.js.map +0 -1
- package/dist/do/withFs.d.ts +0 -237
- package/dist/do/withFs.d.ts.map +0 -1
- package/dist/do/withFs.js +0 -387
- package/dist/do/withFs.js.map +0 -1
- package/dist/do/withGit.d.ts +0 -180
- package/dist/do/withGit.d.ts.map +0 -1
- package/dist/do/withGit.js +0 -271
- package/dist/do/withGit.js.map +0 -1
- package/dist/durable-object/object-store.d.ts +0 -633
- package/dist/durable-object/object-store.d.ts.map +0 -1
- package/dist/durable-object/object-store.js +0 -1161
- package/dist/durable-object/object-store.js.map +0 -1
- package/dist/durable-object/schema.d.ts.map +0 -1
- package/dist/durable-object/schema.js.map +0 -1
- package/dist/durable-object/wal.d.ts +0 -416
- package/dist/durable-object/wal.d.ts.map +0 -1
- package/dist/durable-object/wal.js +0 -445
- package/dist/durable-object/wal.js.map +0 -1
- package/dist/mcp/adapter.d.ts +0 -772
- package/dist/mcp/adapter.d.ts.map +0 -1
- package/dist/mcp/adapter.js +0 -895
- package/dist/mcp/adapter.js.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
- package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.js +0 -30
- package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
- package/dist/mcp/sandbox/template.d.ts +0 -17
- package/dist/mcp/sandbox/template.d.ts.map +0 -1
- package/dist/mcp/sandbox/template.js +0 -71
- package/dist/mcp/sandbox/template.js.map +0 -1
- package/dist/mcp/sandbox.d.ts +0 -764
- package/dist/mcp/sandbox.d.ts.map +0 -1
- package/dist/mcp/sandbox.js +0 -1362
- package/dist/mcp/sandbox.js.map +0 -1
- package/dist/mcp/sdk-adapter.d.ts +0 -835
- package/dist/mcp/sdk-adapter.d.ts.map +0 -1
- package/dist/mcp/sdk-adapter.js +0 -974
- package/dist/mcp/sdk-adapter.js.map +0 -1
- package/dist/mcp/tools/do.d.ts +0 -32
- package/dist/mcp/tools/do.d.ts.map +0 -1
- package/dist/mcp/tools/do.js +0 -115
- package/dist/mcp/tools/do.js.map +0 -1
- package/dist/mcp/tools.d.ts +0 -548
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js +0 -1934
- package/dist/mcp/tools.js.map +0 -1
- package/dist/ops/blame.d.ts +0 -551
- package/dist/ops/blame.d.ts.map +0 -1
- package/dist/ops/blame.js +0 -1037
- package/dist/ops/blame.js.map +0 -1
- package/dist/ops/branch.d.ts +0 -766
- package/dist/ops/branch.d.ts.map +0 -1
- package/dist/ops/branch.js +0 -950
- package/dist/ops/branch.js.map +0 -1
- package/dist/ops/commit-traversal.d.ts +0 -349
- package/dist/ops/commit-traversal.d.ts.map +0 -1
- package/dist/ops/commit-traversal.js +0 -821
- package/dist/ops/commit-traversal.js.map +0 -1
- package/dist/ops/commit.d.ts +0 -555
- package/dist/ops/commit.d.ts.map +0 -1
- package/dist/ops/commit.js +0 -826
- package/dist/ops/commit.js.map +0 -1
- package/dist/ops/merge-base.d.ts +0 -397
- package/dist/ops/merge-base.d.ts.map +0 -1
- package/dist/ops/merge-base.js +0 -691
- package/dist/ops/merge-base.js.map +0 -1
- package/dist/ops/merge.d.ts +0 -855
- package/dist/ops/merge.d.ts.map +0 -1
- package/dist/ops/merge.js +0 -1551
- package/dist/ops/merge.js.map +0 -1
- package/dist/ops/tag.d.ts +0 -247
- package/dist/ops/tag.d.ts.map +0 -1
- package/dist/ops/tag.js +0 -649
- package/dist/ops/tag.js.map +0 -1
- package/dist/ops/tree-builder.d.ts +0 -178
- package/dist/ops/tree-builder.d.ts.map +0 -1
- package/dist/ops/tree-builder.js +0 -271
- package/dist/ops/tree-builder.js.map +0 -1
- package/dist/ops/tree-diff.d.ts +0 -291
- package/dist/ops/tree-diff.d.ts.map +0 -1
- package/dist/ops/tree-diff.js +0 -705
- package/dist/ops/tree-diff.js.map +0 -1
- package/dist/pack/delta.d.ts +0 -248
- package/dist/pack/delta.d.ts.map +0 -1
- package/dist/pack/delta.js +0 -736
- package/dist/pack/delta.js.map +0 -1
- package/dist/pack/format.d.ts +0 -446
- package/dist/pack/format.d.ts.map +0 -1
- package/dist/pack/format.js +0 -572
- package/dist/pack/format.js.map +0 -1
- package/dist/pack/full-generation.d.ts +0 -612
- package/dist/pack/full-generation.d.ts.map +0 -1
- package/dist/pack/full-generation.js +0 -1378
- package/dist/pack/full-generation.js.map +0 -1
- package/dist/pack/generation.d.ts +0 -441
- package/dist/pack/generation.d.ts.map +0 -1
- package/dist/pack/generation.js +0 -707
- package/dist/pack/generation.js.map +0 -1
- package/dist/pack/index.d.ts +0 -502
- package/dist/pack/index.d.ts.map +0 -1
- package/dist/pack/index.js +0 -833
- package/dist/pack/index.js.map +0 -1
- package/dist/refs/branch.d.ts +0 -668
- package/dist/refs/branch.d.ts.map +0 -1
- package/dist/refs/branch.js +0 -897
- package/dist/refs/branch.js.map +0 -1
- package/dist/refs/storage.d.ts +0 -833
- package/dist/refs/storage.d.ts.map +0 -1
- package/dist/refs/storage.js +0 -1023
- package/dist/refs/storage.js.map +0 -1
- package/dist/refs/tag.d.ts +0 -860
- package/dist/refs/tag.d.ts.map +0 -1
- package/dist/refs/tag.js +0 -996
- package/dist/refs/tag.js.map +0 -1
- package/dist/storage/backend.d.ts +0 -425
- package/dist/storage/backend.d.ts.map +0 -1
- package/dist/storage/backend.js +0 -41
- package/dist/storage/backend.js.map +0 -1
- package/dist/storage/fsx-adapter.d.ts +0 -204
- package/dist/storage/fsx-adapter.d.ts.map +0 -1
- package/dist/storage/fsx-adapter.js +0 -470
- package/dist/storage/fsx-adapter.js.map +0 -1
- package/dist/storage/lru-cache.d.ts +0 -691
- package/dist/storage/lru-cache.d.ts.map +0 -1
- package/dist/storage/lru-cache.js +0 -813
- package/dist/storage/lru-cache.js.map +0 -1
- package/dist/storage/object-index.d.ts +0 -585
- package/dist/storage/object-index.d.ts.map +0 -1
- package/dist/storage/object-index.js +0 -532
- package/dist/storage/object-index.js.map +0 -1
- package/dist/storage/r2-pack.d.ts +0 -1257
- package/dist/storage/r2-pack.d.ts.map +0 -1
- package/dist/storage/r2-pack.js +0 -1770
- package/dist/storage/r2-pack.js.map +0 -1
- package/dist/tiered/cdc-pipeline.d.ts +0 -1888
- package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
- package/dist/tiered/cdc-pipeline.js +0 -1880
- package/dist/tiered/cdc-pipeline.js.map +0 -1
- package/dist/tiered/migration.d.ts +0 -1104
- package/dist/tiered/migration.d.ts.map +0 -1
- package/dist/tiered/migration.js +0 -1214
- package/dist/tiered/migration.js.map +0 -1
- package/dist/tiered/parquet-writer.d.ts +0 -1145
- package/dist/tiered/parquet-writer.d.ts.map +0 -1
- package/dist/tiered/parquet-writer.js +0 -1183
- package/dist/tiered/parquet-writer.js.map +0 -1
- package/dist/tiered/read-path.d.ts +0 -835
- package/dist/tiered/read-path.d.ts.map +0 -1
- package/dist/tiered/read-path.js +0 -487
- package/dist/tiered/read-path.js.map +0 -1
- package/dist/types/capability.d.ts +0 -1385
- package/dist/types/capability.d.ts.map +0 -1
- package/dist/types/capability.js +0 -36
- package/dist/types/capability.js.map +0 -1
- package/dist/types/index.d.ts +0 -13
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -18
- package/dist/types/index.js.map +0 -1
- package/dist/types/objects.d.ts +0 -692
- package/dist/types/objects.d.ts.map +0 -1
- package/dist/types/objects.js +0 -837
- package/dist/types/objects.js.map +0 -1
- package/dist/types/storage.d.ts +0 -603
- package/dist/types/storage.d.ts.map +0 -1
- package/dist/types/storage.js +0 -191
- package/dist/types/storage.js.map +0 -1
- package/dist/types/worker-loader.d.ts +0 -60
- package/dist/types/worker-loader.d.ts.map +0 -1
- package/dist/types/worker-loader.js +0 -62
- package/dist/types/worker-loader.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -197
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -268
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/sha1.d.ts +0 -290
- package/dist/utils/sha1.d.ts.map +0 -1
- package/dist/utils/sha1.js +0 -582
- package/dist/utils/sha1.js.map +0 -1
- package/dist/wire/capabilities.d.ts +0 -1044
- package/dist/wire/capabilities.d.ts.map +0 -1
- package/dist/wire/capabilities.js +0 -941
- package/dist/wire/capabilities.js.map +0 -1
- package/dist/wire/path-security.d.ts +0 -157
- package/dist/wire/path-security.d.ts.map +0 -1
- package/dist/wire/path-security.js +0 -307
- package/dist/wire/path-security.js.map +0 -1
- package/dist/wire/pkt-line.d.ts +0 -345
- package/dist/wire/pkt-line.d.ts.map +0 -1
- package/dist/wire/pkt-line.js +0 -381
- package/dist/wire/pkt-line.js.map +0 -1
- package/dist/wire/receive-pack.d.ts +0 -1059
- package/dist/wire/receive-pack.d.ts.map +0 -1
- package/dist/wire/receive-pack.js +0 -1414
- package/dist/wire/receive-pack.js.map +0 -1
- package/dist/wire/smart-http.d.ts +0 -799
- package/dist/wire/smart-http.d.ts.map +0 -1
- package/dist/wire/smart-http.js +0 -945
- package/dist/wire/smart-http.js.map +0 -1
- package/dist/wire/upload-pack.d.ts +0 -727
- package/dist/wire/upload-pack.d.ts.map +0 -1
- package/dist/wire/upload-pack.js +0 -1138
- package/dist/wire/upload-pack.js.map +0 -1
package/dist/pack/delta.js
DELETED
|
@@ -1,736 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Git Packfile Delta Encoding/Decoding
|
|
3
|
-
*
|
|
4
|
-
* This module implements Git's delta compression algorithm, which is used in packfiles
|
|
5
|
-
* to efficiently store objects by encoding only the differences between similar objects.
|
|
6
|
-
*
|
|
7
|
-
* ## Delta Format Overview
|
|
8
|
-
*
|
|
9
|
-
* A delta consists of:
|
|
10
|
-
* 1. **Source size** - Variable-length integer specifying the base object size
|
|
11
|
-
* 2. **Target size** - Variable-length integer specifying the result size
|
|
12
|
-
* 3. **Instructions** - Sequence of copy or insert commands
|
|
13
|
-
*
|
|
14
|
-
* ## Instruction Types
|
|
15
|
-
*
|
|
16
|
-
* ### Copy Instruction (MSB = 1)
|
|
17
|
-
* Copies a range of bytes from the source (base) object.
|
|
18
|
-
*
|
|
19
|
-
* | Bit | Meaning |
|
|
20
|
-
* |--------|-------------------------------------------|
|
|
21
|
-
* | 7 | Always 1 (copy marker) |
|
|
22
|
-
* | 6-4 | Which size bytes follow (bit mask) |
|
|
23
|
-
* | 3-0 | Which offset bytes follow (bit mask) |
|
|
24
|
-
*
|
|
25
|
-
* Following bytes encode offset (up to 4 bytes) and size (up to 3 bytes).
|
|
26
|
-
* If size is 0 after decoding, it means 0x10000 (65536).
|
|
27
|
-
*
|
|
28
|
-
* ### Insert Instruction (MSB = 0)
|
|
29
|
-
* Inserts literal bytes directly into the output.
|
|
30
|
-
*
|
|
31
|
-
* | Bit | Meaning |
|
|
32
|
-
* |--------|-------------------------------------------|
|
|
33
|
-
* | 7 | Always 0 (insert marker) |
|
|
34
|
-
* | 6-0 | Number of bytes to insert (1-127) |
|
|
35
|
-
*
|
|
36
|
-
* The instruction byte is followed by that many literal bytes.
|
|
37
|
-
*
|
|
38
|
-
* ## Performance Optimizations
|
|
39
|
-
*
|
|
40
|
-
* This implementation includes several performance optimizations:
|
|
41
|
-
* - **Rabin fingerprint rolling hash**: O(1) hash updates when sliding the window
|
|
42
|
-
* - **Typed arrays for index**: Uses Uint32Array for memory efficiency
|
|
43
|
-
* - **Chunked processing**: Memory-efficient processing for large files
|
|
44
|
-
* - **Optimized match extension**: SIMD-friendly byte comparison
|
|
45
|
-
*
|
|
46
|
-
* @module pack/delta
|
|
47
|
-
* @see {@link https://git-scm.com/docs/pack-format} Git Pack Format Documentation
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* // Apply a delta to reconstruct an object
|
|
51
|
-
* import { applyDelta } from './delta';
|
|
52
|
-
*
|
|
53
|
-
* const baseObject = getBaseObject(); // Uint8Array
|
|
54
|
-
* const deltaData = getDeltaData(); // Uint8Array from packfile
|
|
55
|
-
* const targetObject = applyDelta(baseObject, deltaData);
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* // Create a delta between two objects
|
|
59
|
-
* import { createDelta } from './delta';
|
|
60
|
-
*
|
|
61
|
-
* const oldVersion = new TextEncoder().encode('Hello, World!');
|
|
62
|
-
* const newVersion = new TextEncoder().encode('Hello, Universe!');
|
|
63
|
-
* const delta = createDelta(oldVersion, newVersion);
|
|
64
|
-
*/
|
|
65
|
-
/**
|
|
66
|
-
* Marker byte for copy instructions (MSB set).
|
|
67
|
-
* When the MSB is set, the instruction copies bytes from the base object.
|
|
68
|
-
*
|
|
69
|
-
* @constant {number}
|
|
70
|
-
*/
|
|
71
|
-
export const COPY_INSTRUCTION = 0x80;
|
|
72
|
-
/**
|
|
73
|
-
* Marker byte for insert instructions (MSB clear).
|
|
74
|
-
* When the MSB is clear, the lower 7 bits indicate how many literal bytes follow.
|
|
75
|
-
*
|
|
76
|
-
* @constant {number}
|
|
77
|
-
*/
|
|
78
|
-
export const INSERT_INSTRUCTION = 0x00;
|
|
79
|
-
// =============================================================================
|
|
80
|
-
// Rabin Fingerprint Rolling Hash Constants and Implementation
|
|
81
|
-
// =============================================================================
|
|
82
|
-
/**
|
|
83
|
-
* Window size for rolling hash matching.
|
|
84
|
-
* 4 bytes is a good balance between collision rate and match granularity.
|
|
85
|
-
*
|
|
86
|
-
* @constant {number}
|
|
87
|
-
*/
|
|
88
|
-
const WINDOW_SIZE = 4;
|
|
89
|
-
/**
|
|
90
|
-
* Rabin fingerprint polynomial base.
|
|
91
|
-
* Using a prime number provides good hash distribution.
|
|
92
|
-
*
|
|
93
|
-
* @constant {number}
|
|
94
|
-
*/
|
|
95
|
-
const RABIN_BASE = 257;
|
|
96
|
-
/**
|
|
97
|
-
* Modulus for Rabin fingerprint to keep values in 32-bit range.
|
|
98
|
-
* Using a prime close to 2^31 for good distribution.
|
|
99
|
-
*
|
|
100
|
-
* @constant {number}
|
|
101
|
-
*/
|
|
102
|
-
const RABIN_MOD = 0x7fffffff; // 2^31 - 1, a Mersenne prime
|
|
103
|
-
/**
|
|
104
|
-
* Pre-computed power of RABIN_BASE^WINDOW_SIZE mod RABIN_MOD.
|
|
105
|
-
* Used for efficiently removing the contribution of the oldest byte.
|
|
106
|
-
*
|
|
107
|
-
* @constant {number}
|
|
108
|
-
*/
|
|
109
|
-
const RABIN_POW = (() => {
|
|
110
|
-
let pow = 1;
|
|
111
|
-
for (let i = 0; i < WINDOW_SIZE; i++) {
|
|
112
|
-
pow = (pow * RABIN_BASE) % RABIN_MOD;
|
|
113
|
-
}
|
|
114
|
-
return pow;
|
|
115
|
-
})();
|
|
116
|
-
/**
|
|
117
|
-
* Minimum match length to be worth a copy instruction.
|
|
118
|
-
* Smaller matches cost more than they save due to instruction overhead.
|
|
119
|
-
*
|
|
120
|
-
* @constant {number}
|
|
121
|
-
*/
|
|
122
|
-
const MIN_COPY_SIZE = 4;
|
|
123
|
-
/**
|
|
124
|
-
* Chunk size for processing large files.
|
|
125
|
-
* 64KB chunks balance memory usage and cache efficiency.
|
|
126
|
-
*
|
|
127
|
-
* @constant {number}
|
|
128
|
-
*/
|
|
129
|
-
const CHUNK_SIZE = 64 * 1024;
|
|
130
|
-
/**
|
|
131
|
-
* Maximum entries per hash bucket to prevent pathological cases.
|
|
132
|
-
* Limits memory usage when many positions hash to the same value.
|
|
133
|
-
*
|
|
134
|
-
* @constant {number}
|
|
135
|
-
*/
|
|
136
|
-
const MAX_BUCKET_SIZE = 64;
|
|
137
|
-
/**
|
|
138
|
-
* Computes Rabin fingerprint hash for initial window.
|
|
139
|
-
*
|
|
140
|
-
* @description Computes the hash value for the first WINDOW_SIZE bytes.
|
|
141
|
-
* Subsequent positions use rolling updates for O(1) computation.
|
|
142
|
-
*
|
|
143
|
-
* @param {Uint8Array} data - The data buffer
|
|
144
|
-
* @param {number} offset - Starting offset
|
|
145
|
-
* @returns {number} 32-bit hash value
|
|
146
|
-
* @internal
|
|
147
|
-
*/
|
|
148
|
-
function rabinHash(data, offset) {
|
|
149
|
-
let hash = 0;
|
|
150
|
-
for (let i = 0; i < WINDOW_SIZE; i++) {
|
|
151
|
-
hash = (hash * RABIN_BASE + data[offset + i]) % RABIN_MOD;
|
|
152
|
-
}
|
|
153
|
-
return hash;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Updates Rabin fingerprint hash by rolling the window one byte forward.
|
|
157
|
-
*
|
|
158
|
-
* @description This is the key optimization: O(1) hash update instead of O(WINDOW_SIZE).
|
|
159
|
-
* Removes contribution of outgoing byte and adds incoming byte.
|
|
160
|
-
*
|
|
161
|
-
* Formula: hash' = (hash * BASE - outgoing * BASE^WINDOW + incoming) mod MOD
|
|
162
|
-
*
|
|
163
|
-
* @param {number} hash - Current hash value
|
|
164
|
-
* @param {number} outgoing - Byte leaving the window
|
|
165
|
-
* @param {number} incoming - Byte entering the window
|
|
166
|
-
* @returns {number} Updated hash value
|
|
167
|
-
* @internal
|
|
168
|
-
*/
|
|
169
|
-
function rabinRoll(hash, outgoing, incoming) {
|
|
170
|
-
// Remove outgoing byte's contribution and add incoming byte
|
|
171
|
-
// hash = hash * BASE - outgoing * BASE^WINDOW + incoming
|
|
172
|
-
let newHash = ((hash * RABIN_BASE) % RABIN_MOD - (outgoing * RABIN_POW) % RABIN_MOD + incoming) % RABIN_MOD;
|
|
173
|
-
// Ensure positive result
|
|
174
|
-
if (newHash < 0)
|
|
175
|
-
newHash += RABIN_MOD;
|
|
176
|
-
return newHash;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Builds an optimized hash index from the base object using Rabin fingerprints.
|
|
180
|
-
*
|
|
181
|
-
* @description Creates a memory-efficient index for finding matching byte sequences.
|
|
182
|
-
* Uses typed arrays to minimize GC pressure and memory fragmentation.
|
|
183
|
-
*
|
|
184
|
-
* **Memory optimization:**
|
|
185
|
-
* - Uses power-of-2 bucket count for fast modulo (bitwise AND)
|
|
186
|
-
* - Limits bucket size to prevent pathological cases
|
|
187
|
-
* - Uses Uint32Array/Uint16Array instead of object arrays
|
|
188
|
-
*
|
|
189
|
-
* @param {Uint8Array} base - The base object to index
|
|
190
|
-
* @returns {HashIndex} The built index
|
|
191
|
-
* @internal
|
|
192
|
-
*/
|
|
193
|
-
function buildHashIndex(base) {
|
|
194
|
-
// Determine optimal bucket count (power of 2, at least 256)
|
|
195
|
-
// Aim for average load factor of ~4 entries per bucket
|
|
196
|
-
const targetBuckets = Math.max(256, Math.ceil((base.length - WINDOW_SIZE + 1) / 4));
|
|
197
|
-
const bucketCount = 1 << Math.ceil(Math.log2(targetBuckets));
|
|
198
|
-
const bucketMask = bucketCount - 1;
|
|
199
|
-
// Allocate typed arrays
|
|
200
|
-
const offsets = new Uint32Array(bucketCount * MAX_BUCKET_SIZE);
|
|
201
|
-
const counts = new Uint16Array(bucketCount);
|
|
202
|
-
if (base.length < WINDOW_SIZE) {
|
|
203
|
-
return { bucketCount, bucketMask, offsets, counts };
|
|
204
|
-
}
|
|
205
|
-
// Use rolling hash to build index
|
|
206
|
-
let hash = rabinHash(base, 0);
|
|
207
|
-
addToIndex(0);
|
|
208
|
-
for (let i = 1; i <= base.length - WINDOW_SIZE; i++) {
|
|
209
|
-
// Roll the hash forward (O(1) operation)
|
|
210
|
-
hash = rabinRoll(hash, base[i - 1], base[i + WINDOW_SIZE - 1]);
|
|
211
|
-
addToIndex(i);
|
|
212
|
-
}
|
|
213
|
-
return { bucketCount, bucketMask, offsets, counts };
|
|
214
|
-
function addToIndex(offset) {
|
|
215
|
-
const bucket = hash & bucketMask;
|
|
216
|
-
const count = counts[bucket];
|
|
217
|
-
if (count < MAX_BUCKET_SIZE) {
|
|
218
|
-
offsets[bucket * MAX_BUCKET_SIZE + count] = offset;
|
|
219
|
-
counts[bucket] = count + 1;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Looks up potential match positions from the hash index.
|
|
225
|
-
*
|
|
226
|
-
* @param {HashIndex} index - The hash index
|
|
227
|
-
* @param {number} hash - The hash to look up
|
|
228
|
-
* @returns {Generator<number>} Yields matching offsets
|
|
229
|
-
* @internal
|
|
230
|
-
*/
|
|
231
|
-
function* lookupIndex(index, hash) {
|
|
232
|
-
const bucket = hash & index.bucketMask;
|
|
233
|
-
const count = index.counts[bucket];
|
|
234
|
-
const baseOffset = bucket * MAX_BUCKET_SIZE;
|
|
235
|
-
for (let i = 0; i < count; i++) {
|
|
236
|
-
yield index.offsets[baseOffset + i];
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Optimized match length calculation using word-at-a-time comparison.
|
|
241
|
-
*
|
|
242
|
-
* @description Compares bytes in chunks of 4 (as 32-bit integers) where possible,
|
|
243
|
-
* falling back to byte-by-byte for the remaining bytes. This is ~4x faster than
|
|
244
|
-
* pure byte comparison for long matches.
|
|
245
|
-
*
|
|
246
|
-
* @param {Uint8Array} a - First array
|
|
247
|
-
* @param {number} aOffset - Starting offset in first array
|
|
248
|
-
* @param {Uint8Array} b - Second array
|
|
249
|
-
* @param {number} bOffset - Starting offset in second array
|
|
250
|
-
* @param {number} maxLength - Maximum number of bytes to compare
|
|
251
|
-
* @returns {number} Number of matching bytes (0 to maxLength)
|
|
252
|
-
* @internal
|
|
253
|
-
*/
|
|
254
|
-
function getMatchLengthOptimized(a, aOffset, b, bOffset, maxLength) {
|
|
255
|
-
if (maxLength <= 0)
|
|
256
|
-
return 0;
|
|
257
|
-
let length = 0;
|
|
258
|
-
// Compare 4 bytes at a time using DataView for unaligned access
|
|
259
|
-
// This is faster than byte-by-byte for larger matches
|
|
260
|
-
const wordCount = (maxLength - length) >>> 2;
|
|
261
|
-
if (wordCount > 0) {
|
|
262
|
-
const aView = new DataView(a.buffer, a.byteOffset + aOffset, maxLength);
|
|
263
|
-
const bView = new DataView(b.buffer, b.byteOffset + bOffset, maxLength);
|
|
264
|
-
for (let i = 0; i < wordCount; i++) {
|
|
265
|
-
const wordOffset = length;
|
|
266
|
-
if (aView.getUint32(wordOffset, true) !== bView.getUint32(wordOffset, true)) {
|
|
267
|
-
// Found difference in this word, find exact byte
|
|
268
|
-
while (length < maxLength && a[aOffset + length] === b[bOffset + length]) {
|
|
269
|
-
length++;
|
|
270
|
-
}
|
|
271
|
-
return length;
|
|
272
|
-
}
|
|
273
|
-
length += 4;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
// Compare remaining bytes
|
|
277
|
-
while (length < maxLength && a[aOffset + length] === b[bOffset + length]) {
|
|
278
|
-
length++;
|
|
279
|
-
}
|
|
280
|
-
return length;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Parses a variable-length size value from the delta header.
|
|
284
|
-
*
|
|
285
|
-
* @description Reads the source or target size from a delta's header using
|
|
286
|
-
* Git's variable-length integer encoding. Each byte's MSB indicates whether
|
|
287
|
-
* more bytes follow, and the lower 7 bits contribute to the value.
|
|
288
|
-
*
|
|
289
|
-
* **Encoding Details:**
|
|
290
|
-
* - Bytes are read sequentially
|
|
291
|
-
* - Lower 7 bits of each byte contribute to the result
|
|
292
|
-
* - MSB = 1 means more bytes follow
|
|
293
|
-
* - MSB = 0 means this is the last byte
|
|
294
|
-
* - Maximum of 10 bytes (supports values up to 2^70)
|
|
295
|
-
*
|
|
296
|
-
* @param {Uint8Array} data - The delta data buffer
|
|
297
|
-
* @param {number} offset - Starting byte offset in the buffer
|
|
298
|
-
* @returns {DeltaHeaderResult} Object with parsed size and bytes consumed
|
|
299
|
-
* @throws {Error} If data ends unexpectedly before size is complete
|
|
300
|
-
* @throws {Error} If size encoding exceeds maximum length (corrupted data)
|
|
301
|
-
*
|
|
302
|
-
* @example
|
|
303
|
-
* // Parse source and target sizes from delta
|
|
304
|
-
* let offset = 0;
|
|
305
|
-
* const source = parseDeltaHeader(delta, offset);
|
|
306
|
-
* offset += source.bytesRead;
|
|
307
|
-
* const target = parseDeltaHeader(delta, offset);
|
|
308
|
-
* offset += target.bytesRead;
|
|
309
|
-
*
|
|
310
|
-
* console.log(`Base size: ${source.size}, Target size: ${target.size}`);
|
|
311
|
-
*/
|
|
312
|
-
export function parseDeltaHeader(data, offset) {
|
|
313
|
-
let size = 0;
|
|
314
|
-
let shift = 0;
|
|
315
|
-
let bytesRead = 0;
|
|
316
|
-
// Maximum bytes for a varint to prevent infinite loops
|
|
317
|
-
const MAX_VARINT_BYTES = 10;
|
|
318
|
-
while (true) {
|
|
319
|
-
if (offset + bytesRead >= data.length) {
|
|
320
|
-
throw new Error(`Delta header parsing failed: unexpected end of data at offset ${offset + bytesRead}`);
|
|
321
|
-
}
|
|
322
|
-
if (bytesRead >= MAX_VARINT_BYTES) {
|
|
323
|
-
throw new Error(`Delta header parsing failed: exceeded maximum length of ${MAX_VARINT_BYTES} bytes (possible infinite loop or corrupted data)`);
|
|
324
|
-
}
|
|
325
|
-
const byte = data[offset + bytesRead];
|
|
326
|
-
bytesRead++;
|
|
327
|
-
// Add the lower 7 bits to the result
|
|
328
|
-
size |= (byte & 0x7f) << shift;
|
|
329
|
-
shift += 7;
|
|
330
|
-
// If MSB is not set, we're done
|
|
331
|
-
if ((byte & 0x80) === 0) {
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return { size, bytesRead };
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Encodes a size as a variable-length integer for delta headers.
|
|
339
|
-
*
|
|
340
|
-
* @description Internal function that encodes sizes using Git's varint format.
|
|
341
|
-
* Used when creating delta headers that specify source and target sizes.
|
|
342
|
-
*
|
|
343
|
-
* @param {number} size - The size value to encode
|
|
344
|
-
* @returns {Uint8Array} The encoded bytes
|
|
345
|
-
* @internal
|
|
346
|
-
*/
|
|
347
|
-
function encodeDeltaSize(size) {
|
|
348
|
-
const bytes = [];
|
|
349
|
-
do {
|
|
350
|
-
let byte = size & 0x7f;
|
|
351
|
-
size >>>= 7;
|
|
352
|
-
if (size > 0) {
|
|
353
|
-
byte |= 0x80; // Set continuation bit
|
|
354
|
-
}
|
|
355
|
-
bytes.push(byte);
|
|
356
|
-
} while (size > 0);
|
|
357
|
-
return new Uint8Array(bytes);
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Applies a delta to a base object to produce the target object.
|
|
361
|
-
*
|
|
362
|
-
* @description Reconstructs the target object by executing the delta's copy and
|
|
363
|
-
* insert instructions against the base object. This is the core operation for
|
|
364
|
-
* unpacking delta-compressed objects in packfiles.
|
|
365
|
-
*
|
|
366
|
-
* **Delta Application Process:**
|
|
367
|
-
* 1. Parse source (base) size and verify it matches
|
|
368
|
-
* 2. Parse target size to allocate result buffer
|
|
369
|
-
* 3. Execute instructions sequentially:
|
|
370
|
-
* - Copy: copy bytes from base object to result
|
|
371
|
-
* - Insert: copy literal bytes from delta to result
|
|
372
|
-
* 4. Verify result size matches expected target size
|
|
373
|
-
*
|
|
374
|
-
* **Error Conditions:**
|
|
375
|
-
* - Base object size doesn't match delta's source size
|
|
376
|
-
* - Copy instruction references bytes outside base object
|
|
377
|
-
* - Instructions would overflow the result buffer
|
|
378
|
-
* - Result size doesn't match delta's target size
|
|
379
|
-
* - Invalid instruction byte (0x00)
|
|
380
|
-
*
|
|
381
|
-
* @param {Uint8Array} base - The source/base object to apply delta against
|
|
382
|
-
* @param {Uint8Array} delta - The delta data (decompressed from packfile)
|
|
383
|
-
* @returns {Uint8Array} The reconstructed target object
|
|
384
|
-
* @throws {Error} If base size doesn't match delta's source size
|
|
385
|
-
* @throws {Error} If delta contains invalid instructions
|
|
386
|
-
* @throws {Error} If copy would read beyond base object bounds
|
|
387
|
-
* @throws {Error} If result size doesn't match expected target size
|
|
388
|
-
*
|
|
389
|
-
* @example
|
|
390
|
-
* // Reconstruct an object from base + delta
|
|
391
|
-
* const base = await getObject(baseSha);
|
|
392
|
-
* const delta = decompressDeltaData(packData, offset);
|
|
393
|
-
* const target = applyDelta(base, delta);
|
|
394
|
-
*
|
|
395
|
-
* @example
|
|
396
|
-
* // Error handling
|
|
397
|
-
* try {
|
|
398
|
-
* const target = applyDelta(base, delta);
|
|
399
|
-
* } catch (e) {
|
|
400
|
-
* console.error('Delta application failed:', e.message);
|
|
401
|
-
* }
|
|
402
|
-
*/
|
|
403
|
-
export function applyDelta(base, delta) {
|
|
404
|
-
let offset = 0;
|
|
405
|
-
// Parse source size
|
|
406
|
-
const sourceHeader = parseDeltaHeader(delta, offset);
|
|
407
|
-
offset += sourceHeader.bytesRead;
|
|
408
|
-
if (sourceHeader.size !== base.length) {
|
|
409
|
-
throw new Error(`Delta source size mismatch: expected ${sourceHeader.size}, got ${base.length}`);
|
|
410
|
-
}
|
|
411
|
-
// Parse target size
|
|
412
|
-
const targetHeader = parseDeltaHeader(delta, offset);
|
|
413
|
-
offset += targetHeader.bytesRead;
|
|
414
|
-
// Allocate result buffer
|
|
415
|
-
const result = new Uint8Array(targetHeader.size);
|
|
416
|
-
let resultOffset = 0;
|
|
417
|
-
// Process instructions
|
|
418
|
-
while (offset < delta.length) {
|
|
419
|
-
const cmd = delta[offset++];
|
|
420
|
-
if (cmd & COPY_INSTRUCTION) {
|
|
421
|
-
// Copy instruction
|
|
422
|
-
let copyOffset = 0;
|
|
423
|
-
let copySize = 0;
|
|
424
|
-
// Read offset bytes (bits 0-3 indicate which bytes are present)
|
|
425
|
-
if (cmd & 0x01)
|
|
426
|
-
copyOffset |= delta[offset++];
|
|
427
|
-
if (cmd & 0x02)
|
|
428
|
-
copyOffset |= delta[offset++] << 8;
|
|
429
|
-
if (cmd & 0x04)
|
|
430
|
-
copyOffset |= delta[offset++] << 16;
|
|
431
|
-
if (cmd & 0x08)
|
|
432
|
-
copyOffset |= delta[offset++] << 24;
|
|
433
|
-
// Read size bytes (bits 4-6 indicate which bytes are present)
|
|
434
|
-
if (cmd & 0x10)
|
|
435
|
-
copySize |= delta[offset++];
|
|
436
|
-
if (cmd & 0x20)
|
|
437
|
-
copySize |= delta[offset++] << 8;
|
|
438
|
-
if (cmd & 0x40)
|
|
439
|
-
copySize |= delta[offset++] << 16;
|
|
440
|
-
// Size of 0 means 0x10000 (65536)
|
|
441
|
-
if (copySize === 0) {
|
|
442
|
-
copySize = 0x10000;
|
|
443
|
-
}
|
|
444
|
-
// Bounds checking to prevent buffer overflows
|
|
445
|
-
if (copyOffset < 0 || copySize < 0) {
|
|
446
|
-
throw new Error(`Invalid copy instruction: offset=${copyOffset}, size=${copySize}`);
|
|
447
|
-
}
|
|
448
|
-
if (copyOffset + copySize > base.length) {
|
|
449
|
-
throw new Error(`Copy instruction out of bounds: offset=${copyOffset}, size=${copySize}, base length=${base.length}`);
|
|
450
|
-
}
|
|
451
|
-
if (resultOffset + copySize > result.length) {
|
|
452
|
-
throw new Error(`Copy would overflow result buffer: resultOffset=${resultOffset}, size=${copySize}, result length=${result.length}`);
|
|
453
|
-
}
|
|
454
|
-
// Copy from base to result
|
|
455
|
-
result.set(base.subarray(copyOffset, copyOffset + copySize), resultOffset);
|
|
456
|
-
resultOffset += copySize;
|
|
457
|
-
}
|
|
458
|
-
else if (cmd !== 0) {
|
|
459
|
-
// Insert instruction: cmd is the number of bytes to insert
|
|
460
|
-
const insertSize = cmd;
|
|
461
|
-
result.set(delta.subarray(offset, offset + insertSize), resultOffset);
|
|
462
|
-
offset += insertSize;
|
|
463
|
-
resultOffset += insertSize;
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
// cmd === 0 is reserved/invalid
|
|
467
|
-
throw new Error('Invalid delta instruction: 0x00');
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
// Verify we produced the expected size
|
|
471
|
-
if (resultOffset !== targetHeader.size) {
|
|
472
|
-
throw new Error(`Delta result size mismatch: expected ${targetHeader.size}, got ${resultOffset}`);
|
|
473
|
-
}
|
|
474
|
-
return result;
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Creates a delta that transforms a base object into a target object.
|
|
478
|
-
*
|
|
479
|
-
* @description Generates delta instructions that can reconstruct the target
|
|
480
|
-
* from the base object. Uses a hash-based algorithm to find matching sequences
|
|
481
|
-
* and emits copy/insert instructions accordingly.
|
|
482
|
-
*
|
|
483
|
-
* **Algorithm:**
|
|
484
|
-
* 1. Build a hash table of 4-byte sequences in the base object
|
|
485
|
-
* 2. Scan through the target looking for matches in the hash table
|
|
486
|
-
* 3. For each match found, verify and extend to maximum length
|
|
487
|
-
* 4. Emit copy instructions for matches (4+ bytes)
|
|
488
|
-
* 5. Emit insert instructions for non-matching data
|
|
489
|
-
*
|
|
490
|
-
* **Optimization Notes:**
|
|
491
|
-
* - Uses 4-byte window for hash matching
|
|
492
|
-
* - Minimum copy size is 4 bytes (smaller copies become inserts)
|
|
493
|
-
* - Insert instructions are limited to 127 bytes each
|
|
494
|
-
* - Empty base results in pure insert delta
|
|
495
|
-
* - Empty target results in headers-only delta
|
|
496
|
-
*
|
|
497
|
-
* **Output Format:**
|
|
498
|
-
* - Source size (varint)
|
|
499
|
-
* - Target size (varint)
|
|
500
|
-
* - Sequence of copy/insert instructions
|
|
501
|
-
*
|
|
502
|
-
* @param {Uint8Array} base - The source/base object
|
|
503
|
-
* @param {Uint8Array} target - The target object to encode as delta
|
|
504
|
-
* @returns {Uint8Array} The delta data (can be applied with {@link applyDelta})
|
|
505
|
-
*
|
|
506
|
-
* @example
|
|
507
|
-
* // Create a delta for similar files
|
|
508
|
-
* const v1 = new TextEncoder().encode('Hello, World!');
|
|
509
|
-
* const v2 = new TextEncoder().encode('Hello, Universe!');
|
|
510
|
-
* const delta = createDelta(v1, v2);
|
|
511
|
-
*
|
|
512
|
-
* // Delta should be smaller than v2 if there's good overlap
|
|
513
|
-
* console.log(`Original: ${v2.length}, Delta: ${delta.length}`);
|
|
514
|
-
*
|
|
515
|
-
* @example
|
|
516
|
-
* // Verify delta correctness
|
|
517
|
-
* const reconstructed = applyDelta(v1, delta);
|
|
518
|
-
* // reconstructed should equal v2
|
|
519
|
-
*/
|
|
520
|
-
export function createDelta(base, target) {
|
|
521
|
-
const instructions = [];
|
|
522
|
-
// Add source and target size headers
|
|
523
|
-
instructions.push(encodeDeltaSize(base.length));
|
|
524
|
-
instructions.push(encodeDeltaSize(target.length));
|
|
525
|
-
if (target.length === 0) {
|
|
526
|
-
// Empty target, just return headers
|
|
527
|
-
return concatArrays(instructions);
|
|
528
|
-
}
|
|
529
|
-
if (base.length === 0) {
|
|
530
|
-
// No base to copy from, insert everything
|
|
531
|
-
emitInserts(instructions, target, 0, target.length);
|
|
532
|
-
return concatArrays(instructions);
|
|
533
|
-
}
|
|
534
|
-
// Build optimized hash index using Rabin fingerprints
|
|
535
|
-
const index = buildHashIndex(base);
|
|
536
|
-
// For large files, process in chunks to limit memory pressure
|
|
537
|
-
const isLargeFile = target.length > CHUNK_SIZE;
|
|
538
|
-
// Scan target and find matches using rolling hash
|
|
539
|
-
let targetOffset = 0;
|
|
540
|
-
let insertStart = 0;
|
|
541
|
-
// Initialize rolling hash if we have enough bytes
|
|
542
|
-
let currentHash = target.length >= WINDOW_SIZE ? rabinHash(target, 0) : 0;
|
|
543
|
-
while (targetOffset < target.length) {
|
|
544
|
-
let bestMatchOffset = -1;
|
|
545
|
-
let bestMatchLength = 0;
|
|
546
|
-
// Look for a match if we have enough bytes
|
|
547
|
-
if (targetOffset <= target.length - WINDOW_SIZE) {
|
|
548
|
-
// Look up candidates from the optimized index
|
|
549
|
-
for (const baseOffset of lookupIndex(index, currentHash)) {
|
|
550
|
-
// Verify the match and extend it using optimized comparison
|
|
551
|
-
const maxLength = Math.min(base.length - baseOffset, target.length - targetOffset);
|
|
552
|
-
const matchLength = isLargeFile
|
|
553
|
-
? getMatchLengthOptimized(base, baseOffset, target, targetOffset, maxLength)
|
|
554
|
-
: getMatchLength(base, baseOffset, target, targetOffset, maxLength);
|
|
555
|
-
if (matchLength >= WINDOW_SIZE && matchLength > bestMatchLength) {
|
|
556
|
-
bestMatchOffset = baseOffset;
|
|
557
|
-
bestMatchLength = matchLength;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
if (bestMatchLength >= MIN_COPY_SIZE) {
|
|
562
|
-
// Emit pending inserts
|
|
563
|
-
if (targetOffset > insertStart) {
|
|
564
|
-
emitInserts(instructions, target, insertStart, targetOffset);
|
|
565
|
-
}
|
|
566
|
-
// Emit copy instruction
|
|
567
|
-
emitCopy(instructions, bestMatchOffset, bestMatchLength);
|
|
568
|
-
targetOffset += bestMatchLength;
|
|
569
|
-
insertStart = targetOffset;
|
|
570
|
-
// Re-compute hash at new position (skip rolling for long jumps)
|
|
571
|
-
if (targetOffset <= target.length - WINDOW_SIZE) {
|
|
572
|
-
currentHash = rabinHash(target, targetOffset);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
targetOffset++;
|
|
577
|
-
// Roll the hash forward (O(1) operation)
|
|
578
|
-
if (targetOffset <= target.length - WINDOW_SIZE) {
|
|
579
|
-
currentHash = rabinRoll(currentHash, target[targetOffset - 1], target[targetOffset + WINDOW_SIZE - 1]);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
// Emit any remaining inserts
|
|
584
|
-
if (target.length > insertStart) {
|
|
585
|
-
emitInserts(instructions, target, insertStart, target.length);
|
|
586
|
-
}
|
|
587
|
-
return concatArrays(instructions);
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Computes a simple hash of a byte sequence for delta matching.
|
|
591
|
-
*
|
|
592
|
-
* @description Uses a fast multiplicative hash (similar to djb2) for
|
|
593
|
-
* building the hash table used in delta creation.
|
|
594
|
-
*
|
|
595
|
-
* @param {Uint8Array} data - The data buffer
|
|
596
|
-
* @param {number} offset - Starting offset
|
|
597
|
-
* @param {number} length - Number of bytes to hash
|
|
598
|
-
* @returns {number} 32-bit hash value
|
|
599
|
-
* @internal
|
|
600
|
-
*/
|
|
601
|
-
function hashBytes(data, offset, length) {
|
|
602
|
-
let hash = 0;
|
|
603
|
-
for (let i = 0; i < length; i++) {
|
|
604
|
-
hash = ((hash << 5) - hash + data[offset + i]) | 0;
|
|
605
|
-
}
|
|
606
|
-
return hash;
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Finds the length of matching bytes between two array regions.
|
|
610
|
-
*
|
|
611
|
-
* @description Compares bytes starting from the given offsets and returns
|
|
612
|
-
* how many consecutive bytes match. Used to extend hash-based matches.
|
|
613
|
-
*
|
|
614
|
-
* @param {Uint8Array} a - First array
|
|
615
|
-
* @param {number} aOffset - Starting offset in first array
|
|
616
|
-
* @param {Uint8Array} b - Second array
|
|
617
|
-
* @param {number} bOffset - Starting offset in second array
|
|
618
|
-
* @param {number} maxLength - Maximum number of bytes to compare
|
|
619
|
-
* @returns {number} Number of matching bytes (0 to maxLength)
|
|
620
|
-
* @internal
|
|
621
|
-
*/
|
|
622
|
-
function getMatchLength(a, aOffset, b, bOffset, maxLength) {
|
|
623
|
-
let length = 0;
|
|
624
|
-
while (length < maxLength && a[aOffset + length] === b[bOffset + length]) {
|
|
625
|
-
length++;
|
|
626
|
-
}
|
|
627
|
-
return length;
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Emits insert instructions for a range of literal bytes.
|
|
631
|
-
*
|
|
632
|
-
* @description Insert commands can only encode 1-127 bytes each, so this
|
|
633
|
-
* function splits larger ranges into multiple instructions as needed.
|
|
634
|
-
*
|
|
635
|
-
* @param {Uint8Array[]} instructions - Array to append instructions to
|
|
636
|
-
* @param {Uint8Array} data - Source data buffer
|
|
637
|
-
* @param {number} start - Starting offset (inclusive)
|
|
638
|
-
* @param {number} end - Ending offset (exclusive)
|
|
639
|
-
* @internal
|
|
640
|
-
*/
|
|
641
|
-
function emitInserts(instructions, data, start, end) {
|
|
642
|
-
const MAX_INSERT = 127;
|
|
643
|
-
let offset = start;
|
|
644
|
-
while (offset < end) {
|
|
645
|
-
const size = Math.min(MAX_INSERT, end - offset);
|
|
646
|
-
const instruction = new Uint8Array(1 + size);
|
|
647
|
-
instruction[0] = size; // Insert command: size in lower 7 bits
|
|
648
|
-
instruction.set(data.subarray(offset, offset + size), 1);
|
|
649
|
-
instructions.push(instruction);
|
|
650
|
-
offset += size;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* Emits a copy instruction that copies bytes from the base object.
|
|
655
|
-
*
|
|
656
|
-
* @description Encodes a copy instruction using Git's compact format where
|
|
657
|
-
* only non-zero offset and size bytes are included, indicated by bit flags.
|
|
658
|
-
*
|
|
659
|
-
* **Encoding Details:**
|
|
660
|
-
* - Offset bytes (up to 4) are included based on bits 0-3 of command byte
|
|
661
|
-
* - Size bytes (up to 3) are included based on bits 4-6 of command byte
|
|
662
|
-
* - Size of 0x10000 (65536) is encoded as no size bytes (size=0 means 0x10000)
|
|
663
|
-
* - Offset of 0 means no offset bytes are included
|
|
664
|
-
*
|
|
665
|
-
* @param {Uint8Array[]} instructions - Array to append the instruction to
|
|
666
|
-
* @param {number} offset - Byte offset in base object to copy from
|
|
667
|
-
* @param {number} size - Number of bytes to copy
|
|
668
|
-
* @internal
|
|
669
|
-
*/
|
|
670
|
-
function emitCopy(instructions, offset, size) {
|
|
671
|
-
const bytes = [];
|
|
672
|
-
let cmd = COPY_INSTRUCTION;
|
|
673
|
-
// Encode offset bytes (little-endian)
|
|
674
|
-
if (offset & 0xff) {
|
|
675
|
-
cmd |= 0x01;
|
|
676
|
-
bytes.push(offset & 0xff);
|
|
677
|
-
}
|
|
678
|
-
if (offset & 0xff00) {
|
|
679
|
-
cmd |= 0x02;
|
|
680
|
-
bytes.push((offset >> 8) & 0xff);
|
|
681
|
-
}
|
|
682
|
-
if (offset & 0xff0000) {
|
|
683
|
-
cmd |= 0x04;
|
|
684
|
-
bytes.push((offset >> 16) & 0xff);
|
|
685
|
-
}
|
|
686
|
-
if (offset & 0xff000000) {
|
|
687
|
-
cmd |= 0x08;
|
|
688
|
-
bytes.push((offset >> 24) & 0xff);
|
|
689
|
-
}
|
|
690
|
-
// Special case: if offset is 0, we don't emit any offset bytes
|
|
691
|
-
// The cmd byte already indicates no offset bytes are present
|
|
692
|
-
// Encode size bytes (little-endian)
|
|
693
|
-
// Note: size of 0x10000 is encoded as no size bytes (all zero)
|
|
694
|
-
if (size !== 0x10000) {
|
|
695
|
-
if (size & 0xff) {
|
|
696
|
-
cmd |= 0x10;
|
|
697
|
-
bytes.push(size & 0xff);
|
|
698
|
-
}
|
|
699
|
-
if (size & 0xff00) {
|
|
700
|
-
cmd |= 0x20;
|
|
701
|
-
bytes.push((size >> 8) & 0xff);
|
|
702
|
-
}
|
|
703
|
-
if (size & 0xff0000) {
|
|
704
|
-
cmd |= 0x40;
|
|
705
|
-
bytes.push((size >> 16) & 0xff);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
// If size is 0x10000, we don't set any size bits, which encodes as size=0x10000
|
|
709
|
-
const instruction = new Uint8Array(1 + bytes.length);
|
|
710
|
-
instruction[0] = cmd;
|
|
711
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
712
|
-
instruction[1 + i] = bytes[i];
|
|
713
|
-
}
|
|
714
|
-
instructions.push(instruction);
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* Concatenates multiple Uint8Arrays into a single array.
|
|
718
|
-
*
|
|
719
|
-
* @param {Uint8Array[]} arrays - Arrays to concatenate
|
|
720
|
-
* @returns {Uint8Array} Combined array
|
|
721
|
-
* @internal
|
|
722
|
-
*/
|
|
723
|
-
function concatArrays(arrays) {
|
|
724
|
-
let totalLength = 0;
|
|
725
|
-
for (const arr of arrays) {
|
|
726
|
-
totalLength += arr.length;
|
|
727
|
-
}
|
|
728
|
-
const result = new Uint8Array(totalLength);
|
|
729
|
-
let offset = 0;
|
|
730
|
-
for (const arr of arrays) {
|
|
731
|
-
result.set(arr, offset);
|
|
732
|
-
offset += arr.length;
|
|
733
|
-
}
|
|
734
|
-
return result;
|
|
735
|
-
}
|
|
736
|
-
//# sourceMappingURL=delta.js.map
|