@gitkraken/core-gitlens 0.5.110 → 0.5.111
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 +16 -1
- package/dist/git/cache.d.ts +111 -6
- package/dist/git/cache.d.ts.map +1 -1
- package/dist/git/cache.js +312 -43
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +11 -0
- package/dist/git/context.d.ts.map +1 -1
- package/dist/git/context.js.map +1 -1
- package/dist/git/models/fileStatus.d.ts +1 -0
- package/dist/git/models/fileStatus.d.ts.map +1 -1
- package/dist/git/models/fileStatus.js +1 -0
- package/dist/git/models/fileStatus.js.map +1 -1
- package/dist/git/models/issue.d.ts +4 -0
- package/dist/git/models/issue.d.ts.map +1 -1
- package/dist/git/models/mergeConflicts.d.ts +2 -0
- package/dist/git/models/mergeConflicts.d.ts.map +1 -1
- package/dist/git/models/pullRequest.d.ts +38 -2
- package/dist/git/models/pullRequest.d.ts.map +1 -1
- package/dist/git/models/pullRequest.js +12 -1
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git/models/repository.d.ts +32 -5
- package/dist/git/models/repository.d.ts.map +1 -1
- package/dist/git/models/repository.js +58 -12
- package/dist/git/models/repository.js.map +1 -1
- package/dist/git/models/statusFile.d.ts.map +1 -1
- package/dist/git/models/statusFile.js +3 -0
- package/dist/git/models/statusFile.js.map +1 -1
- package/dist/git/providers/branches.d.ts +2 -0
- package/dist/git/providers/branches.d.ts.map +1 -1
- package/dist/git/providers/commits.d.ts +8 -0
- package/dist/git/providers/commits.d.ts.map +1 -1
- package/dist/git/providers/config.d.ts +19 -1
- package/dist/git/providers/config.d.ts.map +1 -1
- package/dist/git/providers/operations.d.ts +1 -0
- package/dist/git/providers/operations.d.ts.map +1 -1
- package/dist/git/providers/pausedOperations.d.ts +2 -0
- package/dist/git/providers/pausedOperations.d.ts.map +1 -1
- package/dist/git/providers/status.d.ts +1 -0
- package/dist/git/providers/status.d.ts.map +1 -1
- package/dist/git/utils/config.utils.d.ts +3 -0
- package/dist/git/utils/config.utils.d.ts.map +1 -0
- package/dist/git/utils/config.utils.js +8 -0
- package/dist/git/utils/config.utils.js.map +1 -0
- package/dist/git/utils/issue.utils.d.ts.map +1 -1
- package/dist/git/utils/issue.utils.js +2 -0
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git/utils/pausedOperationStatus.utils.d.ts +14 -0
- package/dist/git/utils/pausedOperationStatus.utils.d.ts.map +1 -1
- package/dist/git/utils/pausedOperationStatus.utils.js +23 -0
- package/dist/git/utils/pausedOperationStatus.utils.js.map +1 -1
- package/dist/git/utils/pullRequest.utils.d.ts +14 -1
- package/dist/git/utils/pullRequest.utils.d.ts.map +1 -1
- package/dist/git/utils/pullRequest.utils.js +31 -0
- package/dist/git/utils/pullRequest.utils.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +6 -1
- package/dist/git/watching/changeEvent.d.ts.map +1 -1
- package/dist/git/watching/classifyChange.d.ts.map +1 -1
- package/dist/git/watching/classifyChange.js +14 -1
- package/dist/git/watching/classifyChange.js.map +1 -1
- package/dist/git/watching/watchService.d.ts.map +1 -1
- package/dist/git/watching/watchService.js +18 -3
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +8 -1
- package/dist/git/watching/watchSession.d.ts.map +1 -1
- package/dist/git/watching/watchSession.js +38 -6
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +2 -2
- package/dist/git/watching/watcherPatterns.d.ts.map +1 -1
- package/dist/git/watching/watcherPatterns.js +1 -1
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +1 -1
- package/dist/git-cli/exec/git.d.ts.map +1 -1
- package/dist/git-cli/exec/git.js +13 -3
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +19 -0
- package/dist/git-cli/providers/branches.d.ts.map +1 -1
- package/dist/git-cli/providers/branches.js +282 -78
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.d.ts +4 -0
- package/dist/git-cli/providers/commits.d.ts.map +1 -1
- package/dist/git-cli/providers/commits.js +84 -11
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +42 -0
- package/dist/git-cli/providers/config.d.ts.map +1 -1
- package/dist/git-cli/providers/config.js +186 -5
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/contributors.d.ts.map +1 -1
- package/dist/git-cli/providers/contributors.js +1 -1
- package/dist/git-cli/providers/contributors.js.map +1 -1
- package/dist/git-cli/providers/graph.d.ts +2 -2
- package/dist/git-cli/providers/graph.d.ts.map +1 -1
- package/dist/git-cli/providers/graph.js +27 -9
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts.map +1 -1
- package/dist/git-cli/providers/operations.js +40 -12
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +5 -0
- package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
- package/dist/git-cli/providers/pausedOperations.js +87 -16
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.d.ts +5 -3
- package/dist/git-cli/providers/refs.d.ts.map +1 -1
- package/dist/git-cli/providers/refs.js +33 -4
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +1 -0
- package/dist/git-cli/providers/status.d.ts.map +1 -1
- package/dist/git-cli/providers/status.js +18 -3
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts.map +1 -1
- package/dist/git-cli/providers/worktrees.js +4 -0
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts.map +1 -1
- package/dist/git-cli/service.js.map +1 -1
- package/dist/plus/agents/providers/claudeCodeProvider.d.ts +39 -1
- package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +1 -1
- package/dist/plus/agents/providers/claudeCodeProvider.js +978 -71
- package/dist/plus/agents/providers/claudeCodeProvider.js.map +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +24 -2
- package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +84 -9
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/agents/types.d.ts +37 -2
- package/dist/plus/agents/types.d.ts.map +1 -1
- package/dist/plus/agents/types.js +2 -0
- package/dist/plus/agents/types.js.map +1 -1
- package/dist/plus/ai/errors.d.ts +10 -0
- package/dist/plus/ai/errors.d.ts.map +1 -1
- package/dist/plus/ai/errors.js +29 -0
- package/dist/plus/ai/errors.js.map +1 -1
- package/dist/plus/ai/models/model.d.ts +4 -0
- package/dist/plus/ai/models/model.d.ts.map +1 -1
- package/dist/plus/ai/models/model.js +51 -6
- package/dist/plus/ai/models/model.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/models/provider.d.ts +60 -2
- package/dist/plus/ai/models/provider.d.ts.map +1 -1
- package/dist/plus/ai/prompts.d.ts +12 -0
- package/dist/plus/ai/prompts.d.ts.map +1 -1
- package/dist/plus/ai/prompts.js +177 -61
- package/dist/plus/ai/prompts.js.map +1 -1
- package/dist/plus/ai/providers/anthropicProvider.d.ts +10 -2
- package/dist/plus/ai/providers/anthropicProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/anthropicProvider.js +139 -46
- package/dist/plus/ai/providers/anthropicProvider.js.map +1 -1
- package/dist/plus/ai/providers/deepSeekProvider.d.ts +2 -0
- package/dist/plus/ai/providers/deepSeekProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/deepSeekProvider.js +10 -21
- package/dist/plus/ai/providers/deepSeekProvider.js.map +1 -1
- package/dist/plus/ai/providers/geminiProvider.d.ts +1 -0
- package/dist/plus/ai/providers/geminiProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/geminiProvider.js +19 -0
- package/dist/plus/ai/providers/geminiProvider.js.map +1 -1
- package/dist/plus/ai/providers/gitkrakenProvider.d.ts +5 -1
- package/dist/plus/ai/providers/gitkrakenProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/gitkrakenProvider.js +118 -3
- package/dist/plus/ai/providers/gitkrakenProvider.js.map +1 -1
- package/dist/plus/ai/providers/huggingFaceProvider.d.ts +1 -0
- package/dist/plus/ai/providers/huggingFaceProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/huggingFaceProvider.js +5 -0
- package/dist/plus/ai/providers/huggingFaceProvider.js.map +1 -1
- package/dist/plus/ai/providers/mistralProvider.d.ts +3 -1
- package/dist/plus/ai/providers/mistralProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/mistralProvider.js +99 -19
- package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
- package/dist/plus/ai/providers/ollamaProvider.d.ts +4 -3
- package/dist/plus/ai/providers/ollamaProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/ollamaProvider.js +38 -3
- package/dist/plus/ai/providers/ollamaProvider.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProvider.d.ts +1 -0
- package/dist/plus/ai/providers/openAICompatibleProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProvider.js +4 -0
- package/dist/plus/ai/providers/openAICompatibleProvider.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts +154 -5
- package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +259 -10
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
- package/dist/plus/ai/providers/openRouterProvider.d.ts +4 -0
- package/dist/plus/ai/providers/openRouterProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/openRouterProvider.js +9 -0
- package/dist/plus/ai/providers/openRouterProvider.js.map +1 -1
- package/dist/plus/ai/providers/openaiProvider.d.ts +1 -0
- package/dist/plus/ai/providers/openaiProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/openaiProvider.js +1 -0
- package/dist/plus/ai/providers/openaiProvider.js.map +1 -1
- package/dist/plus/ai/providers/responseFormatCache.d.ts +8 -0
- package/dist/plus/ai/providers/responseFormatCache.d.ts.map +1 -0
- package/dist/plus/ai/providers/responseFormatCache.js +25 -0
- package/dist/plus/ai/providers/responseFormatCache.js.map +1 -0
- package/dist/plus/ai/providers/xaiProvider.d.ts +1 -0
- package/dist/plus/ai/providers/xaiProvider.d.ts.map +1 -1
- package/dist/plus/ai/providers/xaiProvider.js +10 -1
- package/dist/plus/ai/providers/xaiProvider.js.map +1 -1
- package/dist/plus/ai/utils/ai.utils.d.ts +3 -0
- package/dist/plus/ai/utils/ai.utils.d.ts.map +1 -1
- package/dist/plus/ai/utils/ai.utils.js +13 -0
- package/dist/plus/ai/utils/ai.utils.js.map +1 -1
- package/dist/plus/ai/utils/results.utils.d.ts +16 -2
- package/dist/plus/ai/utils/results.utils.d.ts.map +1 -1
- package/dist/plus/ai/utils/results.utils.js +204 -28
- package/dist/plus/ai/utils/results.utils.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +55 -0
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +177 -4
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/models.d.ts +16 -0
- package/dist/plus/git-github/models.d.ts.map +1 -1
- package/dist/plus/git-github/models.js +29 -3
- package/dist/plus/git-github/models.js.map +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +1 -0
- package/dist/plus/git-github/providers/github/status.d.ts.map +1 -1
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/integrations/context.d.ts +4 -0
- package/dist/plus/integrations/context.d.ts.map +1 -1
- package/dist/plus/integrations/index.d.ts.map +1 -1
- package/dist/plus/integrations/index.js +2 -0
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +6 -3
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +12 -2
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +3 -1
- package/dist/plus/integrations/models/integration.d.ts.map +1 -1
- package/dist/plus/integrations/models/integration.js +2 -2
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/providers/accounts.d.ts.map +1 -1
- package/dist/plus/integrations/providers/accounts.js +4 -0
- package/dist/plus/integrations/providers/accounts.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts.map +1 -1
- package/dist/plus/integrations/providers/azure/models.js +3 -0
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/models.js +1 -0
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +14 -2
- package/dist/plus/integrations/providers/github.d.ts.map +1 -1
- package/dist/plus/integrations/providers/github.js +55 -3
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts.map +1 -1
- package/dist/plus/integrations/providers/models.js +30 -4
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
- package/dist/plus/integrations/reads/broaden.js +6 -1
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
- package/dist/plus/integrations/reads/drains.js +7 -4
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.d.ts.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +3 -1
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +8 -3
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issues.js +9 -4
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.js +9 -4
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/utils/env/browser/base64.d.ts +1 -1
- package/dist/utils/env/browser/base64.d.ts.map +1 -1
- package/dist/utils/env/node/base64.d.ts +1 -1
- package/dist/utils/env/node/base64.d.ts.map +1 -1
- package/dist/utils/env/node/base64.js +2 -0
- package/dist/utils/env/node/base64.js.map +1 -1
- package/dist/utils/keys/chord.d.ts +52 -0
- package/dist/utils/keys/chord.d.ts.map +1 -0
- package/dist/utils/keys/chord.js +135 -0
- package/dist/utils/keys/chord.js.map +1 -0
- package/dist/utils/keys/keybinding.d.ts +53 -0
- package/dist/utils/keys/keybinding.d.ts.map +1 -0
- package/dist/utils/keys/keybinding.js +58 -0
- package/dist/utils/keys/keybinding.js.map +1 -0
- package/dist/utils/path.d.ts +11 -0
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +13 -0
- package/dist/utils/path.js.map +1 -1
- package/package.json +13 -5
- package/src/git/cache.ts +383 -47
- package/src/git/context.ts +11 -0
- package/src/git/models/fileStatus.ts +1 -0
- package/src/git/models/issue.ts +4 -0
- package/src/git/models/mergeConflicts.ts +3 -2
- package/src/git/models/pullRequest.ts +33 -1
- package/src/git/models/repository.ts +58 -17
- package/src/git/models/statusFile.ts +3 -0
- package/src/git/providers/branches.ts +6 -0
- package/src/git/providers/commits.ts +11 -0
- package/src/git/providers/config.ts +18 -0
- package/src/git/providers/operations.ts +1 -0
- package/src/git/providers/pausedOperations.ts +2 -0
- package/src/git/providers/status.ts +1 -0
- package/src/git/utils/config.utils.ts +8 -0
- package/src/git/utils/issue.utils.ts +2 -0
- package/src/git/utils/pausedOperationStatus.utils.ts +28 -0
- package/src/git/utils/pullRequest.utils.ts +36 -0
- package/src/git/watching/changeEvent.ts +6 -1
- package/src/git/watching/classifyChange.ts +15 -1
- package/src/git/watching/watchService.ts +20 -3
- package/src/git/watching/watchSession.ts +41 -6
- package/src/git/watching/watcherPatterns.ts +1 -1
- package/src/git-cli/exec/git.ts +13 -3
- package/src/git-cli/providers/branches.ts +384 -132
- package/src/git-cli/providers/commits.ts +106 -15
- package/src/git-cli/providers/config.ts +229 -8
- package/src/git-cli/providers/contributors.ts +5 -1
- package/src/git-cli/providers/graph.ts +26 -13
- package/src/git-cli/providers/operations.ts +45 -20
- package/src/git-cli/providers/pausedOperations.ts +109 -17
- package/src/git-cli/providers/refs.ts +33 -3
- package/src/git-cli/providers/status.ts +25 -4
- package/src/git-cli/providers/worktrees.ts +5 -0
- package/src/git-cli/service.ts +0 -1
- package/src/plus/agents/providers/claudeCodeProvider.ts +1130 -87
- package/src/plus/agents/providers/claudeCodeTranscript.ts +95 -9
- package/src/plus/agents/types.ts +46 -2
- package/src/plus/ai/errors.ts +30 -0
- package/src/plus/ai/models/model.ts +56 -6
- package/src/plus/ai/models/promptTemplates.ts +1 -1
- package/src/plus/ai/models/provider.ts +69 -2
- package/src/plus/ai/prompts.ts +186 -61
- package/src/plus/ai/providers/anthropicProvider.ts +155 -47
- package/src/plus/ai/providers/deepSeekProvider.ts +11 -21
- package/src/plus/ai/providers/geminiProvider.ts +19 -0
- package/src/plus/ai/providers/gitkrakenProvider.ts +126 -3
- package/src/plus/ai/providers/huggingFaceProvider.ts +6 -0
- package/src/plus/ai/providers/mistralProvider.ts +102 -19
- package/src/plus/ai/providers/ollamaProvider.ts +55 -4
- package/src/plus/ai/providers/openAICompatibleProvider.ts +5 -0
- package/src/plus/ai/providers/openAICompatibleProviderBase.ts +360 -17
- package/src/plus/ai/providers/openRouterProvider.ts +17 -0
- package/src/plus/ai/providers/openaiProvider.ts +1 -0
- package/src/plus/ai/providers/responseFormatCache.ts +39 -0
- package/src/plus/ai/providers/xaiProvider.ts +10 -1
- package/src/plus/ai/utils/ai.utils.ts +14 -0
- package/src/plus/ai/utils/results.utils.ts +255 -31
- package/src/plus/git-github/api/github.ts +291 -7
- package/src/plus/git-github/models.ts +48 -2
- package/src/plus/git-github/providers/github/status.ts +1 -0
- package/src/plus/integrations/context.ts +4 -0
- package/src/plus/integrations/index.ts +2 -0
- package/src/plus/integrations/models/gitHostIntegration.ts +25 -2
- package/src/plus/integrations/models/integration.ts +23 -13
- package/src/plus/integrations/providers/accounts.ts +4 -0
- package/src/plus/integrations/providers/azure/models.ts +3 -0
- package/src/plus/integrations/providers/bitbucket/models.ts +1 -0
- package/src/plus/integrations/providers/github.ts +85 -1
- package/src/plus/integrations/providers/gitlab.ts +2 -10
- package/src/plus/integrations/providers/models.ts +36 -4
- package/src/plus/integrations/reads/broaden.ts +6 -1
- package/src/plus/integrations/reads/drains.ts +7 -4
- package/src/plus/integrations/reads/hierarchy.ts +3 -1
- package/src/plus/integrations/reads/issueTracker.ts +9 -3
- package/src/plus/integrations/reads/issues.ts +9 -4
- package/src/plus/integrations/reads/pullRequests.ts +9 -4
- package/src/utils/env/browser/base64.ts +1 -1
- package/src/utils/env/node/base64.ts +3 -1
- package/src/utils/keys/chord.ts +184 -0
- package/src/utils/keys/keybinding.ts +116 -0
- package/src/utils/path.ts +14 -0
|
@@ -3,7 +3,7 @@ import type { GitServiceContext } from '../../git/context.js';
|
|
|
3
3
|
import { BranchError } from '../../git/errors.js';
|
|
4
4
|
import type { BranchDisposition, BranchMetadata } from '../../git/models/branch.js';
|
|
5
5
|
import { GitBranch } from '../../git/models/branch.js';
|
|
6
|
-
import type { ConflictDetectionResult } from '../../git/models/mergeConflicts.js';
|
|
6
|
+
import type { ConflictDetectionErrorReason, ConflictDetectionResult } from '../../git/models/mergeConflicts.js';
|
|
7
7
|
import type { GitBranchReference } from '../../git/models/reference.js';
|
|
8
8
|
import type {
|
|
9
9
|
BranchContributionsOverview,
|
|
@@ -41,6 +41,7 @@ import type { CliGitProviderInternal } from '../cliGitProvider.js';
|
|
|
41
41
|
import type { GitResult } from '../exec/exec.types.js';
|
|
42
42
|
import type { Git } from '../exec/git.js';
|
|
43
43
|
import { getGitCommandError, gitConfigsBranch, gitConfigsLog, GitError, GitErrors, GitWarnings } from '../exec/git.js';
|
|
44
|
+
import type { GitMergeConflict } from '../parsers/mergeTreeParser.js';
|
|
44
45
|
import { parseMergeTreeConflict } from '../parsers/mergeTreeParser.js';
|
|
45
46
|
|
|
46
47
|
/** Minimum time between writes to the config for last accessed/modified dates */
|
|
@@ -184,6 +185,14 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
184
185
|
|
|
185
186
|
// If we don't get any data, assume the repo doesn't have any commits yet so check if we have a current branch
|
|
186
187
|
if (!records.length) {
|
|
188
|
+
// Never CACHE that conclusion. `getRefs` reports a failed enumeration the same way as a
|
|
189
|
+
// genuinely ref-less repo — an empty array (it invalidates its own entry but doesn't
|
|
190
|
+
// throw) — so a transient `for-each-ref` failure would otherwise pin this degraded
|
|
191
|
+
// current-branch-only list in a map with no TTL, and every branch consumer would see a
|
|
192
|
+
// one-branch repo for the session. A truly empty repo re-reads cheaply, so refusing the
|
|
193
|
+
// entry costs nothing in the case this is actually meant to serve.
|
|
194
|
+
cacheable?.invalidate();
|
|
195
|
+
|
|
187
196
|
const current = await this.getCurrentBranch(
|
|
188
197
|
commonPath,
|
|
189
198
|
metadataMap,
|
|
@@ -360,7 +369,9 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
360
369
|
repoPath: string,
|
|
361
370
|
ref: string,
|
|
362
371
|
options?: {
|
|
363
|
-
associatedPullRequest?: Promise<
|
|
372
|
+
associatedPullRequest?: Promise<
|
|
373
|
+
{ refs?: { base?: { branch: string } }; stack?: { position: number } } | undefined
|
|
374
|
+
>;
|
|
364
375
|
priority?: GitCommandPriority;
|
|
365
376
|
},
|
|
366
377
|
cancellation?: AbortSignal,
|
|
@@ -478,14 +489,20 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
478
489
|
if (branch == null) return undefined;
|
|
479
490
|
|
|
480
491
|
mergeTarget = `${branch.remoteName}/${pr.refs.base.branch}`;
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
492
|
+
// A stacked PR's base is the layer below it, which is deleted when the stack merges.
|
|
493
|
+
// Storing it would leave a stored target naming a branch that no longer exists — and
|
|
494
|
+
// a stored target beats every other tier, so it would keep winning until cleared by
|
|
495
|
+
// hand. Use it for this lookup, but don't persist it.
|
|
496
|
+
if (pr.stack == null) {
|
|
497
|
+
// Store for future reuse — turns the next call into a Tier 1 path. Skip the
|
|
498
|
+
// `branchOverviews` invalidation that the write would otherwise trigger: we're
|
|
499
|
+
// about to populate `${ref}|${mergeTarget}` with this exact target, so evicting
|
|
500
|
+
// it would just force the next caller to redo the body. (gk-merge-target writes
|
|
501
|
+
// do not affect `baseBranchName`, so no need to skip that target.)
|
|
502
|
+
void this.storeMergeTargetBranchName(repoPath, ref, mergeTarget, {
|
|
503
|
+
skipInvalidation: ['branchOverviews'],
|
|
504
|
+
});
|
|
505
|
+
}
|
|
489
506
|
}
|
|
490
507
|
}
|
|
491
508
|
|
|
@@ -736,74 +753,116 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
736
753
|
const priority = options?.priority;
|
|
737
754
|
const priorityOpts = priority != null ? { priority: priority } : undefined;
|
|
738
755
|
|
|
739
|
-
// Local-only: read just the existing local symref, never contacting the remote.
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
//
|
|
756
|
+
// Local-only: read just the existing local symref, never contacting the remote. Cached under a
|
|
757
|
+
// `${remote}:local` key — a colon can't appear in a refname component, so it can never collide
|
|
758
|
+
// with a real remote name used by the networked variant below. Invalidated by the same
|
|
759
|
+
// remotes/branches cache cascades as the networked lookup.
|
|
743
760
|
if (options?.local) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
761
|
+
return this.cache.getDefaultBranchName(
|
|
762
|
+
repoPath,
|
|
763
|
+
`${remote}:local`,
|
|
764
|
+
async (commonPath, cacheable, signal) => {
|
|
765
|
+
try {
|
|
766
|
+
// Bind to the aggregate `signal` (fires only when ALL current callers abort), not
|
|
767
|
+
// this-caller's `cancellation` — otherwise a superseded caller's abort would kill the
|
|
768
|
+
// shared command and reject concurrent riders that never cancelled.
|
|
769
|
+
const result = await this.git.run(
|
|
770
|
+
{ cwd: commonPath, cancellation: signal ?? cancellation, ...priorityOpts },
|
|
771
|
+
'symbolic-ref',
|
|
772
|
+
'--short',
|
|
773
|
+
`refs/remotes/${remote}/HEAD`,
|
|
774
|
+
);
|
|
775
|
+
return result.stdout.trim() || undefined;
|
|
776
|
+
} catch (ex) {
|
|
777
|
+
if (isCancellationError(ex)) throw ex;
|
|
778
|
+
|
|
779
|
+
// `symbolic-ref` on an absent `refs/remotes/<remote>/HEAD` exits fatal rather than
|
|
780
|
+
// empty, and that message matches no `GitWarnings` entry, so it arrives here as a
|
|
781
|
+
// throw. It is still an ANSWER — the ref genuinely isn't set — and the overwhelmingly
|
|
782
|
+
// common one, so cache it. Invalidating instead means every caller re-probes and the
|
|
783
|
+
// whole point of caching this lookup is lost.
|
|
784
|
+
const stderr = (ex as { stderr?: string }).stderr ?? '';
|
|
785
|
+
if (stderr.includes('is not a symbolic ref')) return undefined;
|
|
786
|
+
|
|
787
|
+
// Anything else never produced an answer (spawn failure, queue rejection). This entry
|
|
788
|
+
// has no TTL, so caching that would pin "no default branch" for the session.
|
|
789
|
+
cacheable.invalidate();
|
|
790
|
+
return undefined;
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
cancellation,
|
|
794
|
+
);
|
|
756
795
|
}
|
|
757
796
|
|
|
758
|
-
return this.cache.getDefaultBranchName(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
797
|
+
return this.cache.getDefaultBranchName(
|
|
798
|
+
repoPath,
|
|
799
|
+
remote,
|
|
800
|
+
async (commonPath, cacheable, signal) => {
|
|
801
|
+
let retried = false;
|
|
802
|
+
while (true) {
|
|
803
|
+
try {
|
|
804
|
+
const result = await this.git.run(
|
|
805
|
+
{ cwd: commonPath, cancellation: signal ?? cancellation, ...priorityOpts },
|
|
806
|
+
'symbolic-ref',
|
|
807
|
+
'--short',
|
|
808
|
+
`refs/remotes/${remote}/HEAD`,
|
|
809
|
+
);
|
|
810
|
+
return result.stdout.trim() || undefined;
|
|
811
|
+
} catch (ex) {
|
|
812
|
+
// Only "no HEAD ref configured" is an ANSWER here; every other throw means the lookup
|
|
813
|
+
// never happened. This entry has no TTL, so caching a failure — an offline `ls-remote`
|
|
814
|
+
// being the likely one — would pin "no default branch" for the session.
|
|
815
|
+
let answered = false;
|
|
816
|
+
if (/is not a symbolic ref/.test(ex.stderr)) {
|
|
817
|
+
try {
|
|
818
|
+
if (!retried) {
|
|
819
|
+
retried = true;
|
|
820
|
+
await this.git.run(
|
|
821
|
+
{ cwd: commonPath, cancellation: signal ?? cancellation, ...priorityOpts },
|
|
822
|
+
'remote',
|
|
823
|
+
'set-head',
|
|
824
|
+
'-a',
|
|
825
|
+
remote,
|
|
826
|
+
);
|
|
827
|
+
// This just created `refs/remotes/<remote>/HEAD`, which the `local` variant
|
|
828
|
+
// may already have cached a miss for. The watcher would also catch the new
|
|
829
|
+
// ref, but evict directly so consumers of this package that don't wire up a
|
|
830
|
+
// watcher aren't stuck with that miss.
|
|
831
|
+
this.cache.deleteDefaultBranchName(commonPath, `${remote}:local`);
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const result = await this.git.run(
|
|
836
|
+
{ cwd: commonPath, cancellation: signal ?? cancellation, ...priorityOpts },
|
|
837
|
+
'ls-remote',
|
|
838
|
+
'--symref',
|
|
779
839
|
remote,
|
|
840
|
+
'HEAD',
|
|
780
841
|
);
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
'--symref',
|
|
788
|
-
remote,
|
|
789
|
-
'HEAD',
|
|
790
|
-
);
|
|
791
|
-
if (result.stdout) {
|
|
792
|
-
const match = /ref:\s(\S+)\s+HEAD/m.exec(result.stdout);
|
|
793
|
-
if (match != null) {
|
|
794
|
-
const [, branch] = match;
|
|
795
|
-
return `${remote}/${branch.substring('refs/heads/'.length).trim()}`;
|
|
842
|
+
if (result.stdout) {
|
|
843
|
+
const match = /ref:\s(\S+)\s+HEAD/m.exec(result.stdout);
|
|
844
|
+
if (match != null) {
|
|
845
|
+
const [, branch] = match;
|
|
846
|
+
return `${remote}/${branch.substring('refs/heads/'.length).trim()}`;
|
|
847
|
+
}
|
|
796
848
|
}
|
|
849
|
+
|
|
850
|
+
// `ls-remote` ran and the remote advertised no HEAD — a real "none".
|
|
851
|
+
answered = true;
|
|
852
|
+
} catch {
|
|
853
|
+
if (isCancellationError(ex)) throw ex;
|
|
797
854
|
}
|
|
798
|
-
} catch {
|
|
799
|
-
if (isCancellationError(ex)) throw ex;
|
|
800
855
|
}
|
|
801
|
-
}
|
|
802
856
|
|
|
803
|
-
|
|
857
|
+
if (!answered) {
|
|
858
|
+
cacheable.invalidate();
|
|
859
|
+
}
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
804
862
|
}
|
|
805
|
-
}
|
|
806
|
-
|
|
863
|
+
},
|
|
864
|
+
cancellation,
|
|
865
|
+
);
|
|
807
866
|
}
|
|
808
867
|
|
|
809
868
|
@debug()
|
|
@@ -814,6 +873,12 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
814
873
|
}
|
|
815
874
|
try {
|
|
816
875
|
await this.git.run({ cwd: repoPath }, ...args);
|
|
876
|
+
// Evict the cached base so the new branch re-derives its own. The PERSISTED `branch.<ref>.gk-*`
|
|
877
|
+
// keys are left alone: they're dropped when GitLens deletes a branch, never when one is created.
|
|
878
|
+
// Removing them here would mean treating a reused name as proof of a different branch, which
|
|
879
|
+
// nothing can establish — so the trade is that a branch reusing a name deleted outside GitLens
|
|
880
|
+
// inherits the orphaned section.
|
|
881
|
+
this.cache.deleteBaseBranchName(repoPath, name);
|
|
817
882
|
this.context.hooks?.cache?.onReset?.(repoPath, 'branches');
|
|
818
883
|
this.context.hooks?.repository?.onChanged?.(repoPath, ['heads']);
|
|
819
884
|
} catch (ex) {
|
|
@@ -840,9 +905,31 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
840
905
|
const args = ['branch', options?.force ? '-D' : '-d', ...branches];
|
|
841
906
|
try {
|
|
842
907
|
await this.git.run({ cwd: repoPath }, ...args);
|
|
908
|
+
for (const branch of branches) {
|
|
909
|
+
this.cache.deleteBaseBranchName(repoPath, branch);
|
|
910
|
+
// Drop the persisted gk metadata too, not just the cached resolution. `getBaseBranchName`
|
|
911
|
+
// reads `branch.<ref>.gk-merge-base` before falling back to the reflog, so leaving it behind
|
|
912
|
+
// would hand a later branch reusing this name its predecessor's base — evicting the cache
|
|
913
|
+
// alone just forces a re-derivation that reads the same stale value back.
|
|
914
|
+
await this.provider.config.removeGkConfigBranchSection(repoPath, branch);
|
|
915
|
+
}
|
|
843
916
|
this.context.hooks?.cache?.onReset?.(repoPath, 'branches');
|
|
844
917
|
this.context.hooks?.repository?.onChanged?.(repoPath, ['heads']);
|
|
845
918
|
} catch (ex) {
|
|
919
|
+
// A multi-name delete partially succeeds: `git branch -d a bad c` removes `a` and `c`, refuses
|
|
920
|
+
// `bad`, and still exits non-zero — so the loop above never ran for the branches that did go.
|
|
921
|
+
// Clean those up here rather than leave persisted gk metadata behind for a name a later branch
|
|
922
|
+
// can reuse.
|
|
923
|
+
const anyDeleted = await this.cleanupDeletedBranchMetadata(repoPath, branches);
|
|
924
|
+
|
|
925
|
+
// The success path's notifications never ran, but some branches DID go — without these the
|
|
926
|
+
// branch caches stay stale and consumers wait on the filesystem watcher (which never fires
|
|
927
|
+
// for a packed ref).
|
|
928
|
+
if (anyDeleted) {
|
|
929
|
+
this.context.hooks?.cache?.onReset?.(repoPath, 'branches');
|
|
930
|
+
this.context.hooks?.repository?.onChanged?.(repoPath, ['heads']);
|
|
931
|
+
}
|
|
932
|
+
|
|
846
933
|
if (ex instanceof BranchError) {
|
|
847
934
|
throw ex.update({
|
|
848
935
|
action: options?.force ? 'force delete' : 'delete',
|
|
@@ -862,6 +949,62 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
862
949
|
}
|
|
863
950
|
}
|
|
864
951
|
|
|
952
|
+
/**
|
|
953
|
+
* Drops the cached and persisted per-branch metadata for whichever of `names` no longer exists. Only
|
|
954
|
+
* for the partial-failure path — one extra `for-each-ref` is cheap while already handling an error,
|
|
955
|
+
* and keeps the success path free of it.
|
|
956
|
+
*
|
|
957
|
+
* Returns whether any of `names` is confirmed gone; `false` when the probe couldn't answer, so a
|
|
958
|
+
* spurious notification is never fired on an unrelated failure.
|
|
959
|
+
*/
|
|
960
|
+
private async cleanupDeletedBranchMetadata(repoPath: string, names: string[]): Promise<boolean> {
|
|
961
|
+
try {
|
|
962
|
+
const result = await this.git.run(
|
|
963
|
+
{ cwd: repoPath, errors: 'ignore' },
|
|
964
|
+
'for-each-ref',
|
|
965
|
+
// Full refname, stripped here rather than by git. NOT `%(refname:short)`: that yields the
|
|
966
|
+
// shortest UNAMBIGUOUS name, so a branch with a same-named tag comes back as `heads/<name>`,
|
|
967
|
+
// never matches the requested name, and would read as deleted — wiping a live branch's
|
|
968
|
+
// metadata. `%(refname:strip=2)` also solves that, but `%(refname)` is the oldest, plainest
|
|
969
|
+
// field and needs no assumption about which git version gained `strip`.
|
|
970
|
+
'--format=%(refname)',
|
|
971
|
+
...names.map(n => `refs/heads/${n}`),
|
|
972
|
+
);
|
|
973
|
+
// `errors: 'ignore'` means a failed probe RESOLVES empty, which is indistinguishable from "none
|
|
974
|
+
// of them survived" — and would delete every name's metadata, including user-owned keys like
|
|
975
|
+
// `gk-disposition` and `gk-associated-issues`. Only act on a genuine answer.
|
|
976
|
+
//
|
|
977
|
+
// BOTH conditions: `exitCode !== 0` catches a probe git ran and rejected, `status !== 'exited'`
|
|
978
|
+
// catches one that never produced an answer — a spawn failure or queue rejection (no exit code at
|
|
979
|
+
// all), or a swallowed warning (which carries git's real code, so the exit-code test alone can
|
|
980
|
+
// still read it as "none survived").
|
|
981
|
+
if (result.completion.status !== 'exited' || result.exitCode !== 0) return false;
|
|
982
|
+
|
|
983
|
+
const prefix = 'refs/heads/';
|
|
984
|
+
const surviving = new Set(
|
|
985
|
+
result.stdout
|
|
986
|
+
.split('\n')
|
|
987
|
+
.map(l => l.trim())
|
|
988
|
+
.filter(l => l.startsWith(prefix))
|
|
989
|
+
.map(l => l.substring(prefix.length)),
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
let anyDeleted = false;
|
|
993
|
+
for (const name of names) {
|
|
994
|
+
if (surviving.has(name)) continue;
|
|
995
|
+
|
|
996
|
+
anyDeleted = true;
|
|
997
|
+
this.cache.deleteBaseBranchName(repoPath, name);
|
|
998
|
+
await this.provider.config.removeGkConfigBranchSection(repoPath, name);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
return anyDeleted;
|
|
1002
|
+
} catch {
|
|
1003
|
+
// Best-effort bookkeeping while already unwinding a failure — never mask the original error.
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
865
1008
|
@debug()
|
|
866
1009
|
async deleteRemoteBranch(repoPath: string, names: string | string[], remote: string): Promise<void> {
|
|
867
1010
|
const branches = ensureArray(names);
|
|
@@ -1161,7 +1304,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1161
1304
|
if (!data) return { status: 'clean' };
|
|
1162
1305
|
|
|
1163
1306
|
const mergeConflict = parseMergeTreeConflict(data);
|
|
1164
|
-
if (!mergeConflict.conflicts.length) return { status: 'clean' };
|
|
1307
|
+
if (!mergeConflict.conflicts.length) return { status: 'clean', treeOid: mergeConflict.treeOid };
|
|
1165
1308
|
|
|
1166
1309
|
return {
|
|
1167
1310
|
status: 'conflicts',
|
|
@@ -1172,12 +1315,137 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1172
1315
|
files: mergeConflict.conflicts,
|
|
1173
1316
|
shas: undefined, // Merge conflicts don't have a specific commit SHA
|
|
1174
1317
|
},
|
|
1318
|
+
treeOid: mergeConflict.treeOid,
|
|
1319
|
+
};
|
|
1320
|
+
},
|
|
1321
|
+
{ cancellation: cancellation },
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/** Detects conflicts when an autostash is reapplied onto the tree an integration produced */
|
|
1326
|
+
@debug()
|
|
1327
|
+
async getPotentialStashReapplyConflicts(
|
|
1328
|
+
repoPath: string,
|
|
1329
|
+
ontoTreeOid: string,
|
|
1330
|
+
cancellation?: AbortSignal,
|
|
1331
|
+
): Promise<ConflictDetectionResult> {
|
|
1332
|
+
// Requires Git v2.38+ for --write-tree with 3-arg form
|
|
1333
|
+
if (!(await this.git.supports('git:merge-tree:write-tree'))) {
|
|
1334
|
+
return createConflictDetectionError('unsupported');
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// git stash create writes commit objects but touches neither the working tree nor the stash ref, and
|
|
1338
|
+
// captures tracked changes only — which is exactly what autostash does
|
|
1339
|
+
let stashSha: string;
|
|
1340
|
+
try {
|
|
1341
|
+
const result = await this.git.run(
|
|
1342
|
+
{ cwd: repoPath, cancellation: cancellation, errors: 'throw' },
|
|
1343
|
+
'stash',
|
|
1344
|
+
'create',
|
|
1345
|
+
);
|
|
1346
|
+
stashSha = result.stdout.trim();
|
|
1347
|
+
} catch (ex) {
|
|
1348
|
+
if (isCancellationError(ex)) throw ex;
|
|
1349
|
+
|
|
1350
|
+
return createConflictDetectionError('other');
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (!stashSha) return { status: 'clean' };
|
|
1354
|
+
|
|
1355
|
+
// Key on what the simulation actually consumes — the stash's tree and its first parent (the HEAD it was
|
|
1356
|
+
// taken against, and so the merge base). The stash SHA itself embeds a commit timestamp, so two runs over
|
|
1357
|
+
// an identical tree produce different SHAs and a SHA-keyed entry could never be hit.
|
|
1358
|
+
let stashTree: string;
|
|
1359
|
+
let stashParent: string;
|
|
1360
|
+
try {
|
|
1361
|
+
const result = await this.git.run(
|
|
1362
|
+
{ cwd: repoPath, cancellation: cancellation, errors: 'throw' },
|
|
1363
|
+
'rev-parse',
|
|
1364
|
+
`${stashSha}^{tree}`,
|
|
1365
|
+
`${stashSha}^`,
|
|
1366
|
+
);
|
|
1367
|
+
[stashTree, stashParent] = result.stdout.trim().split('\n');
|
|
1368
|
+
} catch (ex) {
|
|
1369
|
+
if (isCancellationError(ex)) throw ex;
|
|
1370
|
+
|
|
1371
|
+
return createConflictDetectionError('other');
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
const cacheKey: ConflictDetectionCacheKey = `reapply:${ontoTreeOid}:${stashTree}.${stashParent}`;
|
|
1375
|
+
return this.cache.conflictDetection.getOrCreate(
|
|
1376
|
+
repoPath,
|
|
1377
|
+
cacheKey,
|
|
1378
|
+
async (_cacheable, signal) => {
|
|
1379
|
+
// The stash commit's first parent is the HEAD it was taken against, which is the correct merge
|
|
1380
|
+
// base for a reapply — ours is the post-integration tree, theirs is the stash
|
|
1381
|
+
const step = await this.runMergeTreeStep(repoPath, `${stashSha}^`, ontoTreeOid, stashSha, signal);
|
|
1382
|
+
if (!step.ok) return createConflictDetectionError(step.reason);
|
|
1383
|
+
|
|
1384
|
+
if (!step.result.conflicts.length) return { status: 'clean', treeOid: step.result.treeOid };
|
|
1385
|
+
|
|
1386
|
+
return {
|
|
1387
|
+
status: 'conflicts',
|
|
1388
|
+
conflict: {
|
|
1389
|
+
repoPath: repoPath,
|
|
1390
|
+
branch: stashSha,
|
|
1391
|
+
target: ontoTreeOid,
|
|
1392
|
+
files: step.result.conflicts,
|
|
1393
|
+
shas: undefined,
|
|
1394
|
+
},
|
|
1395
|
+
treeOid: step.result.treeOid,
|
|
1175
1396
|
};
|
|
1176
1397
|
},
|
|
1177
1398
|
{ cancellation: cancellation },
|
|
1178
1399
|
);
|
|
1179
1400
|
}
|
|
1180
1401
|
|
|
1402
|
+
private async runMergeTreeStep(
|
|
1403
|
+
repoPath: string,
|
|
1404
|
+
base: string,
|
|
1405
|
+
ours: string,
|
|
1406
|
+
theirs: string,
|
|
1407
|
+
cancellation?: AbortSignal,
|
|
1408
|
+
): Promise<{ ok: true; result: GitMergeConflict } | { ok: false; reason: ConflictDetectionErrorReason }> {
|
|
1409
|
+
const scope = getScopedLogger();
|
|
1410
|
+
|
|
1411
|
+
let data;
|
|
1412
|
+
try {
|
|
1413
|
+
const result = await this.git.run(
|
|
1414
|
+
{ cwd: repoPath, cancellation: cancellation, errors: 'throw' },
|
|
1415
|
+
'merge-tree',
|
|
1416
|
+
'--write-tree',
|
|
1417
|
+
'-z',
|
|
1418
|
+
'--name-only',
|
|
1419
|
+
'--no-messages',
|
|
1420
|
+
`--merge-base=${base}`,
|
|
1421
|
+
ours,
|
|
1422
|
+
theirs,
|
|
1423
|
+
);
|
|
1424
|
+
data = result.stdout;
|
|
1425
|
+
} catch (ex) {
|
|
1426
|
+
if (isCancellationError(ex)) throw ex;
|
|
1427
|
+
|
|
1428
|
+
const msg: string = ex?.toString() ?? '';
|
|
1429
|
+
if (GitErrors.notAValidObjectName.test(msg)) {
|
|
1430
|
+
scope?.error(ex, `'${ours}' or '${theirs}' not found - ensure the branches/commits exist`);
|
|
1431
|
+
return { ok: false, reason: 'refNotFound' };
|
|
1432
|
+
} else if (GitErrors.badRevision.test(msg)) {
|
|
1433
|
+
scope?.error(ex, `Invalid revision: ${msg.slice(msg.indexOf("'"))}`);
|
|
1434
|
+
return { ok: false, reason: 'refNotFound' };
|
|
1435
|
+
} else if (GitErrors.noMergeBase.test(msg)) {
|
|
1436
|
+
scope?.error(ex, `Unable to merge '${ours}' and '${theirs}' as they have no common ancestor`);
|
|
1437
|
+
return { ok: false, reason: 'noMergeBase' };
|
|
1438
|
+
} else if (ex instanceof GitError) {
|
|
1439
|
+
data = ex.stdout;
|
|
1440
|
+
} else {
|
|
1441
|
+
scope?.error(ex, 'Failed to execute merge-tree for conflict check');
|
|
1442
|
+
return { ok: false, reason: 'other' };
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
return { ok: true, result: parseMergeTreeConflict(data ?? '') };
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1181
1449
|
private async checkForPotentialConflicts(
|
|
1182
1450
|
repoPath: string,
|
|
1183
1451
|
commits: Iterable<{ sha: string; parent: string }>,
|
|
@@ -1214,56 +1482,16 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1214
1482
|
for (const commit of commits) {
|
|
1215
1483
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1216
1484
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
const result = await this.git.run(
|
|
1226
|
-
{ cwd: repoPath, cancellation: cancellation, errors: 'throw' },
|
|
1227
|
-
'merge-tree',
|
|
1228
|
-
'--write-tree',
|
|
1229
|
-
'-z',
|
|
1230
|
-
'--name-only',
|
|
1231
|
-
'--no-messages',
|
|
1232
|
-
`--merge-base=${commit.parent}`,
|
|
1233
|
-
currentTreeOid,
|
|
1234
|
-
commit.sha,
|
|
1235
|
-
);
|
|
1236
|
-
data = result.stdout;
|
|
1237
|
-
} catch (ex) {
|
|
1238
|
-
if (isCancellationError(ex)) throw ex;
|
|
1239
|
-
|
|
1240
|
-
const msg: string = ex?.toString() ?? '';
|
|
1241
|
-
if (GitErrors.notAValidObjectName.test(msg)) {
|
|
1242
|
-
scope?.error(
|
|
1243
|
-
ex,
|
|
1244
|
-
`'${targetBranch}' or '${commit.sha}' not found - ensure the branches/commits exist`,
|
|
1245
|
-
);
|
|
1246
|
-
return createConflictDetectionError('refNotFound');
|
|
1247
|
-
} else if (GitErrors.badRevision.test(msg)) {
|
|
1248
|
-
scope?.error(ex, `Invalid revision: ${msg.slice(msg.indexOf("'"))}`);
|
|
1249
|
-
return createConflictDetectionError('refNotFound');
|
|
1250
|
-
} else if (GitErrors.noMergeBase.test(msg)) {
|
|
1251
|
-
scope?.error(
|
|
1252
|
-
ex,
|
|
1253
|
-
`Unable to merge '${commit.sha}' and '${targetBranch}' as they have no common ancestor`,
|
|
1254
|
-
);
|
|
1255
|
-
return createConflictDetectionError('noMergeBase');
|
|
1256
|
-
} else if (ex instanceof GitError) {
|
|
1257
|
-
data = ex.stdout;
|
|
1258
|
-
} else {
|
|
1259
|
-
scope?.error(ex, 'Failed to execute merge-tree for conflict check');
|
|
1260
|
-
return createConflictDetectionError('other');
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
if (!data) continue;
|
|
1485
|
+
// Use merge-tree --write-tree with --merge-base to simulate cherry-pick:
|
|
1486
|
+
// merge-tree --write-tree --merge-base=<commit-parent> <current-tree> <commit>
|
|
1487
|
+
// This performs a 3-way merge where:
|
|
1488
|
+
// - base = commit.parent (where the commit started)
|
|
1489
|
+
// - ours = currentTreeOid (current state of the target)
|
|
1490
|
+
// - theirs = commit.sha (what we're cherry-picking)
|
|
1491
|
+
const step = await this.runMergeTreeStep(repoPath, commit.parent, currentTreeOid, commit.sha, cancellation);
|
|
1492
|
+
if (!step.ok) return createConflictDetectionError(step.reason);
|
|
1265
1493
|
|
|
1266
|
-
const mergeConflict =
|
|
1494
|
+
const mergeConflict = step.result;
|
|
1267
1495
|
|
|
1268
1496
|
if (mergeConflict.conflicts.length) {
|
|
1269
1497
|
conflictingShas.push(commit.sha);
|
|
@@ -1282,6 +1510,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1282
1510
|
shas: [commit.sha],
|
|
1283
1511
|
},
|
|
1284
1512
|
stoppedOnFirstConflict: true,
|
|
1513
|
+
treeOid: mergeConflict.treeOid || undefined,
|
|
1285
1514
|
};
|
|
1286
1515
|
}
|
|
1287
1516
|
}
|
|
@@ -1304,10 +1533,11 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1304
1533
|
shas: conflictingShas,
|
|
1305
1534
|
},
|
|
1306
1535
|
stoppedOnFirstConflict: false,
|
|
1536
|
+
treeOid: currentTreeOid,
|
|
1307
1537
|
};
|
|
1308
1538
|
}
|
|
1309
1539
|
|
|
1310
|
-
return { status: 'clean' };
|
|
1540
|
+
return { status: 'clean', treeOid: currentTreeOid };
|
|
1311
1541
|
}
|
|
1312
1542
|
|
|
1313
1543
|
@debug({ exit: true })
|
|
@@ -1323,7 +1553,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1323
1553
|
return this.cache.getBaseBranchName(
|
|
1324
1554
|
repoPath,
|
|
1325
1555
|
ref,
|
|
1326
|
-
async (commonPath, signal) => {
|
|
1556
|
+
async (commonPath, cacheable, signal) => {
|
|
1327
1557
|
try {
|
|
1328
1558
|
// getGkConfig has built-in fallback to regular config for backward compatibility
|
|
1329
1559
|
let mergeBase = await this.provider.config.getGkConfig(commonPath, `branch.${ref}.gk-merge-base`);
|
|
@@ -1355,18 +1585,25 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1355
1585
|
}
|
|
1356
1586
|
} catch {}
|
|
1357
1587
|
|
|
1358
|
-
const
|
|
1588
|
+
const reflog = await this.getBaseBranchFromReflog(
|
|
1359
1589
|
commonPath,
|
|
1360
1590
|
ref,
|
|
1361
1591
|
{ upstream: true, priority: priority },
|
|
1362
1592
|
signal,
|
|
1363
1593
|
);
|
|
1364
|
-
if (branch != null) {
|
|
1594
|
+
if (reflog.branch != null) {
|
|
1365
1595
|
// Self-write: see comment on the migration path above.
|
|
1366
|
-
void this.storeBaseBranchName(commonPath, ref, branch, {
|
|
1596
|
+
void this.storeBaseBranchName(commonPath, ref, reflog.branch, {
|
|
1367
1597
|
skipInvalidation: ['branchOverviews', 'baseBranchName'],
|
|
1368
1598
|
});
|
|
1369
|
-
return branch;
|
|
1599
|
+
return reflog.branch;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// This entry has no TTL and the `'branches'` cascade no longer clears it (a tip move can't
|
|
1603
|
+
// change a base), so a failed read cached as `undefined` would read as "no base branch" for
|
|
1604
|
+
// the rest of the session. Only a reflog that actually RAN and found nothing is an answer.
|
|
1605
|
+
if (reflog.failed) {
|
|
1606
|
+
cacheable.invalidate();
|
|
1370
1607
|
}
|
|
1371
1608
|
|
|
1372
1609
|
return undefined;
|
|
@@ -1375,12 +1612,16 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1375
1612
|
);
|
|
1376
1613
|
}
|
|
1377
1614
|
|
|
1615
|
+
/**
|
|
1616
|
+
* `failed` separates "the reflog ran and had no answer" from "the read never happened" — the caller
|
|
1617
|
+
* caches the former forever and must not cache the latter.
|
|
1618
|
+
*/
|
|
1378
1619
|
private async getBaseBranchFromReflog(
|
|
1379
1620
|
repoPath: string,
|
|
1380
1621
|
ref: string,
|
|
1381
1622
|
options?: { upstream: true; priority?: GitCommandPriority },
|
|
1382
1623
|
cancellation?: AbortSignal,
|
|
1383
|
-
): Promise<string | undefined> {
|
|
1624
|
+
): Promise<{ branch: string | undefined; failed: boolean }> {
|
|
1384
1625
|
const priority = options?.priority;
|
|
1385
1626
|
const priorityOpts = priority != null ? { priority: priority } : undefined;
|
|
1386
1627
|
|
|
@@ -1393,7 +1634,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1393
1634
|
);
|
|
1394
1635
|
|
|
1395
1636
|
let entries = result.stdout.split('\n').filter(entry => Boolean(entry));
|
|
1396
|
-
if (entries.length !== 1) return undefined;
|
|
1637
|
+
if (entries.length !== 1) return { branch: undefined, failed: false };
|
|
1397
1638
|
|
|
1398
1639
|
// Check if branch created from an explicit branch
|
|
1399
1640
|
let match = entries[0].match(/branch: Created from (.*)$/);
|
|
@@ -1406,11 +1647,11 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1406
1647
|
`${name}@{u}`,
|
|
1407
1648
|
priorityOpts,
|
|
1408
1649
|
);
|
|
1409
|
-
if (upstream) return upstream;
|
|
1650
|
+
if (upstream) return { branch: upstream, failed: false };
|
|
1410
1651
|
}
|
|
1411
1652
|
|
|
1412
1653
|
name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
|
|
1413
|
-
if (name) return name;
|
|
1654
|
+
if (name) return { branch: name, failed: false };
|
|
1414
1655
|
}
|
|
1415
1656
|
}
|
|
1416
1657
|
|
|
@@ -1423,7 +1664,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1423
1664
|
);
|
|
1424
1665
|
|
|
1425
1666
|
entries = result.stdout.split('\n').filter(entry => Boolean(entry));
|
|
1426
|
-
if (!entries.length) return undefined;
|
|
1667
|
+
if (!entries.length) return { branch: undefined, failed: false };
|
|
1427
1668
|
|
|
1428
1669
|
match = entries.at(-1)!.match(/checkout: moving from ([^\s]+)\s/);
|
|
1429
1670
|
if (match?.length === 2) {
|
|
@@ -1434,17 +1675,21 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1434
1675
|
`${name}@{u}`,
|
|
1435
1676
|
priorityOpts,
|
|
1436
1677
|
);
|
|
1437
|
-
if (upstream) return upstream;
|
|
1678
|
+
if (upstream) return { branch: upstream, failed: false };
|
|
1438
1679
|
}
|
|
1439
1680
|
|
|
1440
1681
|
name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
|
|
1441
|
-
if (name) return name;
|
|
1682
|
+
if (name) return { branch: name, failed: false };
|
|
1442
1683
|
}
|
|
1443
1684
|
} catch (ex) {
|
|
1444
1685
|
if (isCancellationError(ex)) throw ex;
|
|
1686
|
+
|
|
1687
|
+
// The reads use default error handling, so a spawn failure, queue rejection or swallowed
|
|
1688
|
+
// `GitWarnings` match lands here rather than producing an empty answer.
|
|
1689
|
+
return { branch: undefined, failed: true };
|
|
1445
1690
|
}
|
|
1446
1691
|
|
|
1447
|
-
return undefined;
|
|
1692
|
+
return { branch: undefined, failed: false };
|
|
1448
1693
|
}
|
|
1449
1694
|
|
|
1450
1695
|
@debug({ exit: true })
|
|
@@ -1506,6 +1751,13 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
|
|
|
1506
1751
|
const args = ['branch', '-m', oldName, newName];
|
|
1507
1752
|
try {
|
|
1508
1753
|
await this.git.run({ cwd: repoPath }, ...args);
|
|
1754
|
+
// The branch keeps its identity across a rename, so move its stored gk metadata with it —
|
|
1755
|
+
// otherwise the new name re-derives a base it already had, and the old name is left holding
|
|
1756
|
+
// metadata for a branch that no longer exists under it.
|
|
1757
|
+
await this.provider.config.renameGkConfigBranchSection(repoPath, oldName, newName);
|
|
1758
|
+
// Old name's entry is now orphaned; the new name must not inherit a predecessor's base
|
|
1759
|
+
this.cache.deleteBaseBranchName(repoPath, oldName);
|
|
1760
|
+
this.cache.deleteBaseBranchName(repoPath, newName);
|
|
1509
1761
|
this.context.hooks?.cache?.onReset?.(repoPath, 'branches');
|
|
1510
1762
|
this.context.hooks?.repository?.onChanged?.(repoPath, ['heads']);
|
|
1511
1763
|
} catch (ex) {
|