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/refs/storage.js
DELETED
|
@@ -1,1023 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Git Reference Storage System
|
|
3
|
-
*
|
|
4
|
-
* This module provides a complete implementation of Git reference management,
|
|
5
|
-
* including branches, tags, HEAD, and symbolic refs. It supports both loose refs
|
|
6
|
-
* (individual files) and packed refs (consolidated file).
|
|
7
|
-
*
|
|
8
|
-
* **Key Concepts**:
|
|
9
|
-
* - **Direct refs**: Point directly to a SHA-1 hash (e.g., branch pointing to commit)
|
|
10
|
-
* - **Symbolic refs**: Point to another ref (e.g., HEAD -> refs/heads/main)
|
|
11
|
-
* - **Loose refs**: Individual ref files in .git/refs/
|
|
12
|
-
* - **Packed refs**: Consolidated refs in .git/packed-refs for efficiency
|
|
13
|
-
*
|
|
14
|
-
* **Backend Support**:
|
|
15
|
-
* - `RefStorageBackend`: Full-featured backend with locking and packed refs
|
|
16
|
-
* - `StorageBackend`: Simpler backend interface (optional, from storage/backend.ts)
|
|
17
|
-
*
|
|
18
|
-
* @module refs/storage
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```typescript
|
|
22
|
-
* import { RefStorage, isValidRefName, isValidSha } from './refs/storage'
|
|
23
|
-
*
|
|
24
|
-
* // Create storage with RefStorageBackend (full features)
|
|
25
|
-
* const storage = new RefStorage(backend)
|
|
26
|
-
*
|
|
27
|
-
* // Or create with StorageBackend (simpler, optional)
|
|
28
|
-
* const storage = new RefStorage({ storageBackend: myStorageBackend })
|
|
29
|
-
*
|
|
30
|
-
* // Resolve HEAD to get current commit
|
|
31
|
-
* const resolved = await storage.resolveRef('HEAD')
|
|
32
|
-
* console.log(`Current commit: ${resolved.sha}`)
|
|
33
|
-
*
|
|
34
|
-
* // Update a branch
|
|
35
|
-
* await storage.updateRef('refs/heads/feature', newCommitSha, { create: true })
|
|
36
|
-
*
|
|
37
|
-
* // List all branches
|
|
38
|
-
* const branches = await storage.listBranches()
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
/**
|
|
42
|
-
* Error thrown when a ref operation fails.
|
|
43
|
-
*
|
|
44
|
-
* @description
|
|
45
|
-
* Provides structured error information including error code
|
|
46
|
-
* and the ref name that caused the error.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```typescript
|
|
50
|
-
* try {
|
|
51
|
-
* await storage.updateRef('refs/heads/main', sha)
|
|
52
|
-
* } catch (e) {
|
|
53
|
-
* if (e instanceof RefError) {
|
|
54
|
-
* switch (e.code) {
|
|
55
|
-
* case 'NOT_FOUND': // Ref doesn't exist
|
|
56
|
-
* case 'CONFLICT': // CAS failed
|
|
57
|
-
* case 'LOCKED': // Ref is locked
|
|
58
|
-
* }
|
|
59
|
-
* }
|
|
60
|
-
* }
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
export class RefError extends Error {
|
|
64
|
-
code;
|
|
65
|
-
refName;
|
|
66
|
-
/**
|
|
67
|
-
* Create a new RefError.
|
|
68
|
-
*
|
|
69
|
-
* @param message - Human-readable error message
|
|
70
|
-
* @param code - Error code for programmatic handling
|
|
71
|
-
* @param refName - The ref that caused the error (optional)
|
|
72
|
-
*/
|
|
73
|
-
constructor(message, code, refName) {
|
|
74
|
-
super(message);
|
|
75
|
-
this.code = code;
|
|
76
|
-
this.refName = refName;
|
|
77
|
-
this.name = 'RefError';
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// Validation Functions
|
|
82
|
-
// ============================================================================
|
|
83
|
-
/**
|
|
84
|
-
* Validate a ref name according to Git rules.
|
|
85
|
-
*
|
|
86
|
-
* @description
|
|
87
|
-
* Git has specific rules for valid ref names. This function implements
|
|
88
|
-
* the validation from `git check-ref-format`.
|
|
89
|
-
*
|
|
90
|
-
* **Rules**:
|
|
91
|
-
* - Cannot be empty or just '@'
|
|
92
|
-
* - Cannot end with '/' or '.lock'
|
|
93
|
-
* - Cannot contain '..', '@{', control chars, space, ~, ^, :, ?, *, [, \
|
|
94
|
-
* - Components cannot start or end with '.'
|
|
95
|
-
* - HEAD is always valid
|
|
96
|
-
*
|
|
97
|
-
* @param name - Ref name to validate
|
|
98
|
-
* @returns True if the name is valid
|
|
99
|
-
*
|
|
100
|
-
* @see https://git-scm.com/docs/git-check-ref-format
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* ```typescript
|
|
104
|
-
* isValidRefName('refs/heads/main') // true
|
|
105
|
-
* isValidRefName('refs/heads/feature/x') // true
|
|
106
|
-
* isValidRefName('HEAD') // true
|
|
107
|
-
* isValidRefName('refs/heads/../main') // false (contains ..)
|
|
108
|
-
* isValidRefName('refs/heads/.hidden') // false (component starts with .)
|
|
109
|
-
* isValidRefName('refs/heads/foo.lock') // false (ends with .lock)
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
export function isValidRefName(name) {
|
|
113
|
-
// HEAD is always valid
|
|
114
|
-
if (name === 'HEAD') {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
// Just @ is invalid
|
|
118
|
-
if (name === '@') {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
// Cannot be empty
|
|
122
|
-
if (!name || name.length === 0) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
// Cannot end with /
|
|
126
|
-
if (name.endsWith('/')) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
// Cannot end with .lock
|
|
130
|
-
if (name.endsWith('.lock')) {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
// Cannot contain @{
|
|
134
|
-
if (name.includes('@{')) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
// Cannot contain ..
|
|
138
|
-
if (name.includes('..')) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
// Cannot contain control characters (ASCII 0-31), space, ~, ^, :, ?, *, [, \
|
|
142
|
-
const invalidChars = /[\x00-\x1f\x7f ~^:?*[\]\\]/;
|
|
143
|
-
if (invalidChars.test(name)) {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
// Split into components and check each
|
|
147
|
-
const components = name.split('/');
|
|
148
|
-
for (const component of components) {
|
|
149
|
-
// Cannot have empty components (// in path)
|
|
150
|
-
if (component.length === 0) {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
// Cannot start with .
|
|
154
|
-
if (component.startsWith('.')) {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
// Cannot end with .
|
|
158
|
-
if (component.endsWith('.')) {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Validate a SHA-1 hash string.
|
|
166
|
-
*
|
|
167
|
-
* @description
|
|
168
|
-
* SHA-1 hashes must be exactly 40 hexadecimal characters.
|
|
169
|
-
* This validates the format, not whether the object exists.
|
|
170
|
-
*
|
|
171
|
-
* @param sha - SHA string to validate
|
|
172
|
-
* @returns True if the string is a valid SHA-1 format
|
|
173
|
-
*
|
|
174
|
-
* @example
|
|
175
|
-
* ```typescript
|
|
176
|
-
* isValidSha('abc123def456789...') // true (if 40 hex chars)
|
|
177
|
-
* isValidSha('abc123') // false (too short)
|
|
178
|
-
* isValidSha('xyz...') // false (invalid hex)
|
|
179
|
-
* isValidSha(null) // false
|
|
180
|
-
* ```
|
|
181
|
-
*/
|
|
182
|
-
export function isValidSha(sha) {
|
|
183
|
-
// Must be exactly 40 characters
|
|
184
|
-
if (!sha || sha.length !== 40) {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
// Must be valid hex
|
|
188
|
-
return /^[0-9a-fA-F]{40}$/.test(sha);
|
|
189
|
-
}
|
|
190
|
-
// ============================================================================
|
|
191
|
-
// Parsing and Serialization Functions
|
|
192
|
-
// ============================================================================
|
|
193
|
-
/**
|
|
194
|
-
* Parse ref file content into type and target.
|
|
195
|
-
*
|
|
196
|
-
* @description
|
|
197
|
-
* Ref files either contain a SHA directly or "ref: <target>" for symbolic refs.
|
|
198
|
-
*
|
|
199
|
-
* @param content - Raw ref file content
|
|
200
|
-
* @returns Parsed type and target
|
|
201
|
-
*
|
|
202
|
-
* @example
|
|
203
|
-
* ```typescript
|
|
204
|
-
* // Direct ref
|
|
205
|
-
* parseRefContent('abc123def456...\n')
|
|
206
|
-
* // => { type: 'direct', target: 'abc123def456...' }
|
|
207
|
-
*
|
|
208
|
-
* // Symbolic ref
|
|
209
|
-
* parseRefContent('ref: refs/heads/main\n')
|
|
210
|
-
* // => { type: 'symbolic', target: 'refs/heads/main' }
|
|
211
|
-
* ```
|
|
212
|
-
*/
|
|
213
|
-
export function parseRefContent(content) {
|
|
214
|
-
const trimmed = content.trim();
|
|
215
|
-
// Check for symbolic ref (starts with "ref:")
|
|
216
|
-
if (trimmed.startsWith('ref:')) {
|
|
217
|
-
const target = trimmed.slice(4).trim();
|
|
218
|
-
return { type: 'symbolic', target };
|
|
219
|
-
}
|
|
220
|
-
// Otherwise it's a direct ref (SHA)
|
|
221
|
-
return { type: 'direct', target: trimmed };
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Serialize a ref to file content format.
|
|
225
|
-
*
|
|
226
|
-
* @description
|
|
227
|
-
* Converts a Ref object to the string format stored in ref files.
|
|
228
|
-
*
|
|
229
|
-
* @param ref - Ref to serialize
|
|
230
|
-
* @returns File content string (with trailing newline)
|
|
231
|
-
*
|
|
232
|
-
* @example
|
|
233
|
-
* ```typescript
|
|
234
|
-
* serializeRefContent({ name: 'HEAD', target: 'refs/heads/main', type: 'symbolic' })
|
|
235
|
-
* // => 'ref: refs/heads/main\n'
|
|
236
|
-
*
|
|
237
|
-
* serializeRefContent({ name: 'refs/heads/main', target: 'abc123...', type: 'direct' })
|
|
238
|
-
* // => 'abc123...\n'
|
|
239
|
-
* ```
|
|
240
|
-
*/
|
|
241
|
-
export function serializeRefContent(ref) {
|
|
242
|
-
if (ref.type === 'symbolic') {
|
|
243
|
-
return `ref: ${ref.target}\n`;
|
|
244
|
-
}
|
|
245
|
-
return `${ref.target}\n`;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Parse packed-refs file content.
|
|
249
|
-
*
|
|
250
|
-
* @description
|
|
251
|
-
* The packed-refs file contains multiple refs in a space-efficient format.
|
|
252
|
-
* Format: "<sha> <refname>" on each line, with optional comments (#) and
|
|
253
|
-
* peeled entries (^sha for annotated tags).
|
|
254
|
-
*
|
|
255
|
-
* @param content - Raw packed-refs file content
|
|
256
|
-
* @returns Map of ref names to SHA values
|
|
257
|
-
*
|
|
258
|
-
* @example
|
|
259
|
-
* ```typescript
|
|
260
|
-
* const content = `# pack-refs with: peeled fully-peeled sorted
|
|
261
|
-
* abc123 refs/heads/main
|
|
262
|
-
* def456 refs/tags/v1.0.0
|
|
263
|
-
* ^aaa111
|
|
264
|
-
* `
|
|
265
|
-
* const refs = parsePackedRefs(content)
|
|
266
|
-
* // Map { 'refs/heads/main' => 'abc123', 'refs/tags/v1.0.0' => 'def456' }
|
|
267
|
-
* ```
|
|
268
|
-
*/
|
|
269
|
-
export function parsePackedRefs(content) {
|
|
270
|
-
const refs = new Map();
|
|
271
|
-
if (!content) {
|
|
272
|
-
return refs;
|
|
273
|
-
}
|
|
274
|
-
const lines = content.split(/\r?\n/);
|
|
275
|
-
for (const line of lines) {
|
|
276
|
-
const trimmed = line.trim();
|
|
277
|
-
// Skip empty lines
|
|
278
|
-
if (!trimmed) {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
// Skip comment lines
|
|
282
|
-
if (trimmed.startsWith('#')) {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
// Skip peeled entries (lines starting with ^)
|
|
286
|
-
if (trimmed.startsWith('^')) {
|
|
287
|
-
continue;
|
|
288
|
-
}
|
|
289
|
-
// Parse "sha refname" format
|
|
290
|
-
const spaceIndex = trimmed.indexOf(' ');
|
|
291
|
-
if (spaceIndex > 0) {
|
|
292
|
-
const sha = trimmed.slice(0, spaceIndex);
|
|
293
|
-
const refName = trimmed.slice(spaceIndex + 1);
|
|
294
|
-
refs.set(refName, sha);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return refs;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Serialize refs to packed-refs file format.
|
|
301
|
-
*
|
|
302
|
-
* @description
|
|
303
|
-
* Creates the content for a packed-refs file from a map of refs.
|
|
304
|
-
* Refs are sorted alphabetically for consistency.
|
|
305
|
-
*
|
|
306
|
-
* @param refs - Map of ref names to SHA values
|
|
307
|
-
* @returns Packed-refs file content
|
|
308
|
-
*
|
|
309
|
-
* @example
|
|
310
|
-
* ```typescript
|
|
311
|
-
* const refs = new Map([
|
|
312
|
-
* ['refs/heads/main', 'abc123...'],
|
|
313
|
-
* ['refs/tags/v1.0.0', 'def456...']
|
|
314
|
-
* ])
|
|
315
|
-
* const content = serializePackedRefs(refs)
|
|
316
|
-
* // '# pack-refs with: peeled fully-peeled sorted\nabc123... refs/heads/main\n...'
|
|
317
|
-
* ```
|
|
318
|
-
*/
|
|
319
|
-
export function serializePackedRefs(refs) {
|
|
320
|
-
const lines = ['# pack-refs with: peeled fully-peeled sorted'];
|
|
321
|
-
// Sort refs alphabetically
|
|
322
|
-
const sortedRefs = Array.from(refs.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
323
|
-
for (const [refName, sha] of sortedRefs) {
|
|
324
|
-
lines.push(`${sha} ${refName}`);
|
|
325
|
-
}
|
|
326
|
-
return lines.join('\n') + '\n';
|
|
327
|
-
}
|
|
328
|
-
// ============================================================================
|
|
329
|
-
// RefStorage Class
|
|
330
|
-
// ============================================================================
|
|
331
|
-
/**
|
|
332
|
-
* Reference storage manager.
|
|
333
|
-
*
|
|
334
|
-
* @description
|
|
335
|
-
* Provides a high-level API for managing Git references. Handles ref
|
|
336
|
-
* resolution, updates with locking, symbolic refs, and packed refs.
|
|
337
|
-
*
|
|
338
|
-
* Supports two backend modes:
|
|
339
|
-
* 1. RefStorageBackend - Full features including locking and packed refs
|
|
340
|
-
* 2. StorageBackend - Simpler interface with basic ref operations
|
|
341
|
-
*
|
|
342
|
-
* @example
|
|
343
|
-
* ```typescript
|
|
344
|
-
* // With RefStorageBackend (full features)
|
|
345
|
-
* const storage = new RefStorage(myBackend)
|
|
346
|
-
*
|
|
347
|
-
* // With StorageBackend (simpler)
|
|
348
|
-
* const storage = new RefStorage({ storageBackend: myStorageBackend })
|
|
349
|
-
*
|
|
350
|
-
* // Get current branch
|
|
351
|
-
* const head = await storage.getHead()
|
|
352
|
-
* if (head.type === 'symbolic') {
|
|
353
|
-
* console.log(`On branch: ${head.target}`)
|
|
354
|
-
* }
|
|
355
|
-
*
|
|
356
|
-
* // Resolve to SHA
|
|
357
|
-
* const resolved = await storage.resolveRef('HEAD')
|
|
358
|
-
* console.log(`Current commit: ${resolved.sha}`)
|
|
359
|
-
*
|
|
360
|
-
* // Create a branch
|
|
361
|
-
* await storage.updateRef('refs/heads/feature', commitSha, { create: true })
|
|
362
|
-
* ```
|
|
363
|
-
*/
|
|
364
|
-
export class RefStorage {
|
|
365
|
-
backend;
|
|
366
|
-
storageBackend;
|
|
367
|
-
/**
|
|
368
|
-
* Create a new RefStorage instance.
|
|
369
|
-
*
|
|
370
|
-
* @param backendOrOptions - Either a RefStorageBackend directly, or options with storageBackend
|
|
371
|
-
*
|
|
372
|
-
* @example
|
|
373
|
-
* ```typescript
|
|
374
|
-
* // Direct backend
|
|
375
|
-
* const storage = new RefStorage(myRefStorageBackend)
|
|
376
|
-
*
|
|
377
|
-
* // Options with StorageBackend
|
|
378
|
-
* const storage = new RefStorage({ storageBackend: myStorageBackend })
|
|
379
|
-
* ```
|
|
380
|
-
*/
|
|
381
|
-
constructor(backendOrOptions) {
|
|
382
|
-
// Type guard: RefStorageOptions has storageBackend, RefStorageBackend has readRef
|
|
383
|
-
if ('storageBackend' in backendOrOptions && !('readRef' in backendOrOptions)) {
|
|
384
|
-
// Options object with StorageBackend
|
|
385
|
-
this.storageBackend = backendOrOptions.storageBackend;
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
// Direct RefStorageBackend
|
|
389
|
-
this.backend = backendOrOptions;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Internal helper to get a lock (or no-op lock for StorageBackend).
|
|
394
|
-
*
|
|
395
|
-
* @description
|
|
396
|
-
* When using StorageBackend, returns a no-op lock since StorageBackend
|
|
397
|
-
* doesn't support locking. Callers should still use try/finally to release.
|
|
398
|
-
*/
|
|
399
|
-
async getLock(name, _timeout) {
|
|
400
|
-
if (this.storageBackend) {
|
|
401
|
-
// No-op lock for StorageBackend
|
|
402
|
-
return {
|
|
403
|
-
refName: name,
|
|
404
|
-
release: async () => { },
|
|
405
|
-
isHeld: () => true
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
if (!this.backend) {
|
|
409
|
-
throw new Error('No backend configured');
|
|
410
|
-
}
|
|
411
|
-
return this.backend.acquireLock(name, _timeout);
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Internal helper to write a ref.
|
|
415
|
-
*/
|
|
416
|
-
async writeRef(ref) {
|
|
417
|
-
if (this.storageBackend) {
|
|
418
|
-
await this.storageBackend.setRef(ref.name, ref);
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
if (!this.backend) {
|
|
422
|
-
throw new Error('No backend configured');
|
|
423
|
-
}
|
|
424
|
-
await this.backend.writeRef(ref);
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Internal helper to delete a ref.
|
|
428
|
-
*/
|
|
429
|
-
async removeRef(name) {
|
|
430
|
-
if (this.storageBackend) {
|
|
431
|
-
await this.storageBackend.deleteRef(name);
|
|
432
|
-
return true;
|
|
433
|
-
}
|
|
434
|
-
if (!this.backend) {
|
|
435
|
-
throw new Error('No backend configured');
|
|
436
|
-
}
|
|
437
|
-
return this.backend.deleteRef(name);
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Internal helper to list refs.
|
|
441
|
-
*/
|
|
442
|
-
async getAllRefs(pattern) {
|
|
443
|
-
if (this.storageBackend) {
|
|
444
|
-
return this.storageBackend.listRefs(pattern);
|
|
445
|
-
}
|
|
446
|
-
if (!this.backend) {
|
|
447
|
-
throw new Error('No backend configured');
|
|
448
|
-
}
|
|
449
|
-
return this.backend.listRefs(pattern);
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Get a ref by name.
|
|
453
|
-
*
|
|
454
|
-
* @description
|
|
455
|
-
* Retrieves a ref without resolving symbolic refs.
|
|
456
|
-
* Use `resolveRef` to follow symbolic refs to their final target.
|
|
457
|
-
*
|
|
458
|
-
* @param name - Full ref name
|
|
459
|
-
* @returns The ref or null if not found
|
|
460
|
-
* @throws Error if no backend is configured
|
|
461
|
-
*
|
|
462
|
-
* @example
|
|
463
|
-
* ```typescript
|
|
464
|
-
* const head = await storage.getRef('HEAD')
|
|
465
|
-
* if (head && head.type === 'symbolic') {
|
|
466
|
-
* console.log(`HEAD points to ${head.target}`)
|
|
467
|
-
* }
|
|
468
|
-
* ```
|
|
469
|
-
*/
|
|
470
|
-
async getRef(name) {
|
|
471
|
-
// Use StorageBackend if available
|
|
472
|
-
if (this.storageBackend) {
|
|
473
|
-
return this.storageBackend.getRef(name);
|
|
474
|
-
}
|
|
475
|
-
// Fall back to RefStorageBackend
|
|
476
|
-
if (!this.backend?.readRef) {
|
|
477
|
-
throw new Error('No backend configured or backend does not support readRef');
|
|
478
|
-
}
|
|
479
|
-
return this.backend.readRef(name);
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Resolve a ref to its final SHA target.
|
|
483
|
-
*
|
|
484
|
-
* @description
|
|
485
|
-
* Follows symbolic refs until reaching a direct ref, then returns
|
|
486
|
-
* the SHA and the chain of refs followed.
|
|
487
|
-
*
|
|
488
|
-
* @param name - Ref name to resolve
|
|
489
|
-
* @param options - Resolution options (maxDepth)
|
|
490
|
-
* @returns Resolved ref with SHA and chain
|
|
491
|
-
* @throws RefError with code 'NOT_FOUND' if ref doesn't exist
|
|
492
|
-
* @throws RefError with code 'CIRCULAR_REF' if circular reference detected
|
|
493
|
-
* @throws RefError with code 'MAX_DEPTH_EXCEEDED' if too many redirects
|
|
494
|
-
*
|
|
495
|
-
* @example
|
|
496
|
-
* ```typescript
|
|
497
|
-
* const resolved = await storage.resolveRef('HEAD')
|
|
498
|
-
* console.log(`SHA: ${resolved.sha}`)
|
|
499
|
-
* console.log(`Chain: ${resolved.chain.map(r => r.name).join(' -> ')}`)
|
|
500
|
-
* // Chain: HEAD -> refs/heads/main
|
|
501
|
-
* ```
|
|
502
|
-
*/
|
|
503
|
-
async resolveRef(name, options) {
|
|
504
|
-
const maxDepth = options?.maxDepth ?? 10;
|
|
505
|
-
const chain = [];
|
|
506
|
-
const visited = new Set();
|
|
507
|
-
let currentName = name;
|
|
508
|
-
let ref = null;
|
|
509
|
-
for (let depth = 0; depth < maxDepth; depth++) {
|
|
510
|
-
// Check for circular refs
|
|
511
|
-
if (visited.has(currentName)) {
|
|
512
|
-
throw new RefError(`Circular reference detected: ${currentName}`, 'CIRCULAR_REF', currentName);
|
|
513
|
-
}
|
|
514
|
-
visited.add(currentName);
|
|
515
|
-
ref = await this.getRef(currentName);
|
|
516
|
-
if (!ref) {
|
|
517
|
-
throw new RefError(`Ref not found: ${currentName}`, 'NOT_FOUND', currentName);
|
|
518
|
-
}
|
|
519
|
-
chain.push(ref);
|
|
520
|
-
// If it's a direct ref, we're done
|
|
521
|
-
if (ref.type === 'direct') {
|
|
522
|
-
return {
|
|
523
|
-
ref: chain[0],
|
|
524
|
-
sha: ref.target,
|
|
525
|
-
chain
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
// Follow symbolic ref
|
|
529
|
-
currentName = ref.target;
|
|
530
|
-
}
|
|
531
|
-
// Max depth exceeded
|
|
532
|
-
throw new RefError(`Max ref resolution depth exceeded: ${maxDepth}`, 'MAX_DEPTH_EXCEEDED', name);
|
|
533
|
-
}
|
|
534
|
-
/**
|
|
535
|
-
* Update or create a ref.
|
|
536
|
-
*
|
|
537
|
-
* @description
|
|
538
|
-
* Creates a new ref or updates an existing one. Supports atomic
|
|
539
|
-
* compare-and-swap operations via oldValue option.
|
|
540
|
-
*
|
|
541
|
-
* Note: For atomic operations, callers can acquire a lock via acquireLock()
|
|
542
|
-
* and pass it via options.lock to avoid double-locking.
|
|
543
|
-
*
|
|
544
|
-
* @param name - Full ref name
|
|
545
|
-
* @param target - SHA-1 hash to point to
|
|
546
|
-
* @param options - Update options (create, oldValue, force, lock)
|
|
547
|
-
* @returns The updated/created ref
|
|
548
|
-
* @throws RefError with code 'INVALID_NAME' if ref name is invalid
|
|
549
|
-
* @throws RefError with code 'INVALID_SHA' if SHA format is invalid
|
|
550
|
-
* @throws RefError with code 'ALREADY_EXISTS' if creating and ref exists
|
|
551
|
-
* @throws RefError with code 'CONFLICT' if oldValue doesn't match
|
|
552
|
-
* @throws RefError with code 'NOT_FOUND' if ref doesn't exist and not creating
|
|
553
|
-
*
|
|
554
|
-
* @example
|
|
555
|
-
* ```typescript
|
|
556
|
-
* // Create a new branch
|
|
557
|
-
* await storage.updateRef('refs/heads/feature', sha, { create: true })
|
|
558
|
-
*
|
|
559
|
-
* // Atomic update (fails if someone else modified)
|
|
560
|
-
* await storage.updateRef('refs/heads/main', newSha, { oldValue: currentSha })
|
|
561
|
-
*
|
|
562
|
-
* // Force update (skips fast-forward check)
|
|
563
|
-
* await storage.updateRef('refs/heads/main', sha, { force: true })
|
|
564
|
-
* ```
|
|
565
|
-
*/
|
|
566
|
-
async updateRef(name, target, options) {
|
|
567
|
-
// Validate ref name
|
|
568
|
-
if (!isValidRefName(name)) {
|
|
569
|
-
throw new RefError(`Invalid ref name: ${name}`, 'INVALID_NAME', name);
|
|
570
|
-
}
|
|
571
|
-
// Validate SHA
|
|
572
|
-
if (!isValidSha(target)) {
|
|
573
|
-
throw new RefError(`Invalid SHA: ${target}`, 'INVALID_SHA', name);
|
|
574
|
-
}
|
|
575
|
-
// Use provided lock or acquire a new one
|
|
576
|
-
const externalLock = options?.lock;
|
|
577
|
-
const lock = externalLock ?? await this.getLock(name);
|
|
578
|
-
try {
|
|
579
|
-
const existingRef = await this.getRef(name);
|
|
580
|
-
// Handle oldValue check (CAS - compare and swap)
|
|
581
|
-
if (options?.oldValue !== undefined) {
|
|
582
|
-
if (options.oldValue === null) {
|
|
583
|
-
// Expect ref to NOT exist
|
|
584
|
-
if (existingRef) {
|
|
585
|
-
throw new RefError(`Ref already exists: ${name}`, 'ALREADY_EXISTS', name);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
else {
|
|
589
|
-
// Expect ref to have specific value
|
|
590
|
-
if (!existingRef || existingRef.target !== options.oldValue) {
|
|
591
|
-
throw new RefError(`Ref value mismatch: ${name}`, 'CONFLICT', name);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
else if (!options?.force && !options?.create && !existingRef) {
|
|
596
|
-
// If not forcing and not creating, ref must exist
|
|
597
|
-
throw new RefError(`Ref not found: ${name}`, 'NOT_FOUND', name);
|
|
598
|
-
}
|
|
599
|
-
const ref = {
|
|
600
|
-
name,
|
|
601
|
-
target,
|
|
602
|
-
type: 'direct'
|
|
603
|
-
};
|
|
604
|
-
await this.writeRef(ref);
|
|
605
|
-
return ref;
|
|
606
|
-
}
|
|
607
|
-
finally {
|
|
608
|
-
// Only release lock if we acquired it ourselves
|
|
609
|
-
if (!externalLock) {
|
|
610
|
-
await lock.release();
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* Delete a ref.
|
|
616
|
-
*
|
|
617
|
-
* @description
|
|
618
|
-
* Removes a ref from storage. HEAD cannot be deleted.
|
|
619
|
-
* Uses locking for atomic compare-and-swap operations when oldValue is specified.
|
|
620
|
-
*
|
|
621
|
-
* @param name - Full ref name to delete
|
|
622
|
-
* @param options - Delete options (oldValue for CAS)
|
|
623
|
-
* @returns True if deleted, false if ref didn't exist
|
|
624
|
-
* @throws RefError with code 'INVALID_NAME' for HEAD or invalid names
|
|
625
|
-
* @throws RefError with code 'CONFLICT' if oldValue doesn't match
|
|
626
|
-
*
|
|
627
|
-
* @example
|
|
628
|
-
* ```typescript
|
|
629
|
-
* // Simple delete
|
|
630
|
-
* const deleted = await storage.deleteRef('refs/heads/old-branch')
|
|
631
|
-
*
|
|
632
|
-
* // Atomic delete (only if value matches)
|
|
633
|
-
* await storage.deleteRef('refs/heads/feature', { oldValue: expectedSha })
|
|
634
|
-
* ```
|
|
635
|
-
*/
|
|
636
|
-
async deleteRef(name, options) {
|
|
637
|
-
// Cannot delete HEAD
|
|
638
|
-
if (name === 'HEAD') {
|
|
639
|
-
throw new RefError('Cannot delete HEAD', 'INVALID_NAME', name);
|
|
640
|
-
}
|
|
641
|
-
// Validate ref name
|
|
642
|
-
if (!isValidRefName(name)) {
|
|
643
|
-
throw new RefError(`Invalid ref name: ${name}`, 'INVALID_NAME', name);
|
|
644
|
-
}
|
|
645
|
-
// Acquire lock for atomic operation
|
|
646
|
-
const lock = await this.getLock(name);
|
|
647
|
-
try {
|
|
648
|
-
const existingRef = await this.getRef(name);
|
|
649
|
-
// Check oldValue if provided (compare-and-swap pattern)
|
|
650
|
-
if (options?.oldValue !== undefined && options.oldValue !== null) {
|
|
651
|
-
if (!existingRef || existingRef.target !== options.oldValue) {
|
|
652
|
-
throw new RefError(`Ref value mismatch: ${name}`, 'CONFLICT', name);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
if (!existingRef) {
|
|
656
|
-
return false;
|
|
657
|
-
}
|
|
658
|
-
return this.removeRef(name);
|
|
659
|
-
}
|
|
660
|
-
finally {
|
|
661
|
-
await lock.release();
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* List refs matching a pattern.
|
|
666
|
-
*
|
|
667
|
-
* @description
|
|
668
|
-
* Returns refs filtered by pattern and options.
|
|
669
|
-
* By default, excludes HEAD and symbolic refs.
|
|
670
|
-
*
|
|
671
|
-
* @param options - Listing options (pattern, includeHead, includeSymbolic)
|
|
672
|
-
* @returns Array of matching refs
|
|
673
|
-
*
|
|
674
|
-
* @example
|
|
675
|
-
* ```typescript
|
|
676
|
-
* // List all refs
|
|
677
|
-
* const all = await storage.listRefs()
|
|
678
|
-
*
|
|
679
|
-
* // List branches only
|
|
680
|
-
* const branches = await storage.listRefs({ pattern: 'refs/heads/*' })
|
|
681
|
-
*
|
|
682
|
-
* // Include HEAD
|
|
683
|
-
* const withHead = await storage.listRefs({ includeHead: true })
|
|
684
|
-
* ```
|
|
685
|
-
*/
|
|
686
|
-
async listRefs(options) {
|
|
687
|
-
let refs = await this.getAllRefs(options?.pattern);
|
|
688
|
-
// Filter out HEAD unless explicitly requested
|
|
689
|
-
if (!options?.includeHead) {
|
|
690
|
-
refs = refs.filter(r => r.name !== 'HEAD');
|
|
691
|
-
}
|
|
692
|
-
else {
|
|
693
|
-
// If includeHead is true, make sure HEAD is in the list
|
|
694
|
-
const hasHead = refs.some(r => r.name === 'HEAD');
|
|
695
|
-
if (!hasHead) {
|
|
696
|
-
const head = await this.getRef('HEAD');
|
|
697
|
-
if (head) {
|
|
698
|
-
refs = [head, ...refs];
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
// Filter symbolic refs unless requested
|
|
703
|
-
// Note: Always keep HEAD if includeHead is true, regardless of includeSymbolic
|
|
704
|
-
if (!options?.includeSymbolic) {
|
|
705
|
-
refs = refs.filter(r => r.type !== 'symbolic' || (options?.includeHead && r.name === 'HEAD'));
|
|
706
|
-
}
|
|
707
|
-
return refs;
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* List all branches.
|
|
711
|
-
*
|
|
712
|
-
* @description
|
|
713
|
-
* Convenience method to list refs under refs/heads/.
|
|
714
|
-
*
|
|
715
|
-
* @returns Array of branch refs
|
|
716
|
-
*
|
|
717
|
-
* @example
|
|
718
|
-
* ```typescript
|
|
719
|
-
* const branches = await storage.listBranches()
|
|
720
|
-
* for (const branch of branches) {
|
|
721
|
-
* console.log(branch.name.replace('refs/heads/', ''))
|
|
722
|
-
* }
|
|
723
|
-
* ```
|
|
724
|
-
*/
|
|
725
|
-
async listBranches() {
|
|
726
|
-
return this.listRefs({ pattern: 'refs/heads/*' });
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* List all tags.
|
|
730
|
-
*
|
|
731
|
-
* @description
|
|
732
|
-
* Convenience method to list refs under refs/tags/.
|
|
733
|
-
*
|
|
734
|
-
* @returns Array of tag refs
|
|
735
|
-
*
|
|
736
|
-
* @example
|
|
737
|
-
* ```typescript
|
|
738
|
-
* const tags = await storage.listTags()
|
|
739
|
-
* for (const tag of tags) {
|
|
740
|
-
* console.log(tag.name.replace('refs/tags/', ''))
|
|
741
|
-
* }
|
|
742
|
-
* ```
|
|
743
|
-
*/
|
|
744
|
-
async listTags() {
|
|
745
|
-
return this.listRefs({ pattern: 'refs/tags/*' });
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* Get HEAD ref.
|
|
749
|
-
*
|
|
750
|
-
* @description
|
|
751
|
-
* Returns the HEAD ref. Every repository should have HEAD.
|
|
752
|
-
*
|
|
753
|
-
* @returns The HEAD ref
|
|
754
|
-
* @throws RefError with code 'NOT_FOUND' if HEAD doesn't exist
|
|
755
|
-
*
|
|
756
|
-
* @example
|
|
757
|
-
* ```typescript
|
|
758
|
-
* const head = await storage.getHead()
|
|
759
|
-
* if (head.type === 'symbolic') {
|
|
760
|
-
* console.log(`On branch: ${head.target}`)
|
|
761
|
-
* } else {
|
|
762
|
-
* console.log(`Detached at: ${head.target}`)
|
|
763
|
-
* }
|
|
764
|
-
* ```
|
|
765
|
-
*/
|
|
766
|
-
async getHead() {
|
|
767
|
-
const head = await this.getRef('HEAD');
|
|
768
|
-
if (!head) {
|
|
769
|
-
throw new RefError('HEAD not found', 'NOT_FOUND', 'HEAD');
|
|
770
|
-
}
|
|
771
|
-
return head;
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* Update HEAD (can be symbolic or detached).
|
|
775
|
-
*
|
|
776
|
-
* @description
|
|
777
|
-
* Sets HEAD to point to a branch (symbolic) or commit (detached).
|
|
778
|
-
* Uses locking to ensure atomic updates to HEAD.
|
|
779
|
-
*
|
|
780
|
-
* @param target - Branch ref name (symbolic) or SHA (detached)
|
|
781
|
-
* @param symbolic - If true, create symbolic ref; if false, direct ref
|
|
782
|
-
* @returns The updated HEAD ref
|
|
783
|
-
*
|
|
784
|
-
* @example
|
|
785
|
-
* ```typescript
|
|
786
|
-
* // Switch to branch
|
|
787
|
-
* await storage.updateHead('refs/heads/main', true)
|
|
788
|
-
*
|
|
789
|
-
* // Detach HEAD at commit
|
|
790
|
-
* await storage.updateHead(commitSha, false)
|
|
791
|
-
* ```
|
|
792
|
-
*/
|
|
793
|
-
async updateHead(target, symbolic) {
|
|
794
|
-
// Acquire lock for atomic HEAD update
|
|
795
|
-
const lock = await this.getLock('HEAD');
|
|
796
|
-
try {
|
|
797
|
-
const ref = {
|
|
798
|
-
name: 'HEAD',
|
|
799
|
-
target,
|
|
800
|
-
type: symbolic ? 'symbolic' : 'direct'
|
|
801
|
-
};
|
|
802
|
-
await this.writeRef(ref);
|
|
803
|
-
return ref;
|
|
804
|
-
}
|
|
805
|
-
finally {
|
|
806
|
-
await lock.release();
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
/**
|
|
810
|
-
* Check if HEAD is detached.
|
|
811
|
-
*
|
|
812
|
-
* @description
|
|
813
|
-
* HEAD is detached when it points directly to a commit SHA
|
|
814
|
-
* rather than symbolically to a branch.
|
|
815
|
-
*
|
|
816
|
-
* @returns True if HEAD is detached (points to SHA directly)
|
|
817
|
-
*
|
|
818
|
-
* @example
|
|
819
|
-
* ```typescript
|
|
820
|
-
* if (await storage.isHeadDetached()) {
|
|
821
|
-
* console.log('You are in detached HEAD state')
|
|
822
|
-
* }
|
|
823
|
-
* ```
|
|
824
|
-
*/
|
|
825
|
-
async isHeadDetached() {
|
|
826
|
-
const head = await this.getHead();
|
|
827
|
-
return head.type === 'direct';
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Create a symbolic ref.
|
|
831
|
-
*
|
|
832
|
-
* @description
|
|
833
|
-
* Creates a ref that points to another ref name (not a SHA).
|
|
834
|
-
* Used primarily for HEAD pointing to a branch.
|
|
835
|
-
* Uses locking to ensure atomic creation.
|
|
836
|
-
*
|
|
837
|
-
* @param name - Name for the new symbolic ref
|
|
838
|
-
* @param target - Target ref name (not SHA)
|
|
839
|
-
* @returns The created symbolic ref
|
|
840
|
-
* @throws RefError with code 'INVALID_NAME' if name is invalid
|
|
841
|
-
* @throws RefError with code 'CIRCULAR_REF' if name equals target
|
|
842
|
-
*
|
|
843
|
-
* @example
|
|
844
|
-
* ```typescript
|
|
845
|
-
* // Make HEAD point to main branch
|
|
846
|
-
* await storage.createSymbolicRef('HEAD', 'refs/heads/main')
|
|
847
|
-
* ```
|
|
848
|
-
*/
|
|
849
|
-
async createSymbolicRef(name, target) {
|
|
850
|
-
// Validate ref name
|
|
851
|
-
if (!isValidRefName(name)) {
|
|
852
|
-
throw new RefError(`Invalid ref name: ${name}`, 'INVALID_NAME', name);
|
|
853
|
-
}
|
|
854
|
-
// Cannot point to itself
|
|
855
|
-
if (name === target) {
|
|
856
|
-
throw new RefError(`Symbolic ref cannot point to itself: ${name}`, 'CIRCULAR_REF', name);
|
|
857
|
-
}
|
|
858
|
-
// Acquire lock for atomic symbolic ref creation
|
|
859
|
-
const lock = await this.getLock(name);
|
|
860
|
-
try {
|
|
861
|
-
const ref = {
|
|
862
|
-
name,
|
|
863
|
-
target,
|
|
864
|
-
type: 'symbolic'
|
|
865
|
-
};
|
|
866
|
-
await this.writeRef(ref);
|
|
867
|
-
return ref;
|
|
868
|
-
}
|
|
869
|
-
finally {
|
|
870
|
-
await lock.release();
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
/**
|
|
874
|
-
* Acquire a lock for updating a ref.
|
|
875
|
-
*
|
|
876
|
-
* @description
|
|
877
|
-
* Acquires an exclusive lock on a ref. Use this for complex operations
|
|
878
|
-
* that need to read-modify-write atomically.
|
|
879
|
-
*
|
|
880
|
-
* @param name - Full ref name to lock
|
|
881
|
-
* @param timeout - Lock acquisition timeout in milliseconds
|
|
882
|
-
* @returns Lock handle - must be released when done
|
|
883
|
-
*
|
|
884
|
-
* @example
|
|
885
|
-
* ```typescript
|
|
886
|
-
* const lock = await storage.acquireLock('refs/heads/main', 5000)
|
|
887
|
-
* try {
|
|
888
|
-
* // Perform atomic operations
|
|
889
|
-
* await storage.updateRef('refs/heads/main', sha, { lock })
|
|
890
|
-
* } finally {
|
|
891
|
-
* await lock.release()
|
|
892
|
-
* }
|
|
893
|
-
* ```
|
|
894
|
-
*/
|
|
895
|
-
async acquireLock(name, timeout) {
|
|
896
|
-
return this.getLock(name, timeout);
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
|
-
* Pack loose refs into packed-refs file.
|
|
900
|
-
*
|
|
901
|
-
* @description
|
|
902
|
-
* Consolidates loose ref files into a single packed-refs file.
|
|
903
|
-
* This improves performance for repositories with many refs.
|
|
904
|
-
* HEAD and symbolic refs are not packed.
|
|
905
|
-
*
|
|
906
|
-
* Uses a transactional approach by acquiring locks on all refs being packed
|
|
907
|
-
* to ensure consistency during the packing operation.
|
|
908
|
-
*
|
|
909
|
-
* @example
|
|
910
|
-
* ```typescript
|
|
911
|
-
* // After creating many branches/tags
|
|
912
|
-
* await storage.packRefs()
|
|
913
|
-
* ```
|
|
914
|
-
*/
|
|
915
|
-
async packRefs() {
|
|
916
|
-
// StorageBackend doesn't support packed refs - no-op
|
|
917
|
-
if (this.storageBackend) {
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
if (!this.backend) {
|
|
921
|
-
throw new Error('No backend configured');
|
|
922
|
-
}
|
|
923
|
-
const allRefs = await this.getAllRefs();
|
|
924
|
-
const packed = new Map();
|
|
925
|
-
const locks = [];
|
|
926
|
-
// Filter refs that can be packed (not HEAD, not symbolic)
|
|
927
|
-
const packableRefs = allRefs.filter(ref => {
|
|
928
|
-
if (ref.name === 'HEAD')
|
|
929
|
-
return false;
|
|
930
|
-
if (ref.type === 'symbolic')
|
|
931
|
-
return false;
|
|
932
|
-
return true;
|
|
933
|
-
});
|
|
934
|
-
// Acquire locks on all refs being packed for transactional consistency
|
|
935
|
-
try {
|
|
936
|
-
for (const ref of packableRefs) {
|
|
937
|
-
const lock = await this.getLock(ref.name);
|
|
938
|
-
locks.push(lock);
|
|
939
|
-
}
|
|
940
|
-
// Re-read refs while holding locks to ensure consistency
|
|
941
|
-
for (const ref of packableRefs) {
|
|
942
|
-
const currentRef = await this.getRef(ref.name);
|
|
943
|
-
if (currentRef && currentRef.type === 'direct') {
|
|
944
|
-
packed.set(currentRef.name, currentRef.target);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
// Write packed refs atomically
|
|
948
|
-
await this.backend.writePackedRefs(packed);
|
|
949
|
-
}
|
|
950
|
-
finally {
|
|
951
|
-
// Release all locks
|
|
952
|
-
for (const lock of locks) {
|
|
953
|
-
await lock.release();
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
// ============================================================================
|
|
959
|
-
// Convenience Functions
|
|
960
|
-
// ============================================================================
|
|
961
|
-
/**
|
|
962
|
-
* Resolve a ref to its final SHA target.
|
|
963
|
-
*
|
|
964
|
-
* @description
|
|
965
|
-
* Convenience function that wraps RefStorage.resolveRef.
|
|
966
|
-
*
|
|
967
|
-
* @param storage - RefStorage instance
|
|
968
|
-
* @param name - Ref name to resolve
|
|
969
|
-
* @param options - Resolution options
|
|
970
|
-
* @returns The final SHA target
|
|
971
|
-
*
|
|
972
|
-
* @example
|
|
973
|
-
* ```typescript
|
|
974
|
-
* const sha = await resolveRef(storage, 'HEAD')
|
|
975
|
-
* ```
|
|
976
|
-
*/
|
|
977
|
-
export async function resolveRef(storage, name, options) {
|
|
978
|
-
const resolved = await storage.resolveRef(name, options);
|
|
979
|
-
return resolved.sha;
|
|
980
|
-
}
|
|
981
|
-
/**
|
|
982
|
-
* Update a ref.
|
|
983
|
-
*
|
|
984
|
-
* @description
|
|
985
|
-
* Convenience function that wraps RefStorage.updateRef.
|
|
986
|
-
*
|
|
987
|
-
* @param storage - RefStorage instance
|
|
988
|
-
* @param name - Full ref name
|
|
989
|
-
* @param target - SHA target
|
|
990
|
-
* @param options - Update options
|
|
991
|
-
* @returns The updated ref
|
|
992
|
-
*/
|
|
993
|
-
export async function updateRef(storage, name, target, options) {
|
|
994
|
-
return storage.updateRef(name, target, options);
|
|
995
|
-
}
|
|
996
|
-
/**
|
|
997
|
-
* Delete a ref.
|
|
998
|
-
*
|
|
999
|
-
* @description
|
|
1000
|
-
* Convenience function that wraps RefStorage.deleteRef.
|
|
1001
|
-
*
|
|
1002
|
-
* @param storage - RefStorage instance
|
|
1003
|
-
* @param name - Full ref name to delete
|
|
1004
|
-
* @param options - Delete options
|
|
1005
|
-
* @returns True if deleted
|
|
1006
|
-
*/
|
|
1007
|
-
export async function deleteRef(storage, name, options) {
|
|
1008
|
-
return storage.deleteRef(name, options);
|
|
1009
|
-
}
|
|
1010
|
-
/**
|
|
1011
|
-
* List refs.
|
|
1012
|
-
*
|
|
1013
|
-
* @description
|
|
1014
|
-
* Convenience function that wraps RefStorage.listRefs.
|
|
1015
|
-
*
|
|
1016
|
-
* @param storage - RefStorage instance
|
|
1017
|
-
* @param options - Listing options
|
|
1018
|
-
* @returns Array of refs
|
|
1019
|
-
*/
|
|
1020
|
-
export async function listRefs(storage, options) {
|
|
1021
|
-
return storage.listRefs(options);
|
|
1022
|
-
}
|
|
1023
|
-
//# sourceMappingURL=storage.js.map
|