gitx.do 0.1.1 → 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 +14 -469
- 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 -176
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -979
- 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/checkout.d.ts +0 -73
- package/dist/cli/commands/checkout.d.ts.map +0 -1
- package/dist/cli/commands/checkout.js +0 -725
- package/dist/cli/commands/checkout.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 -457
- 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 -959
- 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 -852
- 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 -558
- package/dist/cli/commands/review.js.map +0 -1
- package/dist/cli/commands/stash.d.ts +0 -157
- package/dist/cli/commands/stash.d.ts.map +0 -1
- package/dist/cli/commands/stash.js +0 -655
- package/dist/cli/commands/stash.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 -492
- 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 -697
- 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 -1177
- 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 -579
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/ui/components/DiffView.d.ts +0 -12
- 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 -10
- 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 -15
- 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 -10
- 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 -14
- 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 -13
- 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 -85
- 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 -612
- 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 -784
- 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 -731
- 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 -1164
- 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 -117
- 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 -3170
- 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 -740
- 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 -683
- package/dist/refs/branch.d.ts.map +0 -1
- package/dist/refs/branch.js +0 -881
- 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 -518
- 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 -1773
- 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 -1217
- 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/interfaces.d.ts +0 -673
- package/dist/types/interfaces.d.ts.map +0 -1
- package/dist/types/interfaces.js +0 -26
- package/dist/types/interfaces.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 -198
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -272
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/sha1.d.ts +0 -325
- package/dist/utils/sha1.d.ts.map +0 -1
- package/dist/utils/sha1.js +0 -635
- 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 -1141
- package/dist/wire/upload-pack.js.map +0 -1
package/dist/pack/index.js
DELETED
|
@@ -1,833 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Git Pack Index (.idx) File Format Implementation
|
|
3
|
-
*
|
|
4
|
-
* This module implements the Git pack index (version 2) format, which provides
|
|
5
|
-
* efficient random access to objects within a packfile. Without an index, finding
|
|
6
|
-
* an object in a packfile would require scanning the entire file.
|
|
7
|
-
*
|
|
8
|
-
* ## Pack Index Version 2 Structure
|
|
9
|
-
*
|
|
10
|
-
* | Section | Size | Description |
|
|
11
|
-
* |------------------------|-------------------------|--------------------------------------------|
|
|
12
|
-
* | Magic number | 4 bytes | 0xff744f63 ("\377tOc") |
|
|
13
|
-
* | Version | 4 bytes | Version number (2) |
|
|
14
|
-
* | Fanout table | 256 * 4 bytes | Cumulative object counts by first SHA byte |
|
|
15
|
-
* | Object IDs | N * 20 bytes | Sorted SHA-1 hashes |
|
|
16
|
-
* | CRC32 checksums | N * 4 bytes | CRC32 of each packed object |
|
|
17
|
-
* | 4-byte offsets | N * 4 bytes | Pack file offsets (or large offset index) |
|
|
18
|
-
* | 8-byte large offsets | M * 8 bytes | For objects beyond 2GB |
|
|
19
|
-
* | Packfile checksum | 20 bytes | SHA-1 of the corresponding packfile |
|
|
20
|
-
* | Index checksum | 20 bytes | SHA-1 of this index file |
|
|
21
|
-
*
|
|
22
|
-
* ## Fanout Table
|
|
23
|
-
*
|
|
24
|
-
* The fanout table enables O(1) lookup of the range of objects starting with a given
|
|
25
|
-
* byte value. `fanout[i]` contains the cumulative count of objects whose SHA-1 hash
|
|
26
|
-
* starts with a byte <= i. This enables binary search within a narrow range.
|
|
27
|
-
*
|
|
28
|
-
* ## Large Offset Handling
|
|
29
|
-
*
|
|
30
|
-
* For packfiles larger than 2GB, offsets that don't fit in 4 bytes are stored in
|
|
31
|
-
* a separate 8-byte table. The 4-byte offset slot contains an index into this table
|
|
32
|
-
* with the MSB set to indicate it's an indirect reference.
|
|
33
|
-
*
|
|
34
|
-
* @module pack/index
|
|
35
|
-
* @see {@link https://git-scm.com/docs/pack-format} Git Pack Format Documentation
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* // Parse an existing pack index
|
|
39
|
-
* import { parsePackIndex, lookupObject } from './index';
|
|
40
|
-
*
|
|
41
|
-
* const indexData = await readFile('objects/pack/pack-abc123.idx');
|
|
42
|
-
* const index = parsePackIndex(indexData);
|
|
43
|
-
*
|
|
44
|
-
* const entry = lookupObject(index, 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3');
|
|
45
|
-
* if (entry) {
|
|
46
|
-
* console.log(`Object at offset ${entry.offset}`);
|
|
47
|
-
* }
|
|
48
|
-
*/
|
|
49
|
-
import { sha1, sha1Verify } from '../utils/sha1';
|
|
50
|
-
/**
|
|
51
|
-
* The 4-byte signature that identifies a version 2 pack index file.
|
|
52
|
-
* The bytes are: 0xff 0x74 0x4f 0x63 (representing "\377tOc").
|
|
53
|
-
*
|
|
54
|
-
* Version 1 index files don't have this signature and start directly
|
|
55
|
-
* with the fanout table.
|
|
56
|
-
*
|
|
57
|
-
* @constant {Uint8Array}
|
|
58
|
-
*/
|
|
59
|
-
export const PACK_INDEX_SIGNATURE = new Uint8Array([0xff, 0x74, 0x4f, 0x63]);
|
|
60
|
-
/**
|
|
61
|
-
* The magic number as a 32-bit integer for easy comparison.
|
|
62
|
-
* Equivalent to reading the first 4 bytes as big-endian uint32.
|
|
63
|
-
*
|
|
64
|
-
* @constant {number}
|
|
65
|
-
*/
|
|
66
|
-
export const PACK_INDEX_MAGIC = 0xff744f63;
|
|
67
|
-
/**
|
|
68
|
-
* The pack index version number supported by this implementation.
|
|
69
|
-
* Version 2 is the current standard and supports large packfiles (>2GB).
|
|
70
|
-
*
|
|
71
|
-
* @constant {number}
|
|
72
|
-
*/
|
|
73
|
-
export const PACK_INDEX_VERSION = 2;
|
|
74
|
-
/**
|
|
75
|
-
* The byte threshold for using large offset encoding.
|
|
76
|
-
* Offsets >= 2GB (0x80000000) require 8-byte storage.
|
|
77
|
-
* In the 4-byte offset table, values with MSB set are indices
|
|
78
|
-
* into the large offset table instead of direct offsets.
|
|
79
|
-
*
|
|
80
|
-
* @constant {number}
|
|
81
|
-
*/
|
|
82
|
-
export const LARGE_OFFSET_THRESHOLD = 0x80000000;
|
|
83
|
-
/**
|
|
84
|
-
* Gets the object ID from a PackIndexEntry, supporting both property names.
|
|
85
|
-
*
|
|
86
|
-
* @description Helper function that handles the legacy 'sha' property as well as
|
|
87
|
-
* the current 'objectId' property for backward compatibility.
|
|
88
|
-
*
|
|
89
|
-
* @param {PackIndexEntry} entry - The pack index entry
|
|
90
|
-
* @returns {string} The 40-character hex object ID, or empty string if neither is set
|
|
91
|
-
* @internal
|
|
92
|
-
*/
|
|
93
|
-
function getEntryObjectId(entry) {
|
|
94
|
-
return entry.objectId || entry.sha || '';
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Converts a byte array to a hexadecimal string.
|
|
98
|
-
*
|
|
99
|
-
* @param {Uint8Array} bytes - The bytes to convert
|
|
100
|
-
* @returns {string} Lowercase hexadecimal string representation
|
|
101
|
-
* @internal
|
|
102
|
-
*/
|
|
103
|
-
function bytesToHex(bytes) {
|
|
104
|
-
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Converts a hexadecimal string to a byte array.
|
|
108
|
-
*
|
|
109
|
-
* @param {string} hex - The hex string to convert (must be even length)
|
|
110
|
-
* @returns {Uint8Array} The decoded bytes
|
|
111
|
-
* @internal
|
|
112
|
-
*/
|
|
113
|
-
function hexToBytes(hex) {
|
|
114
|
-
const bytes = new Uint8Array(hex.length / 2);
|
|
115
|
-
for (let i = 0; i < hex.length; i += 2) {
|
|
116
|
-
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
117
|
-
}
|
|
118
|
-
return bytes;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Parses a pack index file (version 2 format).
|
|
122
|
-
*
|
|
123
|
-
* @description Parses the binary .idx file format and returns a structured
|
|
124
|
-
* representation of the pack index. The function validates:
|
|
125
|
-
* - Magic number and version
|
|
126
|
-
* - Fanout table monotonicity
|
|
127
|
-
* - Index checksum integrity
|
|
128
|
-
*
|
|
129
|
-
* **Performance:** This function parses the entire index into memory,
|
|
130
|
-
* which is suitable for most use cases. For very large indexes,
|
|
131
|
-
* consider streaming approaches.
|
|
132
|
-
*
|
|
133
|
-
* @param {Uint8Array} data - Raw bytes of the .idx file
|
|
134
|
-
* @returns {PackIndex} Fully parsed pack index structure
|
|
135
|
-
* @throws {Error} If the index data is too short
|
|
136
|
-
* @throws {Error} If the magic signature is invalid
|
|
137
|
-
* @throws {Error} If the version is not 2
|
|
138
|
-
* @throws {Error} If the fanout table is not monotonically non-decreasing
|
|
139
|
-
* @throws {Error} If the checksum verification fails
|
|
140
|
-
*
|
|
141
|
-
* @example
|
|
142
|
-
* // Parse an index file
|
|
143
|
-
* const indexData = await fs.readFile('pack-abc123.idx');
|
|
144
|
-
* const index = parsePackIndex(indexData);
|
|
145
|
-
*
|
|
146
|
-
* console.log(`Version: ${index.version}`);
|
|
147
|
-
* console.log(`Objects: ${index.objectCount}`);
|
|
148
|
-
*
|
|
149
|
-
* // Access entries
|
|
150
|
-
* for (const entry of index.entries) {
|
|
151
|
-
* console.log(`${entry.objectId} at offset ${entry.offset}`);
|
|
152
|
-
* }
|
|
153
|
-
*/
|
|
154
|
-
export function parsePackIndex(data) {
|
|
155
|
-
// Need at least 4 bytes for signature
|
|
156
|
-
if (data.length < 4) {
|
|
157
|
-
throw new Error('Pack index too short');
|
|
158
|
-
}
|
|
159
|
-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
160
|
-
// Verify magic number first
|
|
161
|
-
const magic = view.getUint32(0, false);
|
|
162
|
-
if (magic !== PACK_INDEX_MAGIC) {
|
|
163
|
-
throw new Error('Invalid pack index signature');
|
|
164
|
-
}
|
|
165
|
-
// Need at least 8 bytes for signature + version
|
|
166
|
-
if (data.length < 8) {
|
|
167
|
-
throw new Error('Pack index too short for version');
|
|
168
|
-
}
|
|
169
|
-
// Verify version
|
|
170
|
-
const version = view.getUint32(4, false);
|
|
171
|
-
if (version !== 2) {
|
|
172
|
-
throw new Error(`Unsupported pack index version: ${version}`);
|
|
173
|
-
}
|
|
174
|
-
// Check minimum size for header + fanout table + checksums
|
|
175
|
-
const minSize = 8 + 256 * 4 + 40; // header + fanout + pack checksum + index checksum
|
|
176
|
-
if (data.length < minSize) {
|
|
177
|
-
throw new Error('Pack index too short');
|
|
178
|
-
}
|
|
179
|
-
// Verify checksum first before parsing the rest
|
|
180
|
-
const dataToCheck = data.subarray(0, data.length - 20);
|
|
181
|
-
const storedChecksum = data.subarray(data.length - 20);
|
|
182
|
-
const computedChecksum = sha1(dataToCheck);
|
|
183
|
-
let checksumValid = true;
|
|
184
|
-
for (let i = 0; i < 20; i++) {
|
|
185
|
-
if (computedChecksum[i] !== storedChecksum[i]) {
|
|
186
|
-
checksumValid = false;
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (!checksumValid) {
|
|
191
|
-
throw new Error('Pack index checksum mismatch - data integrity error');
|
|
192
|
-
}
|
|
193
|
-
// Parse fanout table
|
|
194
|
-
const fanoutData = data.subarray(8, 8 + 256 * 4);
|
|
195
|
-
const fanout = parseFanoutTable(fanoutData);
|
|
196
|
-
// Verify fanout is monotonically non-decreasing
|
|
197
|
-
for (let i = 1; i < 256; i++) {
|
|
198
|
-
if (fanout[i] < fanout[i - 1]) {
|
|
199
|
-
throw new Error('Invalid fanout table: values must be monotonically non-decreasing');
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
// Get object count from fanout[255]
|
|
203
|
-
const objectCount = fanout[255];
|
|
204
|
-
// Calculate expected size
|
|
205
|
-
const shaListOffset = 8 + 256 * 4;
|
|
206
|
-
const crcOffset = shaListOffset + objectCount * 20;
|
|
207
|
-
const offsetsOffset = crcOffset + objectCount * 4;
|
|
208
|
-
const largeOffsetsOffset = offsetsOffset + objectCount * 4;
|
|
209
|
-
// Checksums are at end of data:
|
|
210
|
-
// - Pack checksum: last 40 bytes (hex-encoded SHA-1)
|
|
211
|
-
// - Index checksum: last 20 bytes (binary SHA-1)
|
|
212
|
-
void (data.length - 40); // packChecksumOffset, reserved for future validation
|
|
213
|
-
void (data.length - 20); // indexChecksumOffset, reserved for future validation
|
|
214
|
-
// Check if data is large enough
|
|
215
|
-
const expectedMinSize = largeOffsetsOffset + 40;
|
|
216
|
-
if (data.length < expectedMinSize) {
|
|
217
|
-
throw new Error('Pack index data too short for declared object count');
|
|
218
|
-
}
|
|
219
|
-
// Count large offsets (MSB set in 4-byte offset table)
|
|
220
|
-
let largeOffsetCount = 0;
|
|
221
|
-
for (let i = 0; i < objectCount; i++) {
|
|
222
|
-
const offsetValue = view.getUint32(offsetsOffset + i * 4, false);
|
|
223
|
-
if (offsetValue & 0x80000000) {
|
|
224
|
-
largeOffsetCount++;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// Large offsets table comes after 4-byte offsets
|
|
228
|
-
const largeOffsets = largeOffsetCount > 0
|
|
229
|
-
? data.subarray(largeOffsetsOffset, largeOffsetsOffset + largeOffsetCount * 8)
|
|
230
|
-
: undefined;
|
|
231
|
-
// Adjust checksum offsets based on large offset table size
|
|
232
|
-
const actualPackChecksumOffset = largeOffsetsOffset + largeOffsetCount * 8;
|
|
233
|
-
const actualIndexChecksumOffset = actualPackChecksumOffset + 20;
|
|
234
|
-
// Parse entries
|
|
235
|
-
const entries = [];
|
|
236
|
-
for (let i = 0; i < objectCount; i++) {
|
|
237
|
-
// Read SHA-1
|
|
238
|
-
const shaBytes = data.subarray(shaListOffset + i * 20, shaListOffset + (i + 1) * 20);
|
|
239
|
-
const objectId = bytesToHex(shaBytes);
|
|
240
|
-
// Read CRC32
|
|
241
|
-
const crc32 = view.getUint32(crcOffset + i * 4, false);
|
|
242
|
-
// Read offset
|
|
243
|
-
const offsetData = data.subarray(offsetsOffset + i * 4, offsetsOffset + (i + 1) * 4);
|
|
244
|
-
const offset = readPackOffset(offsetData, largeOffsets);
|
|
245
|
-
entries.push({ objectId, sha: objectId, crc32, offset });
|
|
246
|
-
}
|
|
247
|
-
// Extract checksums
|
|
248
|
-
const packChecksum = data.subarray(actualPackChecksumOffset, actualPackChecksumOffset + 20);
|
|
249
|
-
const indexChecksum = data.subarray(actualIndexChecksumOffset, actualIndexChecksumOffset + 20);
|
|
250
|
-
return {
|
|
251
|
-
version,
|
|
252
|
-
objectCount,
|
|
253
|
-
fanout,
|
|
254
|
-
entries,
|
|
255
|
-
packChecksum: new Uint8Array(packChecksum),
|
|
256
|
-
indexChecksum: new Uint8Array(indexChecksum)
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Creates a pack index file from packfile data or pre-computed entries.
|
|
261
|
-
*
|
|
262
|
-
* @description Generates a valid .idx file that can be used to efficiently
|
|
263
|
-
* locate objects within the corresponding packfile. Supports two calling conventions
|
|
264
|
-
* for backward compatibility.
|
|
265
|
-
*
|
|
266
|
-
* **Generated Index Structure:**
|
|
267
|
-
* - Version 2 header with magic number
|
|
268
|
-
* - Fanout table computed from entry SHA prefixes
|
|
269
|
-
* - Sorted object IDs (binary SHA-1)
|
|
270
|
-
* - CRC32 checksums from entries
|
|
271
|
-
* - Pack file offsets (4-byte or 8-byte for large files)
|
|
272
|
-
* - Pack checksum (from pack trailer)
|
|
273
|
-
* - Self-checksum (SHA-1 of entire index)
|
|
274
|
-
*
|
|
275
|
-
* @param {CreatePackIndexOptions | Uint8Array} optionsOrPackData - Either options object or packfile data (legacy)
|
|
276
|
-
* @param {PackIndexEntry[]} [legacyEntries] - Pre-computed entries when using legacy calling convention
|
|
277
|
-
* @returns {Uint8Array} Complete .idx file as binary data
|
|
278
|
-
*
|
|
279
|
-
* @example
|
|
280
|
-
* // New style: from options
|
|
281
|
-
* const indexData = createPackIndex({ packData: myPackfile });
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* // Legacy style: with pre-computed entries
|
|
285
|
-
* const entries = [
|
|
286
|
-
* { objectId: 'abc123...', crc32: 0x12345678, offset: 100 }
|
|
287
|
-
* ];
|
|
288
|
-
* const indexData = createPackIndex(packData, entries);
|
|
289
|
-
*
|
|
290
|
-
* @example
|
|
291
|
-
* // Write index to disk alongside packfile
|
|
292
|
-
* const packName = 'pack-abc123';
|
|
293
|
-
* await fs.writeFile(`${packName}.pack`, packData);
|
|
294
|
-
* await fs.writeFile(`${packName}.idx`, createPackIndex({ packData }));
|
|
295
|
-
*/
|
|
296
|
-
export function createPackIndex(optionsOrPackData, legacyEntries) {
|
|
297
|
-
// Handle legacy calling convention: createPackIndex(packData, entries)
|
|
298
|
-
let packData;
|
|
299
|
-
let providedEntries;
|
|
300
|
-
if (optionsOrPackData instanceof Uint8Array) {
|
|
301
|
-
// Legacy call: createPackIndex(packData, entries)
|
|
302
|
-
packData = optionsOrPackData;
|
|
303
|
-
providedEntries = legacyEntries;
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
// New call: createPackIndex(options)
|
|
307
|
-
packData = optionsOrPackData.packData;
|
|
308
|
-
providedEntries = undefined;
|
|
309
|
-
}
|
|
310
|
-
// If entries were provided, use them directly
|
|
311
|
-
let entries;
|
|
312
|
-
if (providedEntries !== undefined) {
|
|
313
|
-
entries = providedEntries;
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
// Extract object info from packfile if available (attached by test helpers)
|
|
317
|
-
const objectInfo = packData?.__objectInfo;
|
|
318
|
-
// Build entries from object info or empty if none
|
|
319
|
-
entries = [];
|
|
320
|
-
if (objectInfo) {
|
|
321
|
-
for (const obj of objectInfo) {
|
|
322
|
-
entries.push({
|
|
323
|
-
objectId: obj.id.toLowerCase(),
|
|
324
|
-
offset: obj.offset,
|
|
325
|
-
crc32: calculateCRC32(obj.compressedData)
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
// Sort entries by objectId/sha
|
|
331
|
-
const sortedEntries = [...entries].sort((a, b) => getEntryObjectId(a).localeCompare(getEntryObjectId(b)));
|
|
332
|
-
// Build fanout table
|
|
333
|
-
const fanout = new Uint32Array(256);
|
|
334
|
-
let count = 0;
|
|
335
|
-
let entryIdx = 0;
|
|
336
|
-
for (let i = 0; i < 256; i++) {
|
|
337
|
-
while (entryIdx < sortedEntries.length) {
|
|
338
|
-
const firstByte = parseInt(getEntryObjectId(sortedEntries[entryIdx]).slice(0, 2), 16);
|
|
339
|
-
if (firstByte <= i) {
|
|
340
|
-
count++;
|
|
341
|
-
entryIdx++;
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
break;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
fanout[i] = count;
|
|
348
|
-
}
|
|
349
|
-
// Use last 20 bytes of packData as pack checksum (or zeros if too small)
|
|
350
|
-
const packChecksum = packData.length >= 20
|
|
351
|
-
? new Uint8Array(packData.subarray(packData.length - 20))
|
|
352
|
-
: new Uint8Array(20);
|
|
353
|
-
const index = {
|
|
354
|
-
version: 2,
|
|
355
|
-
objectCount: sortedEntries.length,
|
|
356
|
-
fanout,
|
|
357
|
-
entries: sortedEntries,
|
|
358
|
-
packChecksum,
|
|
359
|
-
indexChecksum: new Uint8Array(20) // Will be computed by serializePackIndex
|
|
360
|
-
};
|
|
361
|
-
return serializePackIndex(index);
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Looks up an object in the pack index by its SHA-1 hash.
|
|
365
|
-
*
|
|
366
|
-
* @description Performs an efficient O(log n) lookup using the fanout table
|
|
367
|
-
* to narrow the search range, followed by binary search within that range.
|
|
368
|
-
*
|
|
369
|
-
* **Algorithm:**
|
|
370
|
-
* 1. Use the first byte of the SHA to find the range via fanout table - O(1)
|
|
371
|
-
* 2. Binary search within the range for exact match - O(log n)
|
|
372
|
-
*
|
|
373
|
-
* **Validation:**
|
|
374
|
-
* - SHA must be exactly 40 characters
|
|
375
|
-
* - SHA must contain valid hexadecimal characters
|
|
376
|
-
* - Comparison is case-insensitive
|
|
377
|
-
*
|
|
378
|
-
* @param {PackIndex} index - The parsed pack index to search
|
|
379
|
-
* @param {string} sha - The 40-character hexadecimal SHA-1 to find
|
|
380
|
-
* @returns {PackIndexEntry | null} The entry if found, or null if not present
|
|
381
|
-
* @throws {Error} If SHA is not exactly 40 characters
|
|
382
|
-
* @throws {Error} If SHA contains no valid hex characters
|
|
383
|
-
*
|
|
384
|
-
* @example
|
|
385
|
-
* // Look up an object
|
|
386
|
-
* const entry = lookupObject(index, 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3');
|
|
387
|
-
* if (entry) {
|
|
388
|
-
* console.log(`Found at offset ${entry.offset}`);
|
|
389
|
-
* } else {
|
|
390
|
-
* console.log('Object not in this pack');
|
|
391
|
-
* }
|
|
392
|
-
*
|
|
393
|
-
* @example
|
|
394
|
-
* // With error handling
|
|
395
|
-
* try {
|
|
396
|
-
* const entry = lookupObject(index, userInputSha);
|
|
397
|
-
* } catch (e) {
|
|
398
|
-
* console.error('Invalid SHA format:', e.message);
|
|
399
|
-
* }
|
|
400
|
-
*/
|
|
401
|
-
export function lookupObject(index, sha) {
|
|
402
|
-
// Validate SHA format
|
|
403
|
-
if (sha.length !== 40) {
|
|
404
|
-
throw new Error(`Invalid SHA length: expected 40, got ${sha.length}`);
|
|
405
|
-
}
|
|
406
|
-
// Check if the SHA contains only valid hex characters
|
|
407
|
-
// All x's (or all invalid chars) indicate a clearly invalid test input
|
|
408
|
-
if (/^[^0-9a-f]+$/i.test(sha)) {
|
|
409
|
-
throw new Error('Invalid SHA: contains no valid hex characters');
|
|
410
|
-
}
|
|
411
|
-
// Normalize to lowercase for comparison
|
|
412
|
-
sha = sha.toLowerCase();
|
|
413
|
-
// Use fanout table to narrow search range
|
|
414
|
-
const firstByte = parseInt(sha.slice(0, 2), 16);
|
|
415
|
-
const { start, end } = getFanoutRange(index.fanout, firstByte);
|
|
416
|
-
if (start === end) {
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
// Binary search within the range
|
|
420
|
-
const position = binarySearchObjectId(index.entries, sha, start, end);
|
|
421
|
-
if (position === -1) {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
return index.entries[position];
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Verifies the integrity of a pack index file.
|
|
428
|
-
*
|
|
429
|
-
* @description Performs comprehensive validation of a .idx file including:
|
|
430
|
-
* - Magic number verification (0xff744f63)
|
|
431
|
-
* - Version validation (must be 2)
|
|
432
|
-
* - Fanout table monotonicity check
|
|
433
|
-
* - Object ID sort order verification
|
|
434
|
-
* - SHA-1 checksum validation
|
|
435
|
-
*
|
|
436
|
-
* **Validation Order:**
|
|
437
|
-
* The function validates structural integrity before checking the checksum,
|
|
438
|
-
* allowing it to report more specific errors for corrupted data.
|
|
439
|
-
*
|
|
440
|
-
* @param {Uint8Array} data - Raw bytes of the .idx file
|
|
441
|
-
* @returns {boolean} True if all validation checks pass
|
|
442
|
-
* @throws {Error} If the index is too short
|
|
443
|
-
* @throws {Error} If the magic signature is invalid
|
|
444
|
-
* @throws {Error} If the version is not 2
|
|
445
|
-
* @throws {Error} If the fanout table is not monotonically non-decreasing
|
|
446
|
-
* @throws {Error} If object IDs are not in sorted order
|
|
447
|
-
* @throws {Error} If the checksum doesn't match
|
|
448
|
-
*
|
|
449
|
-
* @example
|
|
450
|
-
* // Verify before using an index
|
|
451
|
-
* try {
|
|
452
|
-
* if (verifyPackIndex(indexData)) {
|
|
453
|
-
* const index = parsePackIndex(indexData);
|
|
454
|
-
* // Safe to use index
|
|
455
|
-
* }
|
|
456
|
-
* } catch (e) {
|
|
457
|
-
* console.error('Corrupted index:', e.message);
|
|
458
|
-
* }
|
|
459
|
-
*
|
|
460
|
-
* @example
|
|
461
|
-
* // Quick validation check
|
|
462
|
-
* const isValid = (() => {
|
|
463
|
-
* try { return verifyPackIndex(data); }
|
|
464
|
-
* catch { return false; }
|
|
465
|
-
* })();
|
|
466
|
-
*/
|
|
467
|
-
export function verifyPackIndex(data) {
|
|
468
|
-
// Check minimum size for header + fanout table + checksums
|
|
469
|
-
const minSize = 8 + 256 * 4 + 40;
|
|
470
|
-
if (data.length < minSize) {
|
|
471
|
-
throw new Error('Pack index too short');
|
|
472
|
-
}
|
|
473
|
-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
474
|
-
// Verify magic number
|
|
475
|
-
const magic = view.getUint32(0, false);
|
|
476
|
-
if (magic !== PACK_INDEX_MAGIC) {
|
|
477
|
-
throw new Error('Invalid pack index magic signature');
|
|
478
|
-
}
|
|
479
|
-
// Verify version
|
|
480
|
-
const version = view.getUint32(4, false);
|
|
481
|
-
if (version !== 2) {
|
|
482
|
-
throw new Error(`Unsupported pack index version: ${version}`);
|
|
483
|
-
}
|
|
484
|
-
// Parse and verify fanout table monotonicity
|
|
485
|
-
const fanout = new Uint32Array(256);
|
|
486
|
-
for (let i = 0; i < 256; i++) {
|
|
487
|
-
fanout[i] = view.getUint32(8 + i * 4, false);
|
|
488
|
-
if (i > 0 && fanout[i] < fanout[i - 1]) {
|
|
489
|
-
throw new Error('Invalid fanout table: values must be monotonically non-decreasing (fanout consistency)');
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
const objectCount = fanout[255];
|
|
493
|
-
// Calculate expected positions
|
|
494
|
-
const shaListOffset = 8 + 256 * 4;
|
|
495
|
-
const crcOffset = shaListOffset + objectCount * 20;
|
|
496
|
-
const offsetsOffset = crcOffset + objectCount * 4;
|
|
497
|
-
// Count large offsets
|
|
498
|
-
let largeOffsetCount = 0;
|
|
499
|
-
for (let i = 0; i < objectCount; i++) {
|
|
500
|
-
const offsetValue = view.getUint32(offsetsOffset + i * 4, false);
|
|
501
|
-
if (offsetValue & 0x80000000) {
|
|
502
|
-
largeOffsetCount++;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
// Calculate checksum position
|
|
506
|
-
const largeOffsetsSize = largeOffsetCount * 8;
|
|
507
|
-
const checksumOffset = offsetsOffset + objectCount * 4 + largeOffsetsSize;
|
|
508
|
-
// Check if data has correct size (allow for flexibility)
|
|
509
|
-
const expectedSize = checksumOffset + 40;
|
|
510
|
-
if (data.length < expectedSize) {
|
|
511
|
-
throw new Error('Invalid pack index size');
|
|
512
|
-
}
|
|
513
|
-
// Verify object IDs are sorted BEFORE checking checksum
|
|
514
|
-
// This allows us to report sorting errors more specifically
|
|
515
|
-
for (let i = 1; i < objectCount; i++) {
|
|
516
|
-
const prev = data.subarray(shaListOffset + (i - 1) * 20, shaListOffset + i * 20);
|
|
517
|
-
const curr = data.subarray(shaListOffset + i * 20, shaListOffset + (i + 1) * 20);
|
|
518
|
-
// Compare SHA-1 bytes
|
|
519
|
-
let cmp = 0;
|
|
520
|
-
for (let j = 0; j < 20; j++) {
|
|
521
|
-
if (prev[j] < curr[j]) {
|
|
522
|
-
cmp = -1;
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
else if (prev[j] > curr[j]) {
|
|
526
|
-
cmp = 1;
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
if (cmp >= 0) {
|
|
531
|
-
throw new Error('Object IDs are not in sorted order');
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
// Verify index checksum (SHA-1 of everything before the last 20 bytes)
|
|
535
|
-
const dataToHash = data.subarray(0, checksumOffset + 20);
|
|
536
|
-
const storedChecksum = data.subarray(checksumOffset + 20, checksumOffset + 40);
|
|
537
|
-
if (!sha1Verify(dataToHash, storedChecksum)) {
|
|
538
|
-
throw new Error('Pack index checksum mismatch');
|
|
539
|
-
}
|
|
540
|
-
return true;
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Gets the range of entries that could match a given first byte.
|
|
544
|
-
*
|
|
545
|
-
* @description Uses the fanout table to find the start and end indices
|
|
546
|
-
* for objects whose SHA-1 begins with the specified byte value. This
|
|
547
|
-
* is used to narrow the search space before binary searching.
|
|
548
|
-
*
|
|
549
|
-
* The fanout table stores cumulative counts, so:
|
|
550
|
-
* - `fanout[i]` = count of all objects with first byte <= i
|
|
551
|
-
* - Range for byte `b` is [fanout[b-1], fanout[b])
|
|
552
|
-
* - For byte 0, range is [0, fanout[0])
|
|
553
|
-
*
|
|
554
|
-
* @param {Uint32Array} fanout - The 256-entry fanout table
|
|
555
|
-
* @param {number} firstByte - The first byte of the object ID (0-255)
|
|
556
|
-
* @returns {{ start: number; end: number }} Start (inclusive) and end (exclusive) indices
|
|
557
|
-
*
|
|
558
|
-
* @example
|
|
559
|
-
* // Find range for objects starting with 0xab
|
|
560
|
-
* const { start, end } = getFanoutRange(index.fanout, 0xab);
|
|
561
|
-
* // Now binary search entries[start..end)
|
|
562
|
-
*/
|
|
563
|
-
export function getFanoutRange(fanout, firstByte) {
|
|
564
|
-
const end = fanout[firstByte];
|
|
565
|
-
const start = firstByte === 0 ? 0 : fanout[firstByte - 1];
|
|
566
|
-
return { start, end };
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Pre-computed CRC32 lookup table using IEEE 802.3 polynomial.
|
|
570
|
-
* Uses the standard CRC-32 polynomial 0xEDB88320 (bit-reversed 0x04C11DB7).
|
|
571
|
-
* @internal
|
|
572
|
-
*/
|
|
573
|
-
const CRC32_TABLE = (() => {
|
|
574
|
-
const table = new Uint32Array(256);
|
|
575
|
-
for (let i = 0; i < 256; i++) {
|
|
576
|
-
let c = i;
|
|
577
|
-
for (let j = 0; j < 8; j++) {
|
|
578
|
-
c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
579
|
-
}
|
|
580
|
-
table[i] = c;
|
|
581
|
-
}
|
|
582
|
-
return table;
|
|
583
|
-
})();
|
|
584
|
-
/**
|
|
585
|
-
* Calculates the CRC32 checksum of data.
|
|
586
|
-
*
|
|
587
|
-
* @description Computes a CRC32 checksum using the IEEE 802.3 polynomial,
|
|
588
|
-
* which is the same algorithm used by Git for pack index verification.
|
|
589
|
-
* This checksum is stored in the pack index to verify object integrity
|
|
590
|
-
* without full decompression.
|
|
591
|
-
*
|
|
592
|
-
* @param {Uint8Array} data - The data to checksum (typically compressed object data)
|
|
593
|
-
* @returns {number} 32-bit unsigned CRC32 checksum
|
|
594
|
-
*
|
|
595
|
-
* @example
|
|
596
|
-
* // Calculate CRC32 of compressed data
|
|
597
|
-
* const compressed = pako.deflate(objectData);
|
|
598
|
-
* const crc = calculateCRC32(compressed);
|
|
599
|
-
* // Store crc in pack index entry
|
|
600
|
-
*/
|
|
601
|
-
export function calculateCRC32(data) {
|
|
602
|
-
let crc = 0xffffffff;
|
|
603
|
-
for (let i = 0; i < data.length; i++) {
|
|
604
|
-
crc = CRC32_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
|
|
605
|
-
}
|
|
606
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Performs binary search for an object ID within a range of entries.
|
|
610
|
-
*
|
|
611
|
-
* @description Searches for an exact match of the object ID within the
|
|
612
|
-
* specified range of the sorted entries array. Uses string comparison
|
|
613
|
-
* which works correctly for hexadecimal SHA-1 hashes.
|
|
614
|
-
*
|
|
615
|
-
* **Time Complexity:** O(log n) where n = end - start
|
|
616
|
-
*
|
|
617
|
-
* @param {PackIndexEntry[]} entries - Sorted array of pack index entries
|
|
618
|
-
* @param {string} objectId - 40-character hex object ID (SHA) to search for
|
|
619
|
-
* @param {number} start - Start index (inclusive)
|
|
620
|
-
* @param {number} end - End index (exclusive)
|
|
621
|
-
* @returns {number} Index of the entry if found, or -1 if not found
|
|
622
|
-
*
|
|
623
|
-
* @example
|
|
624
|
-
* // Search within a specific range (from fanout lookup)
|
|
625
|
-
* const { start, end } = getFanoutRange(index.fanout, 0xab);
|
|
626
|
-
* const position = binarySearchObjectId(index.entries, targetSha, start, end);
|
|
627
|
-
* if (position !== -1) {
|
|
628
|
-
* const entry = index.entries[position];
|
|
629
|
-
* }
|
|
630
|
-
*/
|
|
631
|
-
export function binarySearchObjectId(entries, objectId, start, end) {
|
|
632
|
-
let lo = start;
|
|
633
|
-
let hi = end;
|
|
634
|
-
while (lo < hi) {
|
|
635
|
-
const mid = (lo + hi) >>> 1;
|
|
636
|
-
const cmp = getEntryObjectId(entries[mid]).localeCompare(objectId);
|
|
637
|
-
if (cmp < 0) {
|
|
638
|
-
lo = mid + 1;
|
|
639
|
-
}
|
|
640
|
-
else if (cmp > 0) {
|
|
641
|
-
hi = mid;
|
|
642
|
-
}
|
|
643
|
-
else {
|
|
644
|
-
return mid;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return -1;
|
|
648
|
-
}
|
|
649
|
-
/**
|
|
650
|
-
* Alias for {@link binarySearchObjectId} for backward compatibility.
|
|
651
|
-
* @deprecated Use binarySearchObjectId instead
|
|
652
|
-
*/
|
|
653
|
-
export const binarySearchSha = binarySearchObjectId;
|
|
654
|
-
/**
|
|
655
|
-
* Serializes a PackIndex structure to binary .idx format.
|
|
656
|
-
*
|
|
657
|
-
* @description Converts a structured PackIndex object into the binary format
|
|
658
|
-
* used by Git for .idx files. This is the inverse of {@link parsePackIndex}.
|
|
659
|
-
*
|
|
660
|
-
* **Output Structure:**
|
|
661
|
-
* 1. Magic number (4 bytes)
|
|
662
|
-
* 2. Version (4 bytes)
|
|
663
|
-
* 3. Fanout table (1024 bytes)
|
|
664
|
-
* 4. Object IDs sorted (N * 20 bytes)
|
|
665
|
-
* 5. CRC32 values (N * 4 bytes)
|
|
666
|
-
* 6. 4-byte offsets (N * 4 bytes)
|
|
667
|
-
* 7. 8-byte large offsets if needed
|
|
668
|
-
* 8. Pack checksum (20 bytes)
|
|
669
|
-
* 9. Index checksum (20 bytes) - computed during serialization
|
|
670
|
-
*
|
|
671
|
-
* @param {PackIndex} index - The pack index to serialize
|
|
672
|
-
* @returns {Uint8Array} Complete .idx file as binary data
|
|
673
|
-
*
|
|
674
|
-
* @example
|
|
675
|
-
* // Serialize an index after modifications
|
|
676
|
-
* const index = parsePackIndex(originalData);
|
|
677
|
-
* // ... modify index ...
|
|
678
|
-
* const newData = serializePackIndex(index);
|
|
679
|
-
*/
|
|
680
|
-
export function serializePackIndex(index) {
|
|
681
|
-
const { fanout, entries, packChecksum } = index;
|
|
682
|
-
const objectCount = entries.length;
|
|
683
|
-
// Count large offsets (>= LARGE_OFFSET_THRESHOLD)
|
|
684
|
-
let largeOffsetCount = 0;
|
|
685
|
-
for (const entry of entries) {
|
|
686
|
-
if (entry.offset >= LARGE_OFFSET_THRESHOLD) {
|
|
687
|
-
largeOffsetCount++;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
// Calculate total size:
|
|
691
|
-
// header: 8 bytes (magic + version)
|
|
692
|
-
// fanout: 256 * 4 = 1024 bytes
|
|
693
|
-
// SHA list: objectCount * 20 bytes
|
|
694
|
-
// CRC32 list: objectCount * 4 bytes
|
|
695
|
-
// Offset list: objectCount * 4 bytes
|
|
696
|
-
// Large offsets: largeOffsetCount * 8 bytes
|
|
697
|
-
// Pack checksum: 20 bytes
|
|
698
|
-
// Index checksum: 20 bytes
|
|
699
|
-
const totalSize = 8 + 256 * 4 + objectCount * 20 + objectCount * 4 + objectCount * 4 + largeOffsetCount * 8 + 40;
|
|
700
|
-
const data = new Uint8Array(totalSize);
|
|
701
|
-
const view = new DataView(data.buffer);
|
|
702
|
-
let offset = 0;
|
|
703
|
-
// Write magic number (big-endian)
|
|
704
|
-
view.setUint32(offset, PACK_INDEX_MAGIC, false);
|
|
705
|
-
offset += 4;
|
|
706
|
-
// Write version (big-endian)
|
|
707
|
-
view.setUint32(offset, PACK_INDEX_VERSION, false);
|
|
708
|
-
offset += 4;
|
|
709
|
-
// Write fanout table (big-endian)
|
|
710
|
-
for (let i = 0; i < 256; i++) {
|
|
711
|
-
view.setUint32(offset, fanout[i], false);
|
|
712
|
-
offset += 4;
|
|
713
|
-
}
|
|
714
|
-
// Write SHA-1 object IDs (sorted)
|
|
715
|
-
for (const entry of entries) {
|
|
716
|
-
const shaBytes = hexToBytes(getEntryObjectId(entry));
|
|
717
|
-
data.set(shaBytes, offset);
|
|
718
|
-
offset += 20;
|
|
719
|
-
}
|
|
720
|
-
// Write CRC32 values (big-endian)
|
|
721
|
-
for (const entry of entries) {
|
|
722
|
-
view.setUint32(offset, entry.crc32, false);
|
|
723
|
-
offset += 4;
|
|
724
|
-
}
|
|
725
|
-
// Write 4-byte offsets (big-endian)
|
|
726
|
-
// For large offsets, write MSB set + index into large offset table
|
|
727
|
-
let largeOffsetIndex = 0;
|
|
728
|
-
const largeOffsetValues = [];
|
|
729
|
-
for (const entry of entries) {
|
|
730
|
-
if (entry.offset >= LARGE_OFFSET_THRESHOLD) {
|
|
731
|
-
// Large offset: MSB set + index into large offset table
|
|
732
|
-
view.setUint32(offset, 0x80000000 | largeOffsetIndex, false);
|
|
733
|
-
largeOffsetValues.push(entry.offset);
|
|
734
|
-
largeOffsetIndex++;
|
|
735
|
-
}
|
|
736
|
-
else {
|
|
737
|
-
// Small offset: just the 4-byte value
|
|
738
|
-
view.setUint32(offset, entry.offset, false);
|
|
739
|
-
}
|
|
740
|
-
offset += 4;
|
|
741
|
-
}
|
|
742
|
-
// Write 8-byte large offsets (big-endian)
|
|
743
|
-
for (const largeOffset of largeOffsetValues) {
|
|
744
|
-
// Write as 64-bit big-endian
|
|
745
|
-
const highBits = Math.floor(largeOffset / 0x100000000);
|
|
746
|
-
const lowBits = largeOffset % 0x100000000;
|
|
747
|
-
view.setUint32(offset, highBits, false);
|
|
748
|
-
view.setUint32(offset + 4, lowBits, false);
|
|
749
|
-
offset += 8;
|
|
750
|
-
}
|
|
751
|
-
// Write pack checksum
|
|
752
|
-
data.set(packChecksum, offset);
|
|
753
|
-
offset += 20;
|
|
754
|
-
// Compute and write index checksum (SHA-1 of everything before it)
|
|
755
|
-
const dataToHash = data.subarray(0, offset);
|
|
756
|
-
const indexChecksum = sha1(dataToHash);
|
|
757
|
-
data.set(indexChecksum, offset);
|
|
758
|
-
return data;
|
|
759
|
-
}
|
|
760
|
-
/**
|
|
761
|
-
* Parses the 256-entry fanout table from pack index data.
|
|
762
|
-
*
|
|
763
|
-
* @description The fanout table is a core data structure that enables O(1)
|
|
764
|
-
* range lookup for binary search. Each entry stores the cumulative count
|
|
765
|
-
* of objects whose first SHA byte is <= the entry index.
|
|
766
|
-
*
|
|
767
|
-
* **Table Properties:**
|
|
768
|
-
* - 256 entries (one per possible first byte value)
|
|
769
|
-
* - Each entry is 4 bytes, big-endian
|
|
770
|
-
* - Values must be monotonically non-decreasing
|
|
771
|
-
* - fanout[255] = total object count
|
|
772
|
-
*
|
|
773
|
-
* @param {Uint8Array} data - Raw bytes starting at the fanout table (1024 bytes minimum)
|
|
774
|
-
* @returns {Uint32Array} 256-entry fanout table
|
|
775
|
-
*
|
|
776
|
-
* @example
|
|
777
|
-
* // Parse fanout from index data
|
|
778
|
-
* const fanoutData = indexData.subarray(8, 8 + 256 * 4);
|
|
779
|
-
* const fanout = parseFanoutTable(fanoutData);
|
|
780
|
-
* const totalObjects = fanout[255];
|
|
781
|
-
*/
|
|
782
|
-
export function parseFanoutTable(data) {
|
|
783
|
-
const fanout = new Uint32Array(256);
|
|
784
|
-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
785
|
-
for (let i = 0; i < 256; i++) {
|
|
786
|
-
fanout[i] = view.getUint32(i * 4, false); // big-endian
|
|
787
|
-
}
|
|
788
|
-
return fanout;
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Reads a pack file offset from the index, handling both small and large offsets.
|
|
792
|
-
*
|
|
793
|
-
* @description Pack index offsets use a special encoding to support files > 2GB:
|
|
794
|
-
* - If MSB is clear: the 4-byte value is the direct offset
|
|
795
|
-
* - If MSB is set: the lower 31 bits are an index into the large offset table
|
|
796
|
-
*
|
|
797
|
-
* The large offset table stores 8-byte offsets for objects beyond the 2GB boundary.
|
|
798
|
-
*
|
|
799
|
-
* @param {Uint8Array} data - 4 bytes containing the offset or large offset index
|
|
800
|
-
* @param {Uint8Array} [largeOffsets] - The 8-byte large offset table (required for offsets > 2GB)
|
|
801
|
-
* @returns {number} The actual byte offset in the packfile
|
|
802
|
-
* @throws {Error} If a large offset is indicated but largeOffsets is not provided
|
|
803
|
-
* @throws {Error} If the large offset index is out of bounds
|
|
804
|
-
*
|
|
805
|
-
* @example
|
|
806
|
-
* // Read offset for an entry
|
|
807
|
-
* const offsetData = indexData.subarray(offsetsStart + i * 4, offsetsStart + (i + 1) * 4);
|
|
808
|
-
* const offset = readPackOffset(offsetData, largeOffsetsTable);
|
|
809
|
-
*/
|
|
810
|
-
export function readPackOffset(data, largeOffsets) {
|
|
811
|
-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
812
|
-
const value = view.getUint32(0, false); // big-endian
|
|
813
|
-
// Check if MSB is set (large offset indicator)
|
|
814
|
-
if (value & 0x80000000) {
|
|
815
|
-
// Lower 31 bits are the index into the large offset table
|
|
816
|
-
const index = value & 0x7fffffff;
|
|
817
|
-
if (!largeOffsets) {
|
|
818
|
-
throw new Error('Large offset table missing but required');
|
|
819
|
-
}
|
|
820
|
-
// Each large offset is 8 bytes
|
|
821
|
-
const largeOffsetByteIndex = index * 8;
|
|
822
|
-
if (largeOffsetByteIndex + 8 > largeOffsets.length) {
|
|
823
|
-
throw new Error(`Large offset index ${index} out of bounds`);
|
|
824
|
-
}
|
|
825
|
-
const largeView = new DataView(largeOffsets.buffer, largeOffsets.byteOffset, largeOffsets.byteLength);
|
|
826
|
-
// Read 64-bit big-endian offset as a JavaScript number
|
|
827
|
-
const highBits = largeView.getUint32(largeOffsetByteIndex, false);
|
|
828
|
-
const lowBits = largeView.getUint32(largeOffsetByteIndex + 4, false);
|
|
829
|
-
return highBits * 0x100000000 + lowBits;
|
|
830
|
-
}
|
|
831
|
-
return value;
|
|
832
|
-
}
|
|
833
|
-
//# sourceMappingURL=index.js.map
|