gitx.do 0.1.1 → 0.1.3
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 -469
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -481
- package/dist/index.js.map +1 -1
- package/dist/mcp/auth.d.ts +77 -0
- package/dist/mcp/auth.d.ts.map +1 -0
- package/dist/mcp/auth.js +278 -0
- package/dist/mcp/auth.js.map +1 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +200 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +275 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tool-registry.d.ts +47 -0
- package/dist/mcp/tool-registry.d.ts.map +1 -0
- package/dist/mcp/tool-registry.js +284 -0
- package/dist/mcp/tool-registry.js.map +1 -0
- package/dist/mcp/tools.d.ts +103 -515
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +676 -3087
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts +124 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +9 -0
- package/dist/mcp/types.js.map +1 -0
- package/package.json +19 -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/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/ops/blame.js
DELETED
|
@@ -1,1037 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Git Blame Algorithm
|
|
3
|
-
*
|
|
4
|
-
* This module provides functionality for attributing each line of a file
|
|
5
|
-
* to the commit that last modified it. It implements a blame algorithm
|
|
6
|
-
* similar to Git's native blame command.
|
|
7
|
-
*
|
|
8
|
-
* ## Features
|
|
9
|
-
*
|
|
10
|
-
* - Line-by-line commit attribution
|
|
11
|
-
* - Rename tracking across commits
|
|
12
|
-
* - Line range filtering
|
|
13
|
-
* - Whitespace-insensitive comparison
|
|
14
|
-
* - Date range filtering
|
|
15
|
-
* - Commit exclusion (ignore revisions)
|
|
16
|
-
* - Binary file detection
|
|
17
|
-
* - Porcelain and human-readable output formats
|
|
18
|
-
*
|
|
19
|
-
* ## Usage Example
|
|
20
|
-
*
|
|
21
|
-
* ```typescript
|
|
22
|
-
* import { blame, formatBlame } from './ops/blame'
|
|
23
|
-
*
|
|
24
|
-
* // Get blame information for a file
|
|
25
|
-
* const result = await blame(storage, 'src/main.ts', 'HEAD', {
|
|
26
|
-
* followRenames: true,
|
|
27
|
-
* ignoreWhitespace: true
|
|
28
|
-
* })
|
|
29
|
-
*
|
|
30
|
-
* // Format for display
|
|
31
|
-
* const output = formatBlame(result, { showLineNumbers: true })
|
|
32
|
-
* console.log(output)
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* @module ops/blame
|
|
36
|
-
*/
|
|
37
|
-
// ============================================================================
|
|
38
|
-
// Helper Functions
|
|
39
|
-
// ============================================================================
|
|
40
|
-
const decoder = new TextDecoder();
|
|
41
|
-
/**
|
|
42
|
-
* Checks if content is likely binary (contains null bytes).
|
|
43
|
-
*
|
|
44
|
-
* Uses a heuristic similar to Git's binary detection:
|
|
45
|
-
* checks the first 8000 bytes for null characters.
|
|
46
|
-
*
|
|
47
|
-
* @param data - The content to check
|
|
48
|
-
* @returns True if the content appears to be binary
|
|
49
|
-
*
|
|
50
|
-
* @internal
|
|
51
|
-
*/
|
|
52
|
-
function isBinaryContent(data) {
|
|
53
|
-
// Check first 8000 bytes or entire file if smaller
|
|
54
|
-
const checkLength = Math.min(data.length, 8000);
|
|
55
|
-
for (let i = 0; i < checkLength; i++) {
|
|
56
|
-
// Null byte is a strong indicator of binary
|
|
57
|
-
if (data[i] === 0)
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Splits content into lines, handling various line ending styles.
|
|
64
|
-
*
|
|
65
|
-
* Handles both Unix (\n) and Windows (\r\n) line endings,
|
|
66
|
-
* normalizing output to not include trailing carriage returns.
|
|
67
|
-
*
|
|
68
|
-
* @param content - The string content to split
|
|
69
|
-
* @returns Array of lines (without line terminators)
|
|
70
|
-
*
|
|
71
|
-
* @internal
|
|
72
|
-
*/
|
|
73
|
-
function splitLines(content) {
|
|
74
|
-
if (content === '')
|
|
75
|
-
return [];
|
|
76
|
-
// Split by \n but handle \r\n as well
|
|
77
|
-
const lines = content.split('\n');
|
|
78
|
-
// If there's a trailing newline, the split will create an empty final element
|
|
79
|
-
// which we should remove to match expected behavior
|
|
80
|
-
if (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
81
|
-
lines.pop();
|
|
82
|
-
}
|
|
83
|
-
return lines.map(line => line.replace(/\r$/, ''));
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Normalizes a line for comparison (optionally ignoring whitespace).
|
|
87
|
-
*
|
|
88
|
-
* @param line - The line to normalize
|
|
89
|
-
* @param ignoreWhitespace - Whether to normalize whitespace
|
|
90
|
-
* @returns The normalized line
|
|
91
|
-
*
|
|
92
|
-
* @internal
|
|
93
|
-
*/
|
|
94
|
-
function normalizeLine(line, ignoreWhitespace) {
|
|
95
|
-
if (ignoreWhitespace) {
|
|
96
|
-
return line.trim().replace(/\s+/g, ' ');
|
|
97
|
-
}
|
|
98
|
-
return line;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Gets file content at a specific path within a commit.
|
|
102
|
-
*
|
|
103
|
-
* Handles nested paths by traversing the tree structure.
|
|
104
|
-
*
|
|
105
|
-
* @param storage - The storage interface
|
|
106
|
-
* @param commit - The commit object
|
|
107
|
-
* @param path - The file path to retrieve
|
|
108
|
-
* @returns The file content, or null if not found
|
|
109
|
-
*
|
|
110
|
-
* @internal
|
|
111
|
-
*/
|
|
112
|
-
async function getFileAtPath(storage, commit, path) {
|
|
113
|
-
// Try the direct storage method first
|
|
114
|
-
const directResult = await storage.getFileAtCommit(commit.tree, path);
|
|
115
|
-
if (directResult)
|
|
116
|
-
return directResult;
|
|
117
|
-
// Handle nested paths manually
|
|
118
|
-
const parts = path.split('/');
|
|
119
|
-
let currentTreeSha = commit.tree;
|
|
120
|
-
for (let i = 0; i < parts.length; i++) {
|
|
121
|
-
const tree = await storage.getTree(currentTreeSha);
|
|
122
|
-
if (!tree)
|
|
123
|
-
return null;
|
|
124
|
-
const entry = tree.entries.find(e => e.name === parts[i]);
|
|
125
|
-
if (!entry)
|
|
126
|
-
return null;
|
|
127
|
-
if (i === parts.length - 1) {
|
|
128
|
-
// Final part - should be a file
|
|
129
|
-
return storage.getBlob(entry.sha);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
// Intermediate part - should be a directory
|
|
133
|
-
if (entry.mode !== '040000')
|
|
134
|
-
return null;
|
|
135
|
-
currentTreeSha = entry.sha;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Computes line mapping between two file versions using LCS algorithm.
|
|
142
|
-
*
|
|
143
|
-
* Returns a mapping of (oldLineIndex -> newLineIndex) for unchanged lines,
|
|
144
|
-
* enabling tracking of line movements between versions.
|
|
145
|
-
*
|
|
146
|
-
* @param oldLines - Lines from the older version
|
|
147
|
-
* @param newLines - Lines from the newer version
|
|
148
|
-
* @param ignoreWhitespace - Whether to ignore whitespace differences
|
|
149
|
-
* @returns Map of old line indices to new line indices
|
|
150
|
-
*
|
|
151
|
-
* @internal
|
|
152
|
-
*/
|
|
153
|
-
function computeLineMapping(oldLines, newLines, ignoreWhitespace = false) {
|
|
154
|
-
// Build a map of unchanged line positions
|
|
155
|
-
const mapping = new Map();
|
|
156
|
-
// Normalize lines for comparison if needed
|
|
157
|
-
const normalizedOld = oldLines.map(l => normalizeLine(l, ignoreWhitespace));
|
|
158
|
-
const normalizedNew = newLines.map(l => normalizeLine(l, ignoreWhitespace));
|
|
159
|
-
// Use a simple greedy LCS approach for line matching
|
|
160
|
-
// Build LCS table
|
|
161
|
-
const m = oldLines.length;
|
|
162
|
-
const n = newLines.length;
|
|
163
|
-
if (m === 0 || n === 0)
|
|
164
|
-
return mapping;
|
|
165
|
-
// Create LCS table
|
|
166
|
-
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
167
|
-
for (let i = 1; i <= m; i++) {
|
|
168
|
-
for (let j = 1; j <= n; j++) {
|
|
169
|
-
if (normalizedOld[i - 1] === normalizedNew[j - 1]) {
|
|
170
|
-
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Backtrack to find the matching lines
|
|
178
|
-
let i = m, j = n;
|
|
179
|
-
while (i > 0 && j > 0) {
|
|
180
|
-
if (normalizedOld[i - 1] === normalizedNew[j - 1]) {
|
|
181
|
-
mapping.set(i - 1, j - 1); // 0-indexed
|
|
182
|
-
i--;
|
|
183
|
-
j--;
|
|
184
|
-
}
|
|
185
|
-
else if (dp[i - 1][j] > dp[i][j - 1]) {
|
|
186
|
-
i--;
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
j--;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return mapping;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Parses a line range specification (git-style -L option).
|
|
196
|
-
*
|
|
197
|
-
* Supports multiple formats:
|
|
198
|
-
* - "start,end": Explicit line range
|
|
199
|
-
* - "start,+offset": Relative offset from start
|
|
200
|
-
* - "/pattern1/,/pattern2/": Regex-based range
|
|
201
|
-
*
|
|
202
|
-
* @param lineRange - The range specification string
|
|
203
|
-
* @param lines - The file content lines (for pattern matching)
|
|
204
|
-
* @returns Object with start and end line numbers (1-indexed)
|
|
205
|
-
*
|
|
206
|
-
* @internal
|
|
207
|
-
*/
|
|
208
|
-
function parseLineRange(lineRange, lines) {
|
|
209
|
-
const totalLines = lines.length;
|
|
210
|
-
// Handle regex patterns like /pattern1/,/pattern2/
|
|
211
|
-
if (lineRange.startsWith('/')) {
|
|
212
|
-
const parts = lineRange.match(/^\/(.+)\/,\/(.+)\/$/);
|
|
213
|
-
if (parts) {
|
|
214
|
-
const startPattern = new RegExp(parts[1]);
|
|
215
|
-
const endPattern = new RegExp(parts[2]);
|
|
216
|
-
let start = -1;
|
|
217
|
-
let end = -1;
|
|
218
|
-
for (let i = 0; i < lines.length; i++) {
|
|
219
|
-
if (start === -1 && startPattern.test(lines[i])) {
|
|
220
|
-
start = i + 1; // 1-indexed
|
|
221
|
-
}
|
|
222
|
-
if (start !== -1 && endPattern.test(lines[i])) {
|
|
223
|
-
end = i + 1; // 1-indexed
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (start === -1)
|
|
228
|
-
start = 1;
|
|
229
|
-
if (end === -1)
|
|
230
|
-
end = totalLines;
|
|
231
|
-
return { start, end };
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// Handle numeric ranges like "2,4" or "2,+3"
|
|
235
|
-
const [startStr, endStr] = lineRange.split(',');
|
|
236
|
-
const start = parseInt(startStr, 10);
|
|
237
|
-
let end;
|
|
238
|
-
if (endStr.startsWith('+')) {
|
|
239
|
-
// Relative offset: start + offset lines
|
|
240
|
-
end = start + parseInt(endStr.slice(1), 10);
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
end = parseInt(endStr, 10);
|
|
244
|
-
}
|
|
245
|
-
return { start, end };
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Calculates similarity between two strings (0-1).
|
|
249
|
-
*
|
|
250
|
-
* Uses line-based comparison with the LCS algorithm to determine
|
|
251
|
-
* what percentage of lines are shared between the two versions.
|
|
252
|
-
*
|
|
253
|
-
* @param a - First string
|
|
254
|
-
* @param b - Second string
|
|
255
|
-
* @returns Similarity score from 0 to 1
|
|
256
|
-
*
|
|
257
|
-
* @internal
|
|
258
|
-
*/
|
|
259
|
-
function calculateSimilarity(a, b) {
|
|
260
|
-
if (a === b)
|
|
261
|
-
return 1;
|
|
262
|
-
if (a.length === 0 || b.length === 0)
|
|
263
|
-
return 0;
|
|
264
|
-
const aLines = splitLines(a);
|
|
265
|
-
const bLines = splitLines(b);
|
|
266
|
-
if (aLines.length === 0 && bLines.length === 0)
|
|
267
|
-
return 1;
|
|
268
|
-
if (aLines.length === 0 || bLines.length === 0)
|
|
269
|
-
return 0;
|
|
270
|
-
// Count matching lines
|
|
271
|
-
const mapping = computeLineMapping(aLines, bLines, false);
|
|
272
|
-
const matchCount = mapping.size;
|
|
273
|
-
const maxLines = Math.max(aLines.length, bLines.length);
|
|
274
|
-
return matchCount / maxLines;
|
|
275
|
-
}
|
|
276
|
-
// ============================================================================
|
|
277
|
-
// Main Functions
|
|
278
|
-
// ============================================================================
|
|
279
|
-
/**
|
|
280
|
-
* Computes blame for a file at a specific commit.
|
|
281
|
-
*
|
|
282
|
-
* Traverses commit history to attribute each line of the file to the
|
|
283
|
-
* commit that last modified it. Supports various options for filtering
|
|
284
|
-
* and tracking behavior.
|
|
285
|
-
*
|
|
286
|
-
* @description
|
|
287
|
-
* The blame algorithm works by:
|
|
288
|
-
* 1. Starting at the specified commit and getting the file content
|
|
289
|
-
* 2. Initially attributing all lines to the starting commit
|
|
290
|
-
* 3. Walking backwards through commit history
|
|
291
|
-
* 4. For each parent commit, computing line mappings using LCS
|
|
292
|
-
* 5. Re-attributing lines that exist unchanged in the parent
|
|
293
|
-
* 6. Continuing until all lines are attributed or history is exhausted
|
|
294
|
-
*
|
|
295
|
-
* @param storage - The storage interface for accessing Git objects
|
|
296
|
-
* @param path - The file path to blame
|
|
297
|
-
* @param commit - The commit SHA to start from
|
|
298
|
-
* @param options - Optional blame configuration
|
|
299
|
-
* @returns The blame result with line attributions
|
|
300
|
-
*
|
|
301
|
-
* @throws {Error} If the commit is not found
|
|
302
|
-
* @throws {Error} If the file is not found at the specified commit
|
|
303
|
-
* @throws {Error} If the file is binary
|
|
304
|
-
*
|
|
305
|
-
* @example
|
|
306
|
-
* ```typescript
|
|
307
|
-
* // Basic blame
|
|
308
|
-
* const result = await blame(storage, 'src/main.ts', 'abc123')
|
|
309
|
-
*
|
|
310
|
-
* // Blame with options
|
|
311
|
-
* const result = await blame(storage, 'README.md', 'HEAD', {
|
|
312
|
-
* followRenames: true,
|
|
313
|
-
* maxCommits: 500,
|
|
314
|
-
* ignoreWhitespace: true
|
|
315
|
-
* })
|
|
316
|
-
*
|
|
317
|
-
* // Blame specific line range
|
|
318
|
-
* const result = await blame(storage, 'config.json', 'main', {
|
|
319
|
-
* lineRange: '10,20'
|
|
320
|
-
* })
|
|
321
|
-
* ```
|
|
322
|
-
*/
|
|
323
|
-
export async function blame(storage, path, commit, options) {
|
|
324
|
-
const opts = options ?? {};
|
|
325
|
-
// Get the commit object
|
|
326
|
-
const commitObj = await storage.getCommit(commit);
|
|
327
|
-
if (!commitObj) {
|
|
328
|
-
throw new Error(`Commit not found: ${commit}`);
|
|
329
|
-
}
|
|
330
|
-
// Get the file content at this commit
|
|
331
|
-
const fileContent = await getFileAtPath(storage, commitObj, path);
|
|
332
|
-
if (fileContent === null) {
|
|
333
|
-
throw new Error(`File not found: ${path} at commit ${commit}`);
|
|
334
|
-
}
|
|
335
|
-
// Check for binary file
|
|
336
|
-
if (isBinaryContent(fileContent)) {
|
|
337
|
-
throw new Error(`Cannot blame binary file: ${path}`);
|
|
338
|
-
}
|
|
339
|
-
const contentStr = decoder.decode(fileContent);
|
|
340
|
-
let lines = splitLines(contentStr);
|
|
341
|
-
// Handle empty file
|
|
342
|
-
if (lines.length === 0) {
|
|
343
|
-
return {
|
|
344
|
-
path,
|
|
345
|
-
lines: [],
|
|
346
|
-
commits: new Map(),
|
|
347
|
-
options: opts
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
// Parse line range if specified
|
|
351
|
-
let startLine = 1;
|
|
352
|
-
let endLine = lines.length;
|
|
353
|
-
if (opts.lineRange) {
|
|
354
|
-
const range = parseLineRange(opts.lineRange, lines);
|
|
355
|
-
startLine = range.start;
|
|
356
|
-
endLine = range.end;
|
|
357
|
-
}
|
|
358
|
-
// Initialize blame info for each line (all attributed to current commit initially)
|
|
359
|
-
const blameInfo = lines.map((content, idx) => ({
|
|
360
|
-
commitSha: commit,
|
|
361
|
-
author: commitObj.author.name,
|
|
362
|
-
email: commitObj.author.email,
|
|
363
|
-
timestamp: commitObj.author.timestamp,
|
|
364
|
-
content,
|
|
365
|
-
lineNumber: idx + 1,
|
|
366
|
-
originalLineNumber: idx + 1,
|
|
367
|
-
originalPath: path
|
|
368
|
-
}));
|
|
369
|
-
// Track which lines still need attribution
|
|
370
|
-
const lineNeedsAttribution = new Array(lines.length).fill(true);
|
|
371
|
-
// Track the current path (for rename following)
|
|
372
|
-
let currentPath = path;
|
|
373
|
-
// Track commits for the result
|
|
374
|
-
const commitsMap = new Map();
|
|
375
|
-
// Add current commit info
|
|
376
|
-
commitsMap.set(commit, {
|
|
377
|
-
sha: commit,
|
|
378
|
-
author: commitObj.author.name,
|
|
379
|
-
email: commitObj.author.email,
|
|
380
|
-
timestamp: commitObj.author.timestamp,
|
|
381
|
-
summary: commitObj.message.split('\n')[0],
|
|
382
|
-
boundary: commitObj.parents.length === 0
|
|
383
|
-
});
|
|
384
|
-
// Walk through commit history
|
|
385
|
-
let currentCommit = commit;
|
|
386
|
-
let currentLines = lines;
|
|
387
|
-
let commitCount = 0;
|
|
388
|
-
const maxCommits = opts.maxCommits ?? Infinity;
|
|
389
|
-
// Handle the followRenames option
|
|
390
|
-
const followRenames = opts.followRenames ?? false;
|
|
391
|
-
// For merge commits, we need to explore both parents
|
|
392
|
-
const commitQueue = [];
|
|
393
|
-
// Initialize with current commit's parents
|
|
394
|
-
const currentCommitObj = await storage.getCommit(currentCommit);
|
|
395
|
-
if (currentCommitObj && currentCommitObj.parents.length > 0) {
|
|
396
|
-
for (const parentSha of currentCommitObj.parents) {
|
|
397
|
-
// Identity mapping for first level
|
|
398
|
-
const identityMapping = new Map();
|
|
399
|
-
for (let i = 0; i < currentLines.length; i++) {
|
|
400
|
-
identityMapping.set(i, i);
|
|
401
|
-
}
|
|
402
|
-
commitQueue.push({
|
|
403
|
-
sha: parentSha,
|
|
404
|
-
lines: currentLines,
|
|
405
|
-
path: currentPath,
|
|
406
|
-
lineMapping: identityMapping,
|
|
407
|
-
childCommitSha: currentCommit
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
// Process commit queue (BFS through history)
|
|
412
|
-
while (commitQueue.length > 0 && commitCount < maxCommits) {
|
|
413
|
-
const item = commitQueue.shift();
|
|
414
|
-
const { sha: parentSha, lines: childLines, path: childPath, lineMapping: childToOriginal, childCommitSha } = item;
|
|
415
|
-
// Check if this commit should be ignored
|
|
416
|
-
if (opts.ignoreRevisions?.includes(parentSha)) {
|
|
417
|
-
// Skip this commit but continue to its parents
|
|
418
|
-
const parentCommitObj = await storage.getCommit(parentSha);
|
|
419
|
-
if (parentCommitObj && parentCommitObj.parents.length > 0) {
|
|
420
|
-
for (const grandparentSha of parentCommitObj.parents) {
|
|
421
|
-
commitQueue.push({
|
|
422
|
-
sha: grandparentSha,
|
|
423
|
-
lines: childLines,
|
|
424
|
-
path: childPath,
|
|
425
|
-
lineMapping: childToOriginal,
|
|
426
|
-
childCommitSha: parentSha
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
commitCount++;
|
|
433
|
-
// Check date filters
|
|
434
|
-
const parentCommitObj = await storage.getCommit(parentSha);
|
|
435
|
-
if (!parentCommitObj)
|
|
436
|
-
continue;
|
|
437
|
-
if (opts.since && parentCommitObj.author.timestamp * 1000 < opts.since.getTime()) {
|
|
438
|
-
continue;
|
|
439
|
-
}
|
|
440
|
-
if (opts.until && parentCommitObj.author.timestamp * 1000 > opts.until.getTime()) {
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
// Track path through renames
|
|
444
|
-
// Renames are stored in the child commit (the one that did the rename)
|
|
445
|
-
// So we check the childCommitSha to find what the file was called in the parent
|
|
446
|
-
let pathInParent = childPath;
|
|
447
|
-
if (followRenames) {
|
|
448
|
-
// Check renames in the child commit (where the rename happened)
|
|
449
|
-
const childRenames = await storage.getRenamesInCommit(childCommitSha);
|
|
450
|
-
// Find reverse rename: oldPath -> newPath means in parent it was oldPath
|
|
451
|
-
for (const [oldPath, newPath] of childRenames) {
|
|
452
|
-
if (newPath === childPath) {
|
|
453
|
-
pathInParent = oldPath;
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
// Get file content in parent
|
|
459
|
-
const parentContent = await getFileAtPath(storage, parentCommitObj, pathInParent);
|
|
460
|
-
// If file doesn't exist in parent, all remaining lines are from the first commit that has them
|
|
461
|
-
if (!parentContent) {
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
const parentContentStr = decoder.decode(parentContent);
|
|
465
|
-
const parentLines = splitLines(parentContentStr);
|
|
466
|
-
// Compute line mapping between parent and child
|
|
467
|
-
const mapping = computeLineMapping(parentLines, childLines, opts.ignoreWhitespace ?? false);
|
|
468
|
-
// Add commit info
|
|
469
|
-
if (!commitsMap.has(parentSha)) {
|
|
470
|
-
commitsMap.set(parentSha, {
|
|
471
|
-
sha: parentSha,
|
|
472
|
-
author: parentCommitObj.author.name,
|
|
473
|
-
email: parentCommitObj.author.email,
|
|
474
|
-
timestamp: parentCommitObj.author.timestamp,
|
|
475
|
-
summary: parentCommitObj.message.split('\n')[0],
|
|
476
|
-
boundary: parentCommitObj.parents.length === 0
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
// Update blame for lines that came from parent
|
|
480
|
-
// mapping: parentLineIdx -> childLineIdx
|
|
481
|
-
for (const [parentIdx, childIdx] of mapping) {
|
|
482
|
-
// Convert childIdx to original index
|
|
483
|
-
for (const [origIdx, mappedChildIdx] of childToOriginal) {
|
|
484
|
-
if (mappedChildIdx === childIdx && lineNeedsAttribution[origIdx]) {
|
|
485
|
-
// This line exists in parent - attribute to parent
|
|
486
|
-
blameInfo[origIdx].commitSha = parentSha;
|
|
487
|
-
blameInfo[origIdx].author = parentCommitObj.author.name;
|
|
488
|
-
blameInfo[origIdx].email = parentCommitObj.author.email;
|
|
489
|
-
blameInfo[origIdx].timestamp = parentCommitObj.author.timestamp;
|
|
490
|
-
blameInfo[origIdx].originalLineNumber = parentIdx + 1;
|
|
491
|
-
if (pathInParent !== childPath) {
|
|
492
|
-
blameInfo[origIdx].originalPath = pathInParent;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
// Build new mapping from original indices to parent indices
|
|
498
|
-
const newMapping = new Map();
|
|
499
|
-
for (const [origIdx, childIdx] of childToOriginal) {
|
|
500
|
-
// Find if this child line maps to a parent line
|
|
501
|
-
for (const [parentIdx, mappedChildIdx] of mapping) {
|
|
502
|
-
if (mappedChildIdx === childIdx) {
|
|
503
|
-
newMapping.set(origIdx, parentIdx);
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
// Add parent's parents to queue if there are still lines to attribute
|
|
509
|
-
if (parentCommitObj.parents.length > 0 && newMapping.size > 0) {
|
|
510
|
-
for (const grandparentSha of parentCommitObj.parents) {
|
|
511
|
-
commitQueue.push({
|
|
512
|
-
sha: grandparentSha,
|
|
513
|
-
lines: parentLines,
|
|
514
|
-
path: pathInParent,
|
|
515
|
-
lineMapping: newMapping,
|
|
516
|
-
childCommitSha: parentSha
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
// Filter to requested line range
|
|
522
|
-
let resultLines = blameInfo;
|
|
523
|
-
if (opts.lineRange) {
|
|
524
|
-
resultLines = blameInfo.filter(l => l.lineNumber >= startLine && l.lineNumber <= endLine);
|
|
525
|
-
}
|
|
526
|
-
return {
|
|
527
|
-
path,
|
|
528
|
-
lines: resultLines,
|
|
529
|
-
commits: commitsMap,
|
|
530
|
-
options: opts
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Alias for blame - get full file blame.
|
|
535
|
-
*
|
|
536
|
-
* This function is identical to `blame` and exists for API compatibility.
|
|
537
|
-
*
|
|
538
|
-
* @param storage - The storage interface
|
|
539
|
-
* @param path - The file path to blame
|
|
540
|
-
* @param commit - The commit SHA to start from
|
|
541
|
-
* @param options - Optional blame configuration
|
|
542
|
-
* @returns The blame result
|
|
543
|
-
*
|
|
544
|
-
* @see {@link blame} for full documentation
|
|
545
|
-
*/
|
|
546
|
-
export async function blameFile(storage, path, commit, options) {
|
|
547
|
-
return blame(storage, path, commit, options);
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Gets blame information for a specific line.
|
|
551
|
-
*
|
|
552
|
-
* Convenience function that performs a full blame and extracts
|
|
553
|
-
* the information for a single line.
|
|
554
|
-
*
|
|
555
|
-
* @param storage - The storage interface
|
|
556
|
-
* @param path - The file path
|
|
557
|
-
* @param lineNumber - The line number (1-indexed)
|
|
558
|
-
* @param commit - The commit SHA
|
|
559
|
-
* @param options - Optional blame configuration
|
|
560
|
-
* @returns Blame information for the specified line
|
|
561
|
-
*
|
|
562
|
-
* @throws {Error} If lineNumber is less than 1
|
|
563
|
-
* @throws {Error} If lineNumber exceeds file length
|
|
564
|
-
*
|
|
565
|
-
* @example
|
|
566
|
-
* ```typescript
|
|
567
|
-
* const lineInfo = await blameLine(storage, 'src/main.ts', 42, 'HEAD')
|
|
568
|
-
* console.log(`Line 42 was last modified by ${lineInfo.author}`)
|
|
569
|
-
* ```
|
|
570
|
-
*/
|
|
571
|
-
export async function blameLine(storage, path, lineNumber, commit, options) {
|
|
572
|
-
if (lineNumber < 1) {
|
|
573
|
-
throw new Error(`Invalid line number: ${lineNumber}. Line numbers start at 1.`);
|
|
574
|
-
}
|
|
575
|
-
const result = await blame(storage, path, commit, options);
|
|
576
|
-
if (lineNumber > result.lines.length) {
|
|
577
|
-
throw new Error(`Invalid line number: ${lineNumber}. File has ${result.lines.length} lines.`);
|
|
578
|
-
}
|
|
579
|
-
return result.lines[lineNumber - 1];
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Gets blame for a specific line range.
|
|
583
|
-
*
|
|
584
|
-
* More efficient than using the lineRange option when you know
|
|
585
|
-
* the exact numeric range you want.
|
|
586
|
-
*
|
|
587
|
-
* @param storage - The storage interface
|
|
588
|
-
* @param path - The file path
|
|
589
|
-
* @param startLine - Starting line number (1-indexed, inclusive)
|
|
590
|
-
* @param endLine - Ending line number (1-indexed, inclusive)
|
|
591
|
-
* @param commit - The commit SHA
|
|
592
|
-
* @param options - Optional blame configuration
|
|
593
|
-
* @returns Blame result for the specified range
|
|
594
|
-
*
|
|
595
|
-
* @throws {Error} If startLine is less than 1
|
|
596
|
-
* @throws {Error} If endLine is less than startLine
|
|
597
|
-
* @throws {Error} If endLine exceeds file length
|
|
598
|
-
*
|
|
599
|
-
* @example
|
|
600
|
-
* ```typescript
|
|
601
|
-
* // Get blame for lines 10-20
|
|
602
|
-
* const result = await blameRange(storage, 'file.ts', 10, 20, 'HEAD')
|
|
603
|
-
* ```
|
|
604
|
-
*/
|
|
605
|
-
export async function blameRange(storage, path, startLine, endLine, commit, options) {
|
|
606
|
-
if (startLine < 1) {
|
|
607
|
-
throw new Error(`Invalid start line: ${startLine}. Line numbers start at 1.`);
|
|
608
|
-
}
|
|
609
|
-
if (endLine < startLine) {
|
|
610
|
-
throw new Error(`Invalid range: end (${endLine}) is before start (${startLine}).`);
|
|
611
|
-
}
|
|
612
|
-
const fullResult = await blame(storage, path, commit, options);
|
|
613
|
-
if (endLine > fullResult.lines.length) {
|
|
614
|
-
throw new Error(`Invalid end line: ${endLine}. File has ${fullResult.lines.length} lines.`);
|
|
615
|
-
}
|
|
616
|
-
return {
|
|
617
|
-
path: fullResult.path,
|
|
618
|
-
lines: fullResult.lines.slice(startLine - 1, endLine),
|
|
619
|
-
commits: fullResult.commits,
|
|
620
|
-
options: fullResult.options
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Gets blame at a specific historical commit.
|
|
625
|
-
*
|
|
626
|
-
* Alias for `blame` - provided for semantic clarity when you want
|
|
627
|
-
* to emphasize you're looking at a specific point in history.
|
|
628
|
-
*
|
|
629
|
-
* @param storage - The storage interface
|
|
630
|
-
* @param path - The file path
|
|
631
|
-
* @param commit - The commit SHA
|
|
632
|
-
* @param options - Optional blame configuration
|
|
633
|
-
* @returns The blame result
|
|
634
|
-
*
|
|
635
|
-
* @see {@link blame} for full documentation
|
|
636
|
-
*/
|
|
637
|
-
export async function getBlameForCommit(storage, path, commit, options) {
|
|
638
|
-
return blame(storage, path, commit, options);
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Tracks file path across renames through history.
|
|
642
|
-
*
|
|
643
|
-
* Walks through commit history and records each path the file
|
|
644
|
-
* had at different points in time.
|
|
645
|
-
*
|
|
646
|
-
* @param storage - The storage interface
|
|
647
|
-
* @param path - Current file path
|
|
648
|
-
* @param commit - Starting commit SHA
|
|
649
|
-
* @param _options - Unused options parameter (reserved for future use)
|
|
650
|
-
* @returns Array of path history entries, newest first
|
|
651
|
-
*
|
|
652
|
-
* @example
|
|
653
|
-
* ```typescript
|
|
654
|
-
* const history = await trackContentAcrossRenames(storage, 'src/new-name.ts', 'HEAD')
|
|
655
|
-
* // history might contain:
|
|
656
|
-
* // [
|
|
657
|
-
* // { commit: 'abc123', path: 'src/new-name.ts' },
|
|
658
|
-
* // { commit: 'def456', path: 'src/old-name.ts' }
|
|
659
|
-
* // ]
|
|
660
|
-
* ```
|
|
661
|
-
*/
|
|
662
|
-
export async function trackContentAcrossRenames(storage, path, commit, _options) {
|
|
663
|
-
const history = [];
|
|
664
|
-
let currentPath = path;
|
|
665
|
-
let currentCommitSha = commit;
|
|
666
|
-
while (currentCommitSha) {
|
|
667
|
-
history.push({ commit: currentCommitSha, path: currentPath });
|
|
668
|
-
const commitObj = await storage.getCommit(currentCommitSha);
|
|
669
|
-
if (!commitObj || commitObj.parents.length === 0)
|
|
670
|
-
break;
|
|
671
|
-
// Check for renames in this commit
|
|
672
|
-
const renames = await storage.getRenamesInCommit(currentCommitSha);
|
|
673
|
-
// Find if our current path was renamed from something
|
|
674
|
-
for (const [oldPath, newPath] of renames) {
|
|
675
|
-
if (newPath === currentPath) {
|
|
676
|
-
currentPath = oldPath;
|
|
677
|
-
break;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
currentCommitSha = commitObj.parents[0];
|
|
681
|
-
}
|
|
682
|
-
return history;
|
|
683
|
-
}
|
|
684
|
-
/**
|
|
685
|
-
* Detects file renames between two commits.
|
|
686
|
-
*
|
|
687
|
-
* Compares two commits to find files that were renamed based on
|
|
688
|
-
* SHA matching (exact renames) and content similarity (renames with modifications).
|
|
689
|
-
*
|
|
690
|
-
* @param storage - The storage interface
|
|
691
|
-
* @param fromCommit - The older commit SHA
|
|
692
|
-
* @param toCommit - The newer commit SHA
|
|
693
|
-
* @param options - Configuration options
|
|
694
|
-
* @param options.threshold - Similarity threshold (0-1) for content-based detection
|
|
695
|
-
* @returns Map of old paths to new paths for detected renames
|
|
696
|
-
*
|
|
697
|
-
* @example
|
|
698
|
-
* ```typescript
|
|
699
|
-
* const renames = await detectRenames(storage, 'abc123', 'def456', {
|
|
700
|
-
* threshold: 0.5
|
|
701
|
-
* })
|
|
702
|
-
*
|
|
703
|
-
* for (const [oldPath, newPath] of renames) {
|
|
704
|
-
* console.log(`${oldPath} -> ${newPath}`)
|
|
705
|
-
* }
|
|
706
|
-
* ```
|
|
707
|
-
*/
|
|
708
|
-
export async function detectRenames(storage, fromCommit, toCommit, options) {
|
|
709
|
-
const threshold = options?.threshold ?? 0.5;
|
|
710
|
-
const renames = new Map();
|
|
711
|
-
const fromCommitObj = await storage.getCommit(fromCommit);
|
|
712
|
-
const toCommitObj = await storage.getCommit(toCommit);
|
|
713
|
-
if (!fromCommitObj || !toCommitObj)
|
|
714
|
-
return renames;
|
|
715
|
-
const fromTree = await storage.getTree(fromCommitObj.tree);
|
|
716
|
-
const toTree = await storage.getTree(toCommitObj.tree);
|
|
717
|
-
if (!fromTree || !toTree)
|
|
718
|
-
return renames;
|
|
719
|
-
// Find files that were deleted in 'from' and added in 'to'
|
|
720
|
-
const fromFiles = new Map(); // name -> sha
|
|
721
|
-
const toFiles = new Map();
|
|
722
|
-
for (const entry of fromTree.entries) {
|
|
723
|
-
if (entry.mode !== '040000') { // Skip directories
|
|
724
|
-
fromFiles.set(entry.name, entry.sha);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
for (const entry of toTree.entries) {
|
|
728
|
-
if (entry.mode !== '040000') {
|
|
729
|
-
toFiles.set(entry.name, entry.sha);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
// Find deleted files (in from but not in to)
|
|
733
|
-
const deletedFiles = [];
|
|
734
|
-
for (const name of fromFiles.keys()) {
|
|
735
|
-
if (!toFiles.has(name)) {
|
|
736
|
-
deletedFiles.push(name);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
// Find added files (in to but not in from)
|
|
740
|
-
const addedFiles = [];
|
|
741
|
-
for (const name of toFiles.keys()) {
|
|
742
|
-
if (!fromFiles.has(name)) {
|
|
743
|
-
addedFiles.push(name);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
// Check for exact SHA matches (pure renames)
|
|
747
|
-
for (const deleted of deletedFiles) {
|
|
748
|
-
const deletedSha = fromFiles.get(deleted);
|
|
749
|
-
for (const added of addedFiles) {
|
|
750
|
-
const addedSha = toFiles.get(added);
|
|
751
|
-
if (deletedSha === addedSha) {
|
|
752
|
-
renames.set(deleted, added);
|
|
753
|
-
break;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
// Check for content similarity (renames with modifications)
|
|
758
|
-
for (const deleted of deletedFiles) {
|
|
759
|
-
if (renames.has(deleted))
|
|
760
|
-
continue;
|
|
761
|
-
const deletedSha = fromFiles.get(deleted);
|
|
762
|
-
const deletedContent = await storage.getBlob(deletedSha);
|
|
763
|
-
if (!deletedContent || isBinaryContent(deletedContent))
|
|
764
|
-
continue;
|
|
765
|
-
const deletedStr = decoder.decode(deletedContent);
|
|
766
|
-
for (const added of addedFiles) {
|
|
767
|
-
// Check if already matched
|
|
768
|
-
let alreadyMatched = false;
|
|
769
|
-
for (const [, v] of renames) {
|
|
770
|
-
if (v === added) {
|
|
771
|
-
alreadyMatched = true;
|
|
772
|
-
break;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
if (alreadyMatched)
|
|
776
|
-
continue;
|
|
777
|
-
const addedSha = toFiles.get(added);
|
|
778
|
-
const addedContent = await storage.getBlob(addedSha);
|
|
779
|
-
if (!addedContent || isBinaryContent(addedContent))
|
|
780
|
-
continue;
|
|
781
|
-
const addedStr = decoder.decode(addedContent);
|
|
782
|
-
const similarity = calculateSimilarity(deletedStr, addedStr);
|
|
783
|
-
if (similarity >= threshold) {
|
|
784
|
-
renames.set(deleted, added);
|
|
785
|
-
break;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
return renames;
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Builds complete blame history for a specific line.
|
|
793
|
-
*
|
|
794
|
-
* Tracks a single line through history, recording its content
|
|
795
|
-
* at each commit where it existed.
|
|
796
|
-
*
|
|
797
|
-
* @param storage - The storage interface
|
|
798
|
-
* @param path - The file path
|
|
799
|
-
* @param lineNumber - The line number to track (1-indexed)
|
|
800
|
-
* @param commit - Starting commit SHA
|
|
801
|
-
* @param options - Optional blame configuration
|
|
802
|
-
* @returns Array of history entries, newest first
|
|
803
|
-
*
|
|
804
|
-
* @example
|
|
805
|
-
* ```typescript
|
|
806
|
-
* const history = await buildBlameHistory(storage, 'main.ts', 10, 'HEAD')
|
|
807
|
-
*
|
|
808
|
-
* for (const entry of history) {
|
|
809
|
-
* console.log(`${entry.commitSha}: ${entry.content}`)
|
|
810
|
-
* }
|
|
811
|
-
* ```
|
|
812
|
-
*/
|
|
813
|
-
export async function buildBlameHistory(storage, path, lineNumber, commit, options) {
|
|
814
|
-
const history = [];
|
|
815
|
-
let currentCommitSha = commit;
|
|
816
|
-
let currentPath = path;
|
|
817
|
-
let currentLineNumber = lineNumber;
|
|
818
|
-
while (currentCommitSha) {
|
|
819
|
-
const commitObj = await storage.getCommit(currentCommitSha);
|
|
820
|
-
if (!commitObj)
|
|
821
|
-
break;
|
|
822
|
-
const fileContent = await getFileAtPath(storage, commitObj, currentPath);
|
|
823
|
-
if (!fileContent)
|
|
824
|
-
break;
|
|
825
|
-
const contentStr = decoder.decode(fileContent);
|
|
826
|
-
const lines = splitLines(contentStr);
|
|
827
|
-
if (currentLineNumber > lines.length || currentLineNumber < 1)
|
|
828
|
-
break;
|
|
829
|
-
history.push({
|
|
830
|
-
commitSha: currentCommitSha,
|
|
831
|
-
content: lines[currentLineNumber - 1],
|
|
832
|
-
lineNumber: currentLineNumber,
|
|
833
|
-
author: commitObj.author.name,
|
|
834
|
-
timestamp: commitObj.author.timestamp
|
|
835
|
-
});
|
|
836
|
-
// Move to parent
|
|
837
|
-
if (commitObj.parents.length === 0)
|
|
838
|
-
break;
|
|
839
|
-
const parentSha = commitObj.parents[0];
|
|
840
|
-
const parentCommitObj = await storage.getCommit(parentSha);
|
|
841
|
-
if (!parentCommitObj)
|
|
842
|
-
break;
|
|
843
|
-
// Check for renames
|
|
844
|
-
const renames = await storage.getRenamesInCommit(currentCommitSha);
|
|
845
|
-
for (const [oldPath, newPath] of renames) {
|
|
846
|
-
if (newPath === currentPath) {
|
|
847
|
-
currentPath = oldPath;
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
// Get parent content and find corresponding line
|
|
852
|
-
const parentContent = await getFileAtPath(storage, parentCommitObj, currentPath);
|
|
853
|
-
if (!parentContent)
|
|
854
|
-
break;
|
|
855
|
-
const parentContentStr = decoder.decode(parentContent);
|
|
856
|
-
const parentLines = splitLines(parentContentStr);
|
|
857
|
-
// Find which line in parent corresponds to our current line
|
|
858
|
-
const mapping = computeLineMapping(parentLines, lines, options?.ignoreWhitespace ?? false);
|
|
859
|
-
let foundParentLine = false;
|
|
860
|
-
for (const [parentIdx, childIdx] of mapping) {
|
|
861
|
-
if (childIdx === currentLineNumber - 1) {
|
|
862
|
-
currentLineNumber = parentIdx + 1;
|
|
863
|
-
foundParentLine = true;
|
|
864
|
-
break;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
// If we didn't find a content match but the parent has the line at the same position,
|
|
868
|
-
// assume it's the same line (content was modified). This is important for tracking
|
|
869
|
-
// history of lines that change content in every commit.
|
|
870
|
-
if (!foundParentLine) {
|
|
871
|
-
if (currentLineNumber <= parentLines.length) {
|
|
872
|
-
// Line exists at same position in parent - assume it's the same logical line
|
|
873
|
-
foundParentLine = true;
|
|
874
|
-
// currentLineNumber stays the same
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
break;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
currentCommitSha = parentSha;
|
|
881
|
-
}
|
|
882
|
-
return history;
|
|
883
|
-
}
|
|
884
|
-
/**
|
|
885
|
-
* Formats blame result for display.
|
|
886
|
-
*
|
|
887
|
-
* Converts a BlameResult into a human-readable or machine-parseable string format.
|
|
888
|
-
*
|
|
889
|
-
* @param result - The blame result to format
|
|
890
|
-
* @param options - Formatting options
|
|
891
|
-
* @returns Formatted string output
|
|
892
|
-
*
|
|
893
|
-
* @example
|
|
894
|
-
* ```typescript
|
|
895
|
-
* const result = await blame(storage, 'main.ts', 'HEAD')
|
|
896
|
-
*
|
|
897
|
-
* // Human-readable format
|
|
898
|
-
* const output = formatBlame(result, {
|
|
899
|
-
* showLineNumbers: true,
|
|
900
|
-
* showDate: true
|
|
901
|
-
* })
|
|
902
|
-
*
|
|
903
|
-
* // Machine-readable format
|
|
904
|
-
* const porcelain = formatBlame(result, { format: 'porcelain' })
|
|
905
|
-
* ```
|
|
906
|
-
*/
|
|
907
|
-
export function formatBlame(result, options) {
|
|
908
|
-
const opts = options ?? {};
|
|
909
|
-
const lines = [];
|
|
910
|
-
if (opts.format === 'porcelain') {
|
|
911
|
-
// Porcelain format - machine readable
|
|
912
|
-
for (const line of result.lines) {
|
|
913
|
-
const commitInfo = result.commits.get(line.commitSha);
|
|
914
|
-
lines.push(`${line.commitSha} ${line.originalLineNumber} ${line.lineNumber} 1`);
|
|
915
|
-
lines.push(`author ${line.author}`);
|
|
916
|
-
lines.push(`author-mail <${line.email || commitInfo?.email || ''}>`);
|
|
917
|
-
lines.push(`author-time ${line.timestamp}`);
|
|
918
|
-
lines.push(`author-tz +0000`);
|
|
919
|
-
lines.push(`committer ${line.author}`);
|
|
920
|
-
lines.push(`committer-mail <${line.email || commitInfo?.email || ''}>`);
|
|
921
|
-
lines.push(`committer-time ${line.timestamp}`);
|
|
922
|
-
lines.push(`committer-tz +0000`);
|
|
923
|
-
lines.push(`filename ${result.path}`);
|
|
924
|
-
lines.push(`\t${line.content}`);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
else {
|
|
928
|
-
// Default format - human readable
|
|
929
|
-
for (const line of result.lines) {
|
|
930
|
-
const sha = line.commitSha.substring(0, 8);
|
|
931
|
-
const author = line.author.padEnd(15).substring(0, 15);
|
|
932
|
-
let datePart = '';
|
|
933
|
-
if (opts.showDate) {
|
|
934
|
-
const date = new Date(line.timestamp * 1000);
|
|
935
|
-
datePart = ` ${date.toISOString().substring(0, 10)}`;
|
|
936
|
-
}
|
|
937
|
-
let authorPart = author;
|
|
938
|
-
if (opts.showEmail) {
|
|
939
|
-
const email = line.email || result.commits.get(line.commitSha)?.email || '';
|
|
940
|
-
authorPart = email.padEnd(25).substring(0, 25);
|
|
941
|
-
}
|
|
942
|
-
let lineNumPart = '';
|
|
943
|
-
if (opts.showLineNumbers) {
|
|
944
|
-
lineNumPart = `${line.lineNumber}) `;
|
|
945
|
-
}
|
|
946
|
-
lines.push(`${sha} (${authorPart}${datePart} ${lineNumPart}${line.content}`);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
return lines.join('\n');
|
|
950
|
-
}
|
|
951
|
-
/**
|
|
952
|
-
* Parses porcelain blame output back into a BlameResult.
|
|
953
|
-
*
|
|
954
|
-
* Useful for consuming blame output from external sources or
|
|
955
|
-
* for round-trip serialization.
|
|
956
|
-
*
|
|
957
|
-
* @param output - Porcelain format blame output string
|
|
958
|
-
* @returns Parsed blame result
|
|
959
|
-
*
|
|
960
|
-
* @example
|
|
961
|
-
* ```typescript
|
|
962
|
-
* const porcelainOutput = formatBlame(result, { format: 'porcelain' })
|
|
963
|
-
* const parsed = parseBlameOutput(porcelainOutput)
|
|
964
|
-
* ```
|
|
965
|
-
*/
|
|
966
|
-
export function parseBlameOutput(output) {
|
|
967
|
-
const lines = [];
|
|
968
|
-
const commits = new Map();
|
|
969
|
-
const outputLines = output.split('\n');
|
|
970
|
-
let i = 0;
|
|
971
|
-
while (i < outputLines.length) {
|
|
972
|
-
const headerLine = outputLines[i];
|
|
973
|
-
if (!headerLine || headerLine.trim() === '') {
|
|
974
|
-
i++;
|
|
975
|
-
continue;
|
|
976
|
-
}
|
|
977
|
-
// Parse header: <sha> <orig-line> <final-line> <num-lines>
|
|
978
|
-
// Accept any 40-char alphanumeric SHA (to support test fixtures using makeSha)
|
|
979
|
-
const headerMatch = headerLine.match(/^([0-9a-zA-Z]{40}) (\d+) (\d+)/);
|
|
980
|
-
if (!headerMatch) {
|
|
981
|
-
i++;
|
|
982
|
-
continue;
|
|
983
|
-
}
|
|
984
|
-
const commitSha = headerMatch[1];
|
|
985
|
-
const originalLineNumber = parseInt(headerMatch[2], 10);
|
|
986
|
-
const lineNumber = parseInt(headerMatch[3], 10);
|
|
987
|
-
// Parse metadata lines until we hit the content line (starts with tab)
|
|
988
|
-
let author = '';
|
|
989
|
-
let email = '';
|
|
990
|
-
let timestamp = 0;
|
|
991
|
-
let content = '';
|
|
992
|
-
i++;
|
|
993
|
-
while (i < outputLines.length) {
|
|
994
|
-
const metaLine = outputLines[i];
|
|
995
|
-
if (metaLine.startsWith('\t')) {
|
|
996
|
-
content = metaLine.substring(1);
|
|
997
|
-
i++;
|
|
998
|
-
break;
|
|
999
|
-
}
|
|
1000
|
-
if (metaLine.startsWith('author ')) {
|
|
1001
|
-
author = metaLine.substring(7);
|
|
1002
|
-
}
|
|
1003
|
-
else if (metaLine.startsWith('author-mail ')) {
|
|
1004
|
-
email = metaLine.substring(12).replace(/[<>]/g, '');
|
|
1005
|
-
}
|
|
1006
|
-
else if (metaLine.startsWith('author-time ')) {
|
|
1007
|
-
timestamp = parseInt(metaLine.substring(12), 10);
|
|
1008
|
-
}
|
|
1009
|
-
i++;
|
|
1010
|
-
}
|
|
1011
|
-
lines.push({
|
|
1012
|
-
commitSha,
|
|
1013
|
-
author,
|
|
1014
|
-
email,
|
|
1015
|
-
timestamp,
|
|
1016
|
-
content,
|
|
1017
|
-
lineNumber,
|
|
1018
|
-
originalLineNumber
|
|
1019
|
-
});
|
|
1020
|
-
// Add commit info if not already present
|
|
1021
|
-
if (!commits.has(commitSha)) {
|
|
1022
|
-
commits.set(commitSha, {
|
|
1023
|
-
sha: commitSha,
|
|
1024
|
-
author,
|
|
1025
|
-
email,
|
|
1026
|
-
timestamp,
|
|
1027
|
-
summary: ''
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
return {
|
|
1032
|
-
path: '',
|
|
1033
|
-
lines,
|
|
1034
|
-
commits
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
//# sourceMappingURL=blame.js.map
|