@gitkraken/core-gitlens 0.5.113 → 0.5.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/dist/git/cache.d.ts +4 -0
- package/dist/git/cache.d.ts.map +1 -1
- package/dist/git/cache.js +33 -0
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +10 -0
- package/dist/git/context.d.ts.map +1 -1
- package/dist/git/context.js.map +1 -1
- package/dist/git/errors.d.ts +6 -3
- package/dist/git/errors.d.ts.map +1 -1
- package/dist/git/errors.js +9 -1
- package/dist/git/errors.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.d.ts.map +1 -1
- package/dist/git/features.js +38 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/gitHealth.d.ts +153 -0
- package/dist/git/gitHealth.d.ts.map +1 -0
- package/dist/git/gitHealth.js +353 -0
- package/dist/git/gitHealth.js.map +1 -0
- package/dist/git/models/search.d.ts +6 -3
- package/dist/git/models/search.d.ts.map +1 -1
- package/dist/git/models/search.js +5 -1
- package/dist/git/models/search.js.map +1 -1
- package/dist/git/parsers/diffParser.d.ts +8 -2
- package/dist/git/parsers/diffParser.d.ts.map +1 -1
- package/dist/git/parsers/diffParser.js +27 -4
- package/dist/git/parsers/diffParser.js.map +1 -1
- package/dist/git/providers/config.d.ts +36 -3
- package/dist/git/providers/config.d.ts.map +1 -1
- package/dist/git/providers/graph.d.ts +9 -0
- package/dist/git/providers/graph.d.ts.map +1 -1
- package/dist/git/providers/maintenance.d.ts +222 -0
- package/dist/git/providers/maintenance.d.ts.map +1 -0
- package/dist/git/providers/maintenance.js +20 -0
- package/dist/git/providers/maintenance.js.map +1 -0
- package/dist/git/providers/operations.d.ts +10 -0
- package/dist/git/providers/operations.d.ts.map +1 -1
- package/dist/git/providers/pausedOperations.d.ts +3 -0
- package/dist/git/providers/pausedOperations.d.ts.map +1 -1
- package/dist/git/providers/provider.d.ts +2 -0
- package/dist/git/providers/provider.d.ts.map +1 -1
- package/dist/git/providers/tags.d.ts +3 -0
- package/dist/git/providers/tags.d.ts.map +1 -1
- package/dist/git/repositoryService.d.ts +1 -0
- package/dist/git/repositoryService.d.ts.map +1 -1
- package/dist/git/repositoryService.js.map +1 -1
- package/dist/git/run.types.d.ts +8 -0
- package/dist/git/run.types.d.ts.map +1 -1
- package/dist/git/service.js +3 -0
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/search.utils.d.ts.map +1 -1
- package/dist/git/utils/search.utils.js +112 -5
- package/dist/git/utils/search.utils.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +3 -0
- package/dist/git/watching/watchService.d.ts.map +1 -1
- package/dist/git/watching/watchService.js +8 -0
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +11 -0
- package/dist/git-cli/cliGitProvider.d.ts.map +1 -1
- package/dist/git-cli/cliGitProvider.js +12 -0
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +23 -1
- package/dist/git-cli/exec/git.d.ts.map +1 -1
- package/dist/git-cli/exec/git.js +83 -8
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +2 -0
- package/dist/git-cli/exec/gitQueue.d.ts.map +1 -1
- package/dist/git-cli/exec/gitQueue.js +19 -17
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/parsers/logParser.d.ts.map +1 -1
- package/dist/git-cli/parsers/logParser.js +4 -1
- package/dist/git-cli/parsers/logParser.js.map +1 -1
- package/dist/git-cli/providers/blame.d.ts.map +1 -1
- package/dist/git-cli/providers/blame.js +4 -7
- package/dist/git-cli/providers/blame.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts.map +1 -1
- package/dist/git-cli/providers/branches.js +13 -3
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.d.ts.map +1 -1
- package/dist/git-cli/providers/commits.js +34 -3
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +27 -0
- package/dist/git-cli/providers/config.d.ts.map +1 -1
- package/dist/git-cli/providers/config.js +31 -6
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/graph.d.ts +8 -0
- package/dist/git-cli/providers/graph.d.ts.map +1 -1
- package/dist/git-cli/providers/graph.js +85 -6
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/maintenance.d.ts +306 -0
- package/dist/git-cli/providers/maintenance.d.ts.map +1 -0
- package/dist/git-cli/providers/maintenance.js +2129 -0
- package/dist/git-cli/providers/maintenance.js.map +1 -0
- package/dist/git-cli/providers/operations.d.ts +1 -0
- package/dist/git-cli/providers/operations.d.ts.map +1 -1
- package/dist/git-cli/providers/operations.js +8 -0
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +1 -0
- package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
- package/dist/git-cli/providers/pausedOperations.js +1 -1
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/tags.d.ts +3 -0
- package/dist/git-cli/providers/tags.d.ts.map +1 -1
- package/dist/git-cli/providers/tags.js +23 -1
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/plus/agents/agentCapabilities.d.ts +77 -0
- package/dist/plus/agents/agentCapabilities.d.ts.map +1 -0
- package/dist/plus/agents/agentCapabilities.js +197 -0
- package/dist/plus/agents/agentCapabilities.js.map +1 -0
- package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +77 -13
- package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +263 -55
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/agents/providers/{claudeCodeProvider.d.ts → gkAgentProvider.d.ts} +52 -19
- package/dist/plus/agents/providers/gkAgentProvider.d.ts.map +1 -0
- package/dist/plus/agents/providers/{claudeCodeProvider.js → gkAgentProvider.js} +935 -262
- package/dist/plus/agents/providers/gkAgentProvider.js.map +1 -0
- package/dist/plus/agents/types.d.ts +113 -35
- package/dist/plus/agents/types.d.ts.map +1 -1
- package/dist/plus/agents/types.js +7 -4
- package/dist/plus/agents/types.js.map +1 -1
- package/dist/plus/ai/models/promptTemplates.d.ts +1 -1
- package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
- package/dist/plus/ai/prompts.d.ts +4 -0
- package/dist/plus/ai/prompts.d.ts.map +1 -1
- package/dist/plus/ai/prompts.js +59 -4
- package/dist/plus/ai/prompts.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +44 -1
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +318 -25
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/issueSearchQuery.d.ts +13 -0
- package/dist/plus/git-github/api/issueSearchQuery.d.ts.map +1 -1
- package/dist/plus/git-github/api/issueSearchQuery.js +41 -0
- package/dist/plus/git-github/api/issueSearchQuery.js.map +1 -1
- package/dist/plus/git-github/providers/github/blame.d.ts.map +1 -1
- package/dist/plus/git-github/providers/github/blame.js +2 -1
- package/dist/plus/git-github/providers/github/blame.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +11 -0
- package/dist/plus/integrations/integrationService.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.js +4 -0
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +26 -0
- package/dist/plus/integrations/manager.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +31 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +31 -0
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +1 -1
- package/dist/plus/integrations/models/integration.d.ts.map +1 -1
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +9 -0
- package/dist/plus/integrations/providers/github.d.ts.map +1 -1
- package/dist/plus/integrations/providers/github.js +7 -0
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.d.ts +47 -10
- package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
- package/dist/plus/integrations/reads/broaden.js +144 -33
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +18 -0
- package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
- package/dist/plus/integrations/reads/filters.js +37 -1
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +51 -0
- package/dist/plus/integrations/reads/issueBatch.d.ts.map +1 -0
- package/dist/plus/integrations/reads/issueBatch.js +91 -0
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -0
- package/dist/plus/integrations/reads/sweeps.d.ts.map +1 -1
- package/dist/plus/integrations/reads/sweeps.js +5 -3
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/utils/promiseCache.d.ts +19 -0
- package/dist/utils/promiseCache.d.ts.map +1 -1
- package/dist/utils/promiseCache.js +30 -0
- package/dist/utils/promiseCache.js.map +1 -1
- package/dist/utils/resourceUsage.d.ts +5 -0
- package/dist/utils/resourceUsage.d.ts.map +1 -0
- package/dist/utils/resourceUsage.js +2 -0
- package/dist/utils/resourceUsage.js.map +1 -0
- package/dist/utils/string.d.ts +12 -0
- package/dist/utils/string.d.ts.map +1 -1
- package/dist/utils/string.js +36 -1
- package/dist/utils/string.js.map +1 -1
- package/docs/integrations.md +29 -25
- package/docs/kepler-read-api-parity.md +12 -4
- package/package.json +11 -3
- package/src/git/cache.ts +36 -0
- package/src/git/context.ts +11 -0
- package/src/git/errors.ts +13 -1
- package/src/git/features.ts +51 -0
- package/src/git/gitHealth.ts +522 -0
- package/src/git/models/search.ts +19 -4
- package/src/git/parsers/diffParser.ts +27 -4
- package/src/git/providers/config.ts +42 -1
- package/src/git/providers/graph.ts +12 -0
- package/src/git/providers/maintenance.ts +224 -0
- package/src/git/providers/operations.ts +10 -0
- package/src/git/providers/pausedOperations.ts +3 -0
- package/src/git/providers/provider.ts +2 -0
- package/src/git/providers/tags.ts +1 -0
- package/src/git/repositoryService.ts +1 -0
- package/src/git/run.types.ts +8 -0
- package/src/git/service.ts +3 -0
- package/src/git/utils/search.utils.ts +129 -5
- package/src/git/watching/watchService.ts +10 -0
- package/src/git-cli/cliGitProvider.ts +25 -0
- package/src/git-cli/exec/git.ts +120 -6
- package/src/git-cli/exec/gitQueue.ts +19 -17
- package/src/git-cli/parsers/logParser.ts +4 -1
- package/src/git-cli/providers/blame.ts +3 -8
- package/src/git-cli/providers/branches.ts +28 -8
- package/src/git-cli/providers/commits.ts +34 -2
- package/src/git-cli/providers/config.ts +38 -6
- package/src/git-cli/providers/graph.ts +107 -7
- package/src/git-cli/providers/maintenance.ts +2599 -0
- package/src/git-cli/providers/operations.ts +11 -0
- package/src/git-cli/providers/pausedOperations.ts +7 -2
- package/src/git-cli/providers/tags.ts +36 -1
- package/src/plus/agents/agentCapabilities.ts +286 -0
- package/src/plus/agents/providers/claudeCodeTranscript.ts +313 -58
- package/src/plus/agents/providers/{claudeCodeProvider.ts → gkAgentProvider.ts} +1123 -277
- package/src/plus/agents/types.ts +137 -40
- package/src/plus/ai/models/promptTemplates.ts +1 -1
- package/src/plus/ai/prompts.ts +62 -4
- package/src/plus/git-github/api/github.ts +353 -24
- package/src/plus/git-github/api/issueSearchQuery.ts +49 -1
- package/src/plus/git-github/providers/github/blame.ts +2 -1
- package/src/plus/integrations/integrationService.ts +15 -0
- package/src/plus/integrations/manager.ts +26 -0
- package/src/plus/integrations/models/gitHostIntegration.ts +48 -0
- package/src/plus/integrations/models/integration.ts +1 -0
- package/src/plus/integrations/providers/github.ts +18 -0
- package/src/plus/integrations/reads/broaden.ts +282 -70
- package/src/plus/integrations/reads/filters.ts +39 -1
- package/src/plus/integrations/reads/issueBatch.ts +197 -0
- package/src/plus/integrations/reads/sweeps.ts +19 -12
- package/src/utils/promiseCache.ts +43 -0
- package/src/utils/resourceUsage.ts +5 -0
- package/src/utils/string.ts +38 -1
- package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +0 -1
- package/dist/plus/agents/providers/claudeCodeProvider.js.map +0 -1
|
@@ -23,6 +23,7 @@ import { ConfigGitSubProvider } from './providers/config.js';
|
|
|
23
23
|
import { ContributorsGitSubProvider } from './providers/contributors.js';
|
|
24
24
|
import { DiffGitSubProvider } from './providers/diff.js';
|
|
25
25
|
import { GraphGitSubProvider } from './providers/graph.js';
|
|
26
|
+
import { MaintenanceGitSubProvider } from './providers/maintenance.js';
|
|
26
27
|
import { OperationsGitSubProvider } from './providers/operations.js';
|
|
27
28
|
import { PatchGitSubProvider } from './providers/patch.js';
|
|
28
29
|
import { PausedOperationsGitSubProvider } from './providers/pausedOperations.js';
|
|
@@ -46,6 +47,13 @@ export interface CliGitProviderOptions {
|
|
|
46
47
|
git?: Git;
|
|
47
48
|
/** Git execution options (timeout, trust, queue config, hooks, etc.) */
|
|
48
49
|
gitOptions?: GitOptions;
|
|
50
|
+
/**
|
|
51
|
+
* Whether repositories reached through this provider are on the LOCAL filesystem. `false` disables the
|
|
52
|
+
* maintenance sub-provider entirely (Live Share): its probes stat local paths, and its writes are
|
|
53
|
+
* rejected by the host's command allowlist, so inheriting it yields empty reports and swallowed
|
|
54
|
+
* failures rather than an honest "unsupported here". Defaults to `true`.
|
|
55
|
+
*/
|
|
56
|
+
localRepositories?: boolean;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
/**
|
|
@@ -83,9 +91,11 @@ export class CliGitProvider implements GitProvider {
|
|
|
83
91
|
private readonly _git: Git;
|
|
84
92
|
|
|
85
93
|
readonly context: GitServiceContext;
|
|
94
|
+
private readonly _localRepositories: boolean;
|
|
86
95
|
|
|
87
96
|
constructor(options: CliGitProviderOptions) {
|
|
88
97
|
this.context = options.context;
|
|
98
|
+
this._localRepositories = options.localRepositories ?? true;
|
|
89
99
|
this._git =
|
|
90
100
|
options.git ??
|
|
91
101
|
new Git(options.locator, {
|
|
@@ -220,6 +230,21 @@ export class CliGitProvider implements GitProvider {
|
|
|
220
230
|
));
|
|
221
231
|
}
|
|
222
232
|
|
|
233
|
+
private _maintenance: MaintenanceGitSubProvider | undefined;
|
|
234
|
+
// Optional so subclasses whose repos aren't local can report the capability as absent — see
|
|
235
|
+
// `VslsGitProvider`. Consumers already treat `maintenance == null` as "unsupported here" (web builds
|
|
236
|
+
// never register it at all).
|
|
237
|
+
get maintenance(): MaintenanceGitSubProvider | undefined {
|
|
238
|
+
if (!this._localRepositories) return undefined;
|
|
239
|
+
|
|
240
|
+
return (this._maintenance ??= new MaintenanceGitSubProvider(
|
|
241
|
+
this.context,
|
|
242
|
+
this._git,
|
|
243
|
+
this._cache,
|
|
244
|
+
this as unknown as CliGitProviderInternal,
|
|
245
|
+
));
|
|
246
|
+
}
|
|
247
|
+
|
|
223
248
|
private _ops: OperationsGitSubProvider | undefined;
|
|
224
249
|
get ops(): OperationsGitSubProvider {
|
|
225
250
|
return (this._ops ??= new OperationsGitSubProvider(
|
package/src/git-cli/exec/git.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
CommitErrorReason,
|
|
9
9
|
FetchErrorReason,
|
|
10
10
|
GitCommandError,
|
|
11
|
+
GitSearchErrorReason,
|
|
11
12
|
GitWarningKey,
|
|
12
13
|
MergeErrorReason,
|
|
13
14
|
PausedOperationAbortErrorReason,
|
|
@@ -45,7 +46,7 @@ import type { GitCommandPriority, GitResult, GitRunOptions, GitSpawnOptions } fr
|
|
|
45
46
|
import type { FilteredGitFeatures, GitFeatureOrPrefix, GitFeatures } from './features.js';
|
|
46
47
|
import { gitFeaturesByVersion } from './features.js';
|
|
47
48
|
import type { GitQueueConfig } from './gitQueue.js';
|
|
48
|
-
import { GitQueue, inferGitCommandPriority } from './gitQueue.js';
|
|
49
|
+
import { getPrimaryGitCommand, GitQueue, inferGitCommandPriority } from './gitQueue.js';
|
|
49
50
|
import type { GitLocation } from './locator.js';
|
|
50
51
|
|
|
51
52
|
const slowCallWarningThreshold = 2000;
|
|
@@ -62,7 +63,7 @@ export const gitConfigsStatus = ['-c', 'color.status=false'] as const;
|
|
|
62
63
|
export const GitErrors = {
|
|
63
64
|
alreadyCheckedOut: /already checked out/i,
|
|
64
65
|
alreadyExists: /already exists/i,
|
|
65
|
-
ambiguousArgument: /fatal:\s*ambiguous argument ['"]
|
|
66
|
+
ambiguousArgument: /fatal:\s*ambiguous argument ['"](.+?)['"]: unknown revision or path not in the working tree/i,
|
|
66
67
|
badObject: /fatal:\s*bad object (.*?)/i,
|
|
67
68
|
badRevision: /bad revision '(.*?)'/i,
|
|
68
69
|
branchAlreadyExists: /fatal:\s*A branch named '.+?' already exists/i,
|
|
@@ -83,6 +84,10 @@ export const GitErrors = {
|
|
|
83
84
|
invalidLineCount: /file .+? has only (\d+) lines/i,
|
|
84
85
|
invalidObjectName: /invalid object name: (.*)\s/i,
|
|
85
86
|
invalidObjectNameList: /could not open object name list: (.*)\s/i,
|
|
87
|
+
// Regcomp failure reasons from `-G`/pathspec regex compilation (search); matched without a known
|
|
88
|
+
// prefix as a fallback when the reason appears in an unrecognized stderr shape
|
|
89
|
+
invalidSearchPattern:
|
|
90
|
+
/Unmatched \( or \\\(|Unmatched \[|Unmatched \) or \\\)|Invalid content of \\\{\\\}|Invalid preceding regular expression|Invalid regular expression|Trailing backslash|invalid regex/i,
|
|
86
91
|
invalidTagName: /invalid tag name/i,
|
|
87
92
|
mainWorkingTree: /is a main working tree/i,
|
|
88
93
|
mergeAborted: /merge.*aborted/i,
|
|
@@ -100,6 +105,7 @@ export const GitErrors = {
|
|
|
100
105
|
/no merge (?:in progress|to abort)|no cherry-pick(?: or revert)? in progress|no rebase in progress/i,
|
|
101
106
|
permissionDenied: /Permission.*denied/i,
|
|
102
107
|
previousOperationEmpty: /The previous (?:cherry-pick|revert) is now empty/i,
|
|
108
|
+
problemWithEditor: /there was a problem with the editor/i,
|
|
103
109
|
pushRejected: /^error:\s*failed to push some refs to\b/m,
|
|
104
110
|
pushRejectedRefDoesNotExists: /error:\s*unable to delete '(.*?)': remote ref does not exist/m,
|
|
105
111
|
rebaseAborted: /Nothing to do|rebase.*aborted/i,
|
|
@@ -277,6 +283,9 @@ const errorToReasonMap = new Map<GitCommand, [RegExp, GitCommandToReasonMap[GitC
|
|
|
277
283
|
[
|
|
278
284
|
'paused-operation-continue',
|
|
279
285
|
[
|
|
286
|
+
// Kept FIRST: an editor failure's stderr can also carry broader messages ("could not
|
|
287
|
+
// commit staged changes", …) that later patterns would otherwise claim
|
|
288
|
+
[GitErrors.problemWithEditor, 'messageEditFailed'],
|
|
280
289
|
[GitErrors.previousOperationEmpty, 'emptyCommit'],
|
|
281
290
|
[GitErrors.noPausedOperation, 'nothingToContinue'],
|
|
282
291
|
[GitErrors.uncommittedChanges, 'uncommittedChanges'],
|
|
@@ -316,8 +325,11 @@ const errorToReasonMap = new Map<GitCommand, [RegExp, GitCommandToReasonMap[GitC
|
|
|
316
325
|
[
|
|
317
326
|
[GitErrors.remoteAhead, 'remoteAhead'],
|
|
318
327
|
[GitWarnings.tipBehind, 'tipBehind'],
|
|
319
|
-
|
|
328
|
+
// Kept BEFORE the generic push rejection: a failed `push -d` of a missing ref reports both
|
|
329
|
+
// "unable to delete '<ref>': remote ref does not exist" and "failed to push some refs to ...",
|
|
330
|
+
// and the specific reason is the actionable one.
|
|
320
331
|
[GitErrors.pushRejectedRefDoesNotExists, 'rejectedRefDoesNotExist'],
|
|
332
|
+
[GitErrors.pushRejected, 'rejected'],
|
|
321
333
|
[GitErrors.permissionDenied, 'permissionDenied'],
|
|
322
334
|
[GitErrors.remoteConnectionFailed, 'remoteConnectionFailed'],
|
|
323
335
|
[GitErrors.noUpstream, 'noUpstream'],
|
|
@@ -383,6 +395,7 @@ const errorToReasonMap = new Map<GitCommand, [RegExp, GitCommandToReasonMap[GitC
|
|
|
383
395
|
[GitErrors.tagNotFound, 'notFound'],
|
|
384
396
|
[GitErrors.permissionDenied, 'permissionDenied'],
|
|
385
397
|
[GitErrors.remoteRejected, 'remoteRejected'],
|
|
398
|
+
[GitErrors.tagConflict, 'tagConflict'],
|
|
386
399
|
],
|
|
387
400
|
],
|
|
388
401
|
[
|
|
@@ -469,6 +482,44 @@ export function inferSigningFormatFromError(ex: unknown): SigningFormat | undefi
|
|
|
469
482
|
return undefined;
|
|
470
483
|
}
|
|
471
484
|
|
|
485
|
+
export interface SearchErrorClassification {
|
|
486
|
+
reason: GitSearchErrorReason;
|
|
487
|
+
detail?: string;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Classifies a graph search failure from its stderr into an {@link GitSearchErrorReason}, with the
|
|
492
|
+
* offending pattern/ref text (when git reports one) as `detail`. Returns `undefined` when the error
|
|
493
|
+
* isn't a `GitError` with stderr, or the stderr doesn't match a recognized shape.
|
|
494
|
+
*/
|
|
495
|
+
export function classifySearchError(ex: unknown): SearchErrorClassification | undefined {
|
|
496
|
+
if (!(ex instanceof GitError) || !ex.stderr) return undefined;
|
|
497
|
+
|
|
498
|
+
const stderr = ex.stderr;
|
|
499
|
+
|
|
500
|
+
// `-G<pattern>`/pathspec regex compile failures report one of two shapes:
|
|
501
|
+
// fatal: command line, '<pattern>': <regcomp reason>
|
|
502
|
+
// fatal: invalid regex: <regcomp reason>
|
|
503
|
+
// The pattern itself may contain "': ", so anchor on the known prefixes and let the greedy `.*`
|
|
504
|
+
// resolve to the LAST matching delimiter rather than splitting on the first occurrence.
|
|
505
|
+
const patternDetail =
|
|
506
|
+
/^fatal:\s*command line,\s*'.*':\s*(.+)$/im.exec(stderr)?.[1] ??
|
|
507
|
+
/^fatal:\s*invalid regex:\s*(.+)$/im.exec(stderr)?.[1];
|
|
508
|
+
if (patternDetail != null) return { reason: 'invalidPattern', detail: patternDetail.trim() };
|
|
509
|
+
|
|
510
|
+
if (GitErrors.invalidSearchPattern.test(stderr)) return { reason: 'invalidPattern' };
|
|
511
|
+
|
|
512
|
+
const ambiguous = GitErrors.ambiguousArgument.exec(stderr);
|
|
513
|
+
if (ambiguous != null) return { reason: 'invalidRef', detail: ambiguous[1] };
|
|
514
|
+
|
|
515
|
+
const badRevision = GitErrors.badRevision.exec(stderr);
|
|
516
|
+
if (badRevision != null) return { reason: 'invalidRef', detail: badRevision[1] };
|
|
517
|
+
|
|
518
|
+
if (/unknown revision/i.test(stderr)) return { reason: 'invalidRef' };
|
|
519
|
+
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
|
|
472
523
|
export interface GitOptions {
|
|
473
524
|
/** Custom environment variables to add to every git command */
|
|
474
525
|
env?: Record<string, string | undefined>;
|
|
@@ -504,6 +555,12 @@ export interface GitHooks {
|
|
|
504
555
|
queued: Record<GitCommandPriority, number>;
|
|
505
556
|
maxConcurrent: number;
|
|
506
557
|
}): void;
|
|
558
|
+
/**
|
|
559
|
+
* Called once per git command that ran slowly (over the slow-call threshold). Fired only for the
|
|
560
|
+
* command that actually executed, never for a deduplicated rider that merely awaited it, so a single
|
|
561
|
+
* slow subprocess counts once. `operation` is the primary git subcommand (e.g. `status`, `rev-list`).
|
|
562
|
+
*/
|
|
563
|
+
onSlowCommand?(info: { operation: string | undefined; cwd: string | undefined; duration: number }): void;
|
|
507
564
|
}
|
|
508
565
|
|
|
509
566
|
const emptyArray: readonly never[] = Object.freeze([]);
|
|
@@ -709,6 +766,7 @@ export class Git {
|
|
|
709
766
|
errors: errorHandling,
|
|
710
767
|
encoding,
|
|
711
768
|
runLocally: _,
|
|
769
|
+
selfMaintenance,
|
|
712
770
|
...opts
|
|
713
771
|
} = options;
|
|
714
772
|
|
|
@@ -726,6 +784,9 @@ export class Git {
|
|
|
726
784
|
options?.stdin != null ? `${uniqueCounterForStdin.next()}:` : ''
|
|
727
785
|
}${cancellation != null ? `${getAbortSignalId(cancellation)}:` : ''}${gitCommand}`;
|
|
728
786
|
|
|
787
|
+
// When the subprocess actually started — `start` includes GitQueue wait, which is congestion rather
|
|
788
|
+
// than anything the repository did. Stays undefined for a dedup rider or a command aborted while queued.
|
|
789
|
+
let execStart: ReturnType<typeof hrtime> | undefined;
|
|
729
790
|
let waiting;
|
|
730
791
|
let promise = this.pendingCommands.get(cacheKey);
|
|
731
792
|
if (promise == null) {
|
|
@@ -758,7 +819,14 @@ export class Git {
|
|
|
758
819
|
// Execute through the queue (interactive/normal run immediately, background is throttled)
|
|
759
820
|
const gitPath = await this.path();
|
|
760
821
|
void this._queue
|
|
761
|
-
.run(
|
|
822
|
+
.run(
|
|
823
|
+
priority,
|
|
824
|
+
() => {
|
|
825
|
+
execStart = hrtime();
|
|
826
|
+
return runSpawn<T>(gitPath, args, encoding ?? 'utf8', runOpts);
|
|
827
|
+
},
|
|
828
|
+
cancellation,
|
|
829
|
+
)
|
|
762
830
|
.then(deferred.fulfill, (e: unknown) => deferred.cancel(e instanceof Error ? e : new Error(String(e))))
|
|
763
831
|
.finally(() => {
|
|
764
832
|
this.pendingCommands.delete(cacheKey);
|
|
@@ -908,7 +976,17 @@ export class Git {
|
|
|
908
976
|
completion: { status: 'warned', warning: warning, error: swallowed },
|
|
909
977
|
};
|
|
910
978
|
} finally {
|
|
911
|
-
this.logGitCommandComplete(
|
|
979
|
+
this.logGitCommandComplete(
|
|
980
|
+
gitCommand,
|
|
981
|
+
exception,
|
|
982
|
+
getDurationMilliseconds(start),
|
|
983
|
+
// 0 when the subprocess never started — there is no execution time to attribute. Also 0 for
|
|
984
|
+
// GitLens's own maintenance work, which is slow by design and must not read as repo slowness.
|
|
985
|
+
execStart == null || selfMaintenance ? 0 : getDurationMilliseconds(execStart),
|
|
986
|
+
waiting,
|
|
987
|
+
options.cwd,
|
|
988
|
+
args,
|
|
989
|
+
);
|
|
912
990
|
}
|
|
913
991
|
}
|
|
914
992
|
|
|
@@ -1013,7 +1091,20 @@ export class Git {
|
|
|
1013
1091
|
try {
|
|
1014
1092
|
proc.removeAllListeners();
|
|
1015
1093
|
} catch {}
|
|
1016
|
-
|
|
1094
|
+
// Streaming spawns directly, with no GitQueue wait to exclude. It isn't pure subprocess time
|
|
1095
|
+
// either — the generator applies backpressure, so a slow consumer stalls the pipe and inflates
|
|
1096
|
+
// this — but there's no separate signal to measure here.
|
|
1097
|
+
const duration = getDurationMilliseconds(start);
|
|
1098
|
+
this.logGitCommandComplete(
|
|
1099
|
+
gitCommand,
|
|
1100
|
+
exception,
|
|
1101
|
+
duration,
|
|
1102
|
+
duration,
|
|
1103
|
+
false,
|
|
1104
|
+
spawnOpts.cwd as string | undefined,
|
|
1105
|
+
runArgs,
|
|
1106
|
+
streamId,
|
|
1107
|
+
);
|
|
1017
1108
|
};
|
|
1018
1109
|
|
|
1019
1110
|
try {
|
|
@@ -1175,12 +1266,35 @@ export class Git {
|
|
|
1175
1266
|
command: string,
|
|
1176
1267
|
ex: Error | undefined,
|
|
1177
1268
|
duration: number,
|
|
1269
|
+
/** Subprocess-only duration (excludes GitQueue wait); 0 when it never ran. */
|
|
1270
|
+
execDuration: number,
|
|
1178
1271
|
waiting: boolean,
|
|
1272
|
+
cwd: string | undefined,
|
|
1273
|
+
args: readonly (string | undefined)[] | undefined,
|
|
1179
1274
|
id?: number,
|
|
1180
1275
|
): void {
|
|
1181
1276
|
const slow = duration > slowCallWarningThreshold;
|
|
1182
1277
|
const status = slow && waiting ? ' (slow, waiting)' : waiting ? ' (waiting)' : slow ? ' (slow)' : '';
|
|
1183
1278
|
|
|
1279
|
+
// The health signal is gated on the SUBPROCESS time, not the total: time spent queued behind other
|
|
1280
|
+
// git work is congestion, not repository slowness, and counting it would let a busy queue (including
|
|
1281
|
+
// our own background maintenance holding slots) manufacture the evidence that recommends more
|
|
1282
|
+
// maintenance. Logging keeps the total — the wait is exactly what you want to see when diagnosing.
|
|
1283
|
+
//
|
|
1284
|
+
// A deduplicated rider (`waiting`) shares the executed command's duration, so counting it too
|
|
1285
|
+
// would double-count a single slow subprocess — fire only for the command that actually ran.
|
|
1286
|
+
// Guarded: this runs inside the command's `finally`, so a hook throw would otherwise replace
|
|
1287
|
+
// the command's real result/error for the caller.
|
|
1288
|
+
if (execDuration > slowCallWarningThreshold && !waiting) {
|
|
1289
|
+
try {
|
|
1290
|
+
this.options.hooks?.onSlowCommand?.({
|
|
1291
|
+
operation: args != null ? getPrimaryGitCommand(args) : undefined,
|
|
1292
|
+
cwd: cwd,
|
|
1293
|
+
duration: execDuration,
|
|
1294
|
+
});
|
|
1295
|
+
} catch {}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1184
1298
|
if (ex != null) {
|
|
1185
1299
|
Logger.error(
|
|
1186
1300
|
undefined,
|
|
@@ -278,20 +278,8 @@ const optionsTakingValue = new Set([
|
|
|
278
278
|
'--list-cmds',
|
|
279
279
|
]);
|
|
280
280
|
|
|
281
|
-
/**
|
|
282
|
-
|
|
283
|
-
*
|
|
284
|
-
* Intentionally narrow: only returns 'background' for commands that are
|
|
285
|
-
* *always* read-only AND *always* potentially expensive. Polymorphic commands
|
|
286
|
-
* like `log` and `rev-list` (which can be quick bounded lookups or full-history
|
|
287
|
-
* walks) are deliberately omitted — call sites that perform heavy walks must
|
|
288
|
-
* pass `priority: 'background'` explicitly. Never upgrades to 'interactive'.
|
|
289
|
-
*
|
|
290
|
-
* No subcommand awareness (e.g. `stash list`, `branch -l`): the risk of
|
|
291
|
-
* misclassifying a write subcommand as background outweighs the gain.
|
|
292
|
-
*/
|
|
293
|
-
export function inferGitCommandPriority(args: readonly (string | undefined)[]): GitCommandPriority {
|
|
294
|
-
let command: string | undefined;
|
|
281
|
+
/** Extracts the primary git subcommand from an arg list, skipping global options and their values. */
|
|
282
|
+
export function getPrimaryGitCommand(args: readonly (string | undefined)[]): string | undefined {
|
|
295
283
|
for (let i = 0; i < args.length; i++) {
|
|
296
284
|
const a = args[i];
|
|
297
285
|
if (a == null) continue;
|
|
@@ -304,11 +292,25 @@ export function inferGitCommandPriority(args: readonly (string | undefined)[]):
|
|
|
304
292
|
}
|
|
305
293
|
if (a.startsWith('-')) continue;
|
|
306
294
|
|
|
307
|
-
|
|
308
|
-
break;
|
|
295
|
+
return a;
|
|
309
296
|
}
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
310
299
|
|
|
311
|
-
|
|
300
|
+
/**
|
|
301
|
+
* Infers a queue priority from the git command being invoked.
|
|
302
|
+
*
|
|
303
|
+
* Intentionally narrow: only returns 'background' for commands that are
|
|
304
|
+
* *always* read-only AND *always* potentially expensive. Polymorphic commands
|
|
305
|
+
* like `log` and `rev-list` (which can be quick bounded lookups or full-history
|
|
306
|
+
* walks) are deliberately omitted — call sites that perform heavy walks must
|
|
307
|
+
* pass `priority: 'background'` explicitly. Never upgrades to 'interactive'.
|
|
308
|
+
*
|
|
309
|
+
* No subcommand awareness (e.g. `stash list`, `branch -l`): the risk of
|
|
310
|
+
* misclassifying a write subcommand as background outweighs the gain.
|
|
311
|
+
*/
|
|
312
|
+
export function inferGitCommandPriority(args: readonly (string | undefined)[]): GitCommandPriority {
|
|
313
|
+
switch (getPrimaryGitCommand(args)) {
|
|
312
314
|
case 'for-each-ref':
|
|
313
315
|
case 'shortlog':
|
|
314
316
|
case 'reflog':
|
|
@@ -2,7 +2,7 @@ import type { GitFileIndexStatus } from '../../git/models/fileStatus.js';
|
|
|
2
2
|
import { diffHunkRegex, diffRegex } from '../../git/parsers/diffParser.js';
|
|
3
3
|
import { joinPaths, normalizePath } from '../../utils/path.js';
|
|
4
4
|
import { maybeStopWatch } from '../../utils/stopwatch.js';
|
|
5
|
-
import { iterateAsyncByDelimiter, iterateByDelimiter } from '../../utils/string.js';
|
|
5
|
+
import { flattenString, iterateAsyncByDelimiter, iterateByDelimiter } from '../../utils/string.js';
|
|
6
6
|
import type { ExtractAll } from '../../utils/types.js';
|
|
7
7
|
|
|
8
8
|
const commitsMapping = {
|
|
@@ -389,6 +389,9 @@ export function createLogParser<T extends Record<string, string>>(mapping: Extra
|
|
|
389
389
|
// semantics exactly: fields beyond the mapping are ignored, and every mapped field is
|
|
390
390
|
// terminated by a field separator (the format string appends one after each field).
|
|
391
391
|
function parseRecord(record: string): LogParsedEntry<T> {
|
|
392
|
+
// Fields sliced out of the record become long-lived (graph rows); flatten the record so they
|
|
393
|
+
// pin a record-sized copy rather than the full walk output the record was sliced from.
|
|
394
|
+
record = flattenString(record);
|
|
392
395
|
const entry = {} as unknown as LogParsedEntry<T>;
|
|
393
396
|
let start = 0;
|
|
394
397
|
for (const key of keys) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stat } from 'fs/promises';
|
|
2
2
|
import type { Cache } from '../../git/cache.js';
|
|
3
|
+
import { shouldEvictBlameCacheEntry } from '../../git/cache.js';
|
|
3
4
|
import { BlameIgnoreRevsFileBadRevisionError, BlameIgnoreRevsFileError } from '../../git/errors.js';
|
|
4
5
|
import type {
|
|
5
6
|
GitBlame,
|
|
@@ -75,8 +76,9 @@ export class BlameGitSubProvider implements GitBlameSubProvider {
|
|
|
75
76
|
return this.cache.blame.getOrCreate(
|
|
76
77
|
repoPath,
|
|
77
78
|
cacheKey,
|
|
78
|
-
() => this.getProgressiveBlameCore(repoPath, path,
|
|
79
|
+
() => this.getProgressiveBlameCore(repoPath, path, rev, contents, options),
|
|
79
80
|
{
|
|
81
|
+
evictWhen: shouldEvictBlameCacheEntry,
|
|
80
82
|
onError: ex => {
|
|
81
83
|
if (ex instanceof BlameIgnoreRevsFileError || ex instanceof BlameIgnoreRevsFileBadRevisionError) {
|
|
82
84
|
// Non-retriable — cache as undefined for 5 minutes
|
|
@@ -92,7 +94,6 @@ export class BlameGitSubProvider implements GitBlameSubProvider {
|
|
|
92
94
|
private async getProgressiveBlameCore(
|
|
93
95
|
repoPath: string,
|
|
94
96
|
path: string,
|
|
95
|
-
cacheKey: string,
|
|
96
97
|
rev?: string,
|
|
97
98
|
contents?: string,
|
|
98
99
|
options?: GitBlameOptions,
|
|
@@ -112,12 +113,6 @@ export class BlameGitSubProvider implements GitBlameSubProvider {
|
|
|
112
113
|
// Start streaming in the background
|
|
113
114
|
void this.streamBlame(writer, repoPath, path, blameOptions, user, mtime);
|
|
114
115
|
|
|
115
|
-
// If streaming fails, progressive.completed rejects. Evict the cache entry
|
|
116
|
-
// so the next call retries instead of serving the failed GitBlameProgressive.
|
|
117
|
-
void progressive.completed.catch(() => {
|
|
118
|
-
this.cache.blame.delete(repoPath, cacheKey);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
116
|
return progressive;
|
|
122
117
|
}
|
|
123
118
|
|
|
@@ -938,13 +938,21 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
938
938
|
});
|
|
939
939
|
}
|
|
940
940
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
gitCommand: { repoPath: repoPath, args: args },
|
|
946
|
-
},
|
|
941
|
+
// Classify the failure (e.g. not fully merged) so consumers like the delete wizard can offer a
|
|
942
|
+
// force retry — without this the reason would be undefined and every failure reads as 'other'.
|
|
943
|
+
throw getGitCommandError(
|
|
944
|
+
'branch',
|
|
947
945
|
ex,
|
|
946
|
+
reason =>
|
|
947
|
+
new BranchError(
|
|
948
|
+
{
|
|
949
|
+
reason: reason ?? 'other',
|
|
950
|
+
action: options?.force ? 'force delete' : 'delete',
|
|
951
|
+
branch: branches.join(', '),
|
|
952
|
+
gitCommand: { repoPath: repoPath, args: args },
|
|
953
|
+
},
|
|
954
|
+
ex,
|
|
955
|
+
),
|
|
948
956
|
);
|
|
949
957
|
}
|
|
950
958
|
}
|
|
@@ -1022,9 +1030,21 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1022
1030
|
});
|
|
1023
1031
|
}
|
|
1024
1032
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1033
|
+
// Classify against the underlying `git push -d` failure modes; map the "remote ref does not
|
|
1034
|
+
// exist" case onto BranchError's noRemoteReference so consumers get an actionable reason.
|
|
1035
|
+
throw getGitCommandError(
|
|
1036
|
+
'push',
|
|
1027
1037
|
ex,
|
|
1038
|
+
reason =>
|
|
1039
|
+
new BranchError(
|
|
1040
|
+
{
|
|
1041
|
+
reason: reason === 'rejectedRefDoesNotExist' ? 'noRemoteReference' : 'other',
|
|
1042
|
+
action: 'delete',
|
|
1043
|
+
branch: branches.join(', '),
|
|
1044
|
+
gitCommand: { repoPath: repoPath, args: args },
|
|
1045
|
+
},
|
|
1046
|
+
ex,
|
|
1047
|
+
),
|
|
1028
1048
|
);
|
|
1029
1049
|
}
|
|
1030
1050
|
}
|
|
@@ -1383,6 +1383,12 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
|
|
|
1383
1383
|
search = (await this.context.searchQuery?.preprocessQuery?.(search, options?.source)) ?? search;
|
|
1384
1384
|
}
|
|
1385
1385
|
|
|
1386
|
+
// The conversion failed — `search.query` is still the raw English sentence, not a query; running
|
|
1387
|
+
// it as an ERE would produce a regex error about text the user never wrote.
|
|
1388
|
+
if (typeof search.naturalLanguage === 'object' && search.naturalLanguage.error) {
|
|
1389
|
+
return { search: search, log: undefined };
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1386
1392
|
try {
|
|
1387
1393
|
const cfg = this.context.config;
|
|
1388
1394
|
const currentUser = await this.provider.config.getCurrentUser(repoPath);
|
|
@@ -1399,6 +1405,11 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
|
|
|
1399
1405
|
];
|
|
1400
1406
|
|
|
1401
1407
|
const { args: searchArgs, files, shas, filters } = parseSearchQueryGitCommand(search, currentUser);
|
|
1408
|
+
// Stash commits have 2-3 parents, so `--merges` matches them; excluded in parseCommits.
|
|
1409
|
+
// Derived from the args git actually runs rather than `filters.type`, since a multi-value
|
|
1410
|
+
// `type:` query (e.g. `type:merge type:tip`) leaves `--merges` in the args while
|
|
1411
|
+
// `filters.type` reflects only the last value parsed
|
|
1412
|
+
const mergesOnly = searchArgs.includes('--merges');
|
|
1402
1413
|
|
|
1403
1414
|
let stashes: Map<string, GitStashCommit> | undefined;
|
|
1404
1415
|
let stdin: string | undefined;
|
|
@@ -1431,9 +1442,22 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
|
|
|
1431
1442
|
} else if (!filters.refs) {
|
|
1432
1443
|
// Don't include stashes when using ref: filter, as they would add unrelated commits
|
|
1433
1444
|
// There *HAS* to be a better way to get git log to return stashes, but this is the best we've found
|
|
1434
|
-
|
|
1445
|
+
const converted = convertStashesToStdin(
|
|
1446
|
+
await this.provider.stash?.getStash(repoPath, undefined, cancellation),
|
|
1447
|
+
);
|
|
1448
|
+
stashes = converted.stashes;
|
|
1449
|
+
// `--all` already surfaces refs/stash's tip on its own, so when merges-only there's no need
|
|
1450
|
+
// to walk the rest of the stash stack via stdin -- parseCommits still excludes any stash
|
|
1451
|
+
// sha that slips through via refs/stash
|
|
1452
|
+
stdin = mergesOnly ? undefined : converted.stdin;
|
|
1453
|
+
} else if (mergesOnly) {
|
|
1454
|
+
// `ref:` skips walking stashes into the results (they're unrelated to the ref), but a
|
|
1455
|
+
// stash's tip can still leak in via `--merges` if the ref reaches refs/stash -- fetch
|
|
1456
|
+
// stashes here purely so parseCommits can exclude them, without widening the walk via
|
|
1457
|
+
// stdin
|
|
1458
|
+
stashes = convertStashesToStdin(
|
|
1435
1459
|
await this.provider.stash?.getStash(repoPath, undefined, cancellation),
|
|
1436
|
-
)
|
|
1460
|
+
).stashes;
|
|
1437
1461
|
}
|
|
1438
1462
|
|
|
1439
1463
|
if (stdin) {
|
|
@@ -1481,6 +1505,7 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
|
|
|
1481
1505
|
stashes,
|
|
1482
1506
|
currentUser,
|
|
1483
1507
|
filters,
|
|
1508
|
+
mergesOnly,
|
|
1484
1509
|
);
|
|
1485
1510
|
|
|
1486
1511
|
const log: GitLog = {
|
|
@@ -1710,6 +1735,11 @@ async function parseCommits(
|
|
|
1710
1735
|
stashes: Map<string, GitStashCommit> | undefined,
|
|
1711
1736
|
currentUser: GitUser | undefined,
|
|
1712
1737
|
searchFilters?: SearchQueryFilters,
|
|
1738
|
+
// Stash commits have 2-3 parents, so `--merges` matches them; excluded below. Passed in by the
|
|
1739
|
+
// caller (derived from the args git actually runs) rather than re-derived from `searchFilters.type`
|
|
1740
|
+
// here, since a multi-value `type:` query (e.g. `type:merge type:tip`) leaves `--merges` in the
|
|
1741
|
+
// args while `searchFilters.type` reflects only the last value parsed
|
|
1742
|
+
mergesOnly: boolean = false,
|
|
1713
1743
|
): Promise<{ commits: Map<string, GitCommit>; count: number; countStashChildCommits: number }> {
|
|
1714
1744
|
let count = 0;
|
|
1715
1745
|
let countStashChildCommits = 0;
|
|
@@ -1729,6 +1759,7 @@ async function parseCommits(
|
|
|
1729
1759
|
|
|
1730
1760
|
for (const c of parser.parse(result.stdout)) {
|
|
1731
1761
|
if (stashesOnly && !stashes?.has(c.sha)) continue;
|
|
1762
|
+
if (mergesOnly && stashes?.has(c.sha)) continue;
|
|
1732
1763
|
if (tipsOnly && !c.tips) continue;
|
|
1733
1764
|
|
|
1734
1765
|
count++;
|
|
@@ -1781,6 +1812,7 @@ async function parseCommits(
|
|
|
1781
1812
|
|
|
1782
1813
|
for await (const c of parser.parseAsync(resultOrStream)) {
|
|
1783
1814
|
if (stashesOnly && !stashes?.has(c.sha)) continue;
|
|
1815
|
+
if (mergesOnly && stashes?.has(c.sha)) continue;
|
|
1784
1816
|
if (tipsOnly && !c.tips) continue;
|
|
1785
1817
|
|
|
1786
1818
|
count++;
|
|
@@ -36,7 +36,7 @@ const gkConfigAllPattern = '.';
|
|
|
36
36
|
* section and variable-name lowercased and only the subsection case-preserved. Without this a
|
|
37
37
|
* subsection-less key like `gk.defaultRemote` would miss the lowercased `gk.defaultremote` in the map.
|
|
38
38
|
*/
|
|
39
|
-
function canonicalizeGitConfigKey(key: string): string {
|
|
39
|
+
export function canonicalizeGitConfigKey(key: string): string {
|
|
40
40
|
const first = key.indexOf('.');
|
|
41
41
|
if (first === -1) return key.toLowerCase();
|
|
42
42
|
|
|
@@ -50,8 +50,22 @@ function canonicalizeGitConfigKey(key: string): string {
|
|
|
50
50
|
/**
|
|
51
51
|
* Parses git config --get-regex output into a Map.
|
|
52
52
|
* The output format is "key value" per line, where key and value are space-separated.
|
|
53
|
+
*
|
|
54
|
+
* A line with NO space at all is git's rendering of a bareword/valueless entry (`key`, no `=`) — git's
|
|
55
|
+
* OWN config parser reads that as boolean TRUE (git-config(1): "a variable defined without `= <value>`
|
|
56
|
+
* is taken as true"), confirmed against git's `format_config` (builtin/config.c): the key-delimiter is
|
|
57
|
+
* appended unconditionally, then backed out ONLY when the stored value is NULL (the bareword case) —
|
|
58
|
+
* an explicit empty value (`key =`) is a real, non-NULL empty string, so its line KEEPS the trailing
|
|
59
|
+
* space and is already handled by the branch below, byte-distinct from a bareword line.
|
|
60
|
+
*
|
|
61
|
+
* By default a bareword line is skipped (the historical behavior, which reads as "unset" — wrong, but
|
|
62
|
+
* safe); pass `includeValueless` to map it to the literal string `'true'` instead, so `parseGitBoolean`
|
|
63
|
+
* and callers checking `.has()` for "configured" both read it correctly.
|
|
53
64
|
*/
|
|
54
|
-
function parseConfigRegexOutput(
|
|
65
|
+
export function parseConfigRegexOutput(
|
|
66
|
+
data: string | undefined,
|
|
67
|
+
options?: { includeValueless?: boolean },
|
|
68
|
+
): Map<string, string> {
|
|
55
69
|
const configMap = new Map<string, string>();
|
|
56
70
|
if (!data) return configMap;
|
|
57
71
|
|
|
@@ -59,7 +73,12 @@ function parseConfigRegexOutput(data: string | undefined): Map<string, string> {
|
|
|
59
73
|
if (!line) continue;
|
|
60
74
|
|
|
61
75
|
const spaceIndex = line.indexOf(' ');
|
|
62
|
-
if (spaceIndex === -1)
|
|
76
|
+
if (spaceIndex === -1) {
|
|
77
|
+
if (options?.includeValueless) {
|
|
78
|
+
configMap.set(line, 'true');
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
63
82
|
|
|
64
83
|
configMap.set(line.substring(0, spaceIndex), line.substring(spaceIndex + 1));
|
|
65
84
|
}
|
|
@@ -67,13 +86,21 @@ function parseConfigRegexOutput(data: string | undefined): Map<string, string> {
|
|
|
67
86
|
return configMap;
|
|
68
87
|
}
|
|
69
88
|
|
|
70
|
-
function parseGitBoolean(value: string | undefined): boolean {
|
|
89
|
+
export function parseGitBoolean(value: string | undefined): boolean {
|
|
71
90
|
if (value == null) return false;
|
|
72
91
|
|
|
73
92
|
const normalized = value.toLowerCase().trim();
|
|
74
93
|
return normalized === 'true' || normalized === 'yes' || normalized === 'on' || normalized === '1';
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
/** Whether a git config value spells boolean-false (`false`/`no`/`off`/`0`/empty), per git's maybe-bool parsing. */
|
|
97
|
+
export function isGitBooleanFalse(value: string): boolean {
|
|
98
|
+
const normalized = value.toLowerCase().trim();
|
|
99
|
+
return (
|
|
100
|
+
normalized === '' || normalized === 'false' || normalized === 'no' || normalized === 'off' || normalized === '0'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
77
104
|
export class ConfigGitSubProvider implements GitConfigSubProvider {
|
|
78
105
|
constructor(
|
|
79
106
|
private readonly context: GitServiceContext,
|
|
@@ -163,7 +190,10 @@ export class ConfigGitSubProvider implements GitConfigSubProvider {
|
|
|
163
190
|
{ cwd: repoPath ?? '', errors: 'ignore', runLocally: options?.runGitLocally },
|
|
164
191
|
...args,
|
|
165
192
|
);
|
|
166
|
-
|
|
193
|
+
// Deliberately NOT `.trim()`-ed: an explicit-empty value's LAST line ends in a meaningful trailing
|
|
194
|
+
// space, and `.trim()`-ing the whole (possibly multi-line) buffer strips it — `parseConfigRegexOutput`
|
|
195
|
+
// already skips the blank line a trailing newline produces, so no trim is needed.
|
|
196
|
+
return parseConfigRegexOutput(result.stdout);
|
|
167
197
|
}
|
|
168
198
|
|
|
169
199
|
@debug()
|
|
@@ -400,7 +430,9 @@ export class ConfigGitSubProvider implements GitConfigSubProvider {
|
|
|
400
430
|
// `--get-regex` exits 1 when nothing matches, which IS an answer (an empty gk config).
|
|
401
431
|
const exited = result.completion.status === 'exited';
|
|
402
432
|
return {
|
|
403
|
-
|
|
433
|
+
// Deliberately NOT `.trim()`-ed — see `getConfigRegexCore`'s comment: it strips the trailing
|
|
434
|
+
// space that marks an explicit-empty value's LAST line, silently dropping it from the map.
|
|
435
|
+
map: parseConfigRegexOutput(result.stdout),
|
|
404
436
|
failed: !exited || (result.exitCode !== 0 && result.exitCode !== 1),
|
|
405
437
|
};
|
|
406
438
|
}
|