diff --git a/contributions.json b/contributions.json index acfadc408a715..484a402985702 100644 --- a/contributions.json +++ b/contributions.json @@ -4717,6 +4717,19 @@ "label": "Toggle Zen Mode", "commandPalette": "gitlens:enabled" }, + "gitlens.views.abortPausedOperation": { + "label": "Abort", + "icon": "$(circle-slash)", + "menus": { + "view/item/context": [ + { + "when": "viewItem =~ /gitlens:paused-operation\\b/ && !listMultiSelection", + "group": "inline", + "order": 3 + } + ] + } + }, "gitlens.views.addAuthor": { "label": "Add as Co-author", "icon": "$(person-add)", @@ -5846,6 +5859,19 @@ ] } }, + "gitlens.views.continuePausedOperation": { + "label": "Continue", + "icon": "$(debug-continue)", + "menus": { + "view/item/context": [ + { + "when": "viewItem =~ /gitlens:paused-operation:(cherry-pick|merge|rebase)\\b/ && !listMultiSelection", + "group": "inline", + "order": 1 + } + ] + } + }, "gitlens.views.contributors.copy": { "label": "Copy", "keybindings": [ @@ -7638,6 +7664,19 @@ ] } }, + "gitlens.views.openPausedOperationInRebaseEditor": { + "label": "Open in Rebase Editor", + "icon": "$(edit)", + "menus": { + "view/item/context": [ + { + "when": "viewItem =~ /gitlens:paused-operation:rebase\\b/ && !listMultiSelection", + "group": "inline", + "order": 4 + } + ] + } + }, "gitlens.views.openPreviousChangesWithWorking": { "label": "Open Previous Changes with Working File", "menus": { @@ -10391,6 +10430,19 @@ ] } }, + "gitlens.views.skipPausedOperation": { + "label": "Skip", + "icon": "$(debug-step-over)", + "menus": { + "view/item/context": [ + { + "when": "viewItem =~ /gitlens:paused-operation:(cherry-pick|rebase|revert)\\b/ && !listMultiSelection", + "group": "inline", + "order": 2 + } + ] + } + }, "gitlens.views.stageDirectory": { "label": "Stage All Changes", "icon": "$(add)", diff --git a/docs/telemetry-events.md b/docs/telemetry-events.md index 065e3dccc9a87..435948ee898d9 100644 --- a/docs/telemetry-events.md +++ b/docs/telemetry-events.md @@ -998,6 +998,17 @@ or } ``` +### home/command + +> Sent when a Home command is executed + +```typescript +{ + 'command': string, + 'webview': string +} +``` + ### home/createBranch > Sent when the user chooses to create a branch from the home view diff --git a/package.json b/package.json index c986aa75ce818..bea17442b8f3f 100644 --- a/package.json +++ b/package.json @@ -7581,6 +7581,11 @@ "title": "Toggle Zen Mode", "category": "GitLens" }, + { + "command": "gitlens.views.abortPausedOperation", + "title": "Abort", + "icon": "$(circle-slash)" + }, { "command": "gitlens.views.addAuthor", "title": "Add as Co-author", @@ -7857,6 +7862,11 @@ "title": "Compare Working Tree to Here", "icon": "$(gitlens-compare-ref-working)" }, + { + "command": "gitlens.views.continuePausedOperation", + "title": "Continue", + "icon": "$(debug-continue)" + }, { "command": "gitlens.views.contributors.copy", "title": "Copy" @@ -8389,6 +8399,11 @@ "command": "gitlens.views.openOnlyChangedFiles", "title": "Open Changed & Close Unchanged Files" }, + { + "command": "gitlens.views.openPausedOperationInRebaseEditor", + "title": "Open in Rebase Editor", + "icon": "$(edit)" + }, { "command": "gitlens.views.openPreviousChangesWithWorking", "title": "Open Previous Changes with Working File" @@ -9116,6 +9131,11 @@ "command": "gitlens.views.setShowRelativeDateMarkersOn", "title": "Show Date Markers" }, + { + "command": "gitlens.views.skipPausedOperation", + "title": "Skip", + "icon": "$(debug-step-over)" + }, { "command": "gitlens.views.stageDirectory", "title": "Stage All Changes", @@ -11265,6 +11285,10 @@ "command": "gitlens.toggleZenMode", "when": "gitlens:enabled" }, + { + "command": "gitlens.views.abortPausedOperation", + "when": "false" + }, { "command": "gitlens.views.addAuthor", "when": "false" @@ -11509,6 +11533,10 @@ "command": "gitlens.views.compareWithWorking", "when": "false" }, + { + "command": "gitlens.views.continuePausedOperation", + "when": "false" + }, { "command": "gitlens.views.contributors.copy", "when": "false" @@ -11957,6 +11985,10 @@ "command": "gitlens.views.openOnlyChangedFiles", "when": "false" }, + { + "command": "gitlens.views.openPausedOperationInRebaseEditor", + "when": "false" + }, { "command": "gitlens.views.openPreviousChangesWithWorking", "when": "false" @@ -12569,6 +12601,10 @@ "command": "gitlens.views.setShowRelativeDateMarkersOn", "when": "false" }, + { + "command": "gitlens.views.skipPausedOperation", + "when": "false" + }, { "command": "gitlens.views.stageDirectory", "when": "false" @@ -16198,6 +16234,26 @@ "when": "viewItem =~ /gitlens:pager\\b/ && !listMultiSelection", "group": "1_gitlens_actions@1" }, + { + "command": "gitlens.views.abortPausedOperation", + "when": "viewItem =~ /gitlens:paused-operation\\b/ && !listMultiSelection", + "group": "inline@3" + }, + { + "command": "gitlens.views.continuePausedOperation", + "when": "viewItem =~ /gitlens:paused-operation:(cherry-pick|merge|rebase)\\b/ && !listMultiSelection", + "group": "inline@1" + }, + { + "command": "gitlens.views.skipPausedOperation", + "when": "viewItem =~ /gitlens:paused-operation:(cherry-pick|rebase|revert)\\b/ && !listMultiSelection", + "group": "inline@2" + }, + { + "command": "gitlens.views.openPausedOperationInRebaseEditor", + "when": "viewItem =~ /gitlens:paused-operation:rebase\\b/ && !listMultiSelection", + "group": "inline@4" + }, { "command": "gitlens.views.openPullRequest", "when": "viewItem =~ /gitlens:pullrequest\\b/ && gitlens:action:openPullRequest > 1", diff --git a/src/constants.commands.ts b/src/constants.commands.ts index c28917505885d..4270a00a7d0ab 100644 --- a/src/constants.commands.ts +++ b/src/constants.commands.ts @@ -665,7 +665,11 @@ export type TreeViewCommands = `gitlens.views.${ | 'setResultsCommitsFilterAuthors' | 'setResultsCommitsFilterOff' | 'setContributorsStatisticsOff' - | 'setContributorsStatisticsOn'}`; + | 'setContributorsStatisticsOn' + | 'abortPausedOperation' + | 'continuePausedOperation' + | 'skipPausedOperation' + | 'openPausedOperationInRebaseEditor'}`; type ExtractSuffix = U extends `${Prefix}${infer V}` ? V : never; type FilterCommands = U extends `${Prefix}${infer V}` ? `${Prefix}${V}` : never; @@ -694,7 +698,11 @@ type HomeWebviewCommands = `home.${ | 'mergeIntoCurrent' | 'rebaseCurrentOnto' | 'startWork' - | 'createCloudPatch'}`; + | 'createCloudPatch' + | 'skipPausedOperation' + | 'continuePausedOperation' + | 'abortPausedOperation' + | 'openRebaseEditor'}`; type GraphWebviewCommands = `graph.${ | 'switchToEditorLayout' @@ -807,7 +815,11 @@ type GraphWebviewCommands = `graph.${ | 'openWorktreeInNewWindow' | 'copyWorkingChangesToWorktree' | 'generateCommitMessage' - | 'compareSelectedCommits.multi'}`; + | 'compareSelectedCommits.multi' + | 'skipPausedOperation' + | 'continuePausedOperation' + | 'abortPausedOperation' + | 'openRebaseEditor'}`; type TimelineWebviewCommands = `timeline.${'refresh' | 'split'}`; diff --git a/src/constants.telemetry.ts b/src/constants.telemetry.ts index 65e9e11d6a08a..0c491a5c6ebb5 100644 --- a/src/constants.telemetry.ts +++ b/src/constants.telemetry.ts @@ -144,6 +144,8 @@ export interface TelemetryEvents extends WebviewShowAbortedEvents, WebviewShownE /** Sent when the user changes the selected tab (mode) on the Graph Details view */ 'graphDetails/mode/changed': GraphDetailsModeChangedEvent; + /** Sent when a Home command is executed */ + 'home/command': CommandEventData; /** Sent when the new Home view preview is toggled on/off */ 'home/preview/toggled': HomePreviewToggledEvent; /** Sent when the user chooses to create a branch from the home view */ diff --git a/src/system/vscode/command.ts b/src/system/vscode/command.ts index 39ed01aa7c5e9..9cda48d0c652e 100644 --- a/src/system/vscode/command.ts +++ b/src/system/vscode/command.ts @@ -46,6 +46,12 @@ export function registerCommand(command: Commands, callback: CommandCallback, th 'context.mode': context?.mode, 'context.submode': context?.submode, }); + } else if (command.startsWith('gitlens.home.')) { + Container.instance.telemetry.sendEvent('home/command', { + command: command, + 'context.mode': context?.mode, + 'context.submode': context?.submode, + }); } void Container.instance.usage.track(`command:${command}:executed`).catch(); @@ -75,6 +81,11 @@ export function registerWebviewCommand(command: Commands, callback: CommandCallb command: command, webview: webview ?? '', }); + } else if (webview === 'gitlens.views.home' || command.startsWith('gitlens.home.')) { + Container.instance.telemetry.sendEvent('home/command', { + command: command, + webview: webview ?? '', + }); } void Container.instance.usage.track(`command:${command}:executed`).catch(); diff --git a/src/views/nodes/pausedOperationStatusNode.ts b/src/views/nodes/pausedOperationStatusNode.ts index a2941b80ab289..100a5d0b3102a 100644 --- a/src/views/nodes/pausedOperationStatusNode.ts +++ b/src/views/nodes/pausedOperationStatusNode.ts @@ -1,4 +1,4 @@ -import { MarkdownString, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; +import { MarkdownString, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode'; import type { Colors } from '../../constants.colors'; import { GitUri } from '../../git/gitUri'; import type { GitBranch } from '../../git/models/branch'; @@ -7,7 +7,6 @@ import { getReferenceLabel } from '../../git/models/reference.utils'; import type { GitStatus } from '../../git/models/status'; import { pausedOperationStatusStringsByType } from '../../git/utils/pausedOperationStatus.utils'; import { pluralize } from '../../system/string'; -import { executeCoreCommand } from '../../system/vscode/command'; import type { ViewsWithCommits } from '../viewBase'; import { createViewDecorationUri } from '../viewDecorationProvider'; import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; @@ -172,13 +171,4 @@ export class PausedOperationStatusNode extends ViewNode<'paused-operation-status markdown.isTrusted = true; return markdown; } - - async openRebaseEditor() { - if (this.pausedOpStatus.type !== 'rebase') return; - - const rebaseTodoUri = Uri.joinPath(this.uri, '.git', 'rebase-merge', 'git-rebase-todo'); - await executeCoreCommand('vscode.openWith', rebaseTodoUri, 'gitlens.rebase', { - preview: false, - }); - } } diff --git a/src/views/viewCommands.ts b/src/views/viewCommands.ts index fd5a315479524..27c4a86796435 100644 --- a/src/views/viewCommands.ts +++ b/src/views/viewCommands.ts @@ -81,6 +81,7 @@ import type { FileRevisionAsCommitNode } from './nodes/fileRevisionAsCommitNode' import type { FolderNode } from './nodes/folderNode'; import type { LineHistoryNode } from './nodes/lineHistoryNode'; import type { MergeConflictFileNode } from './nodes/mergeConflictFileNode'; +import type { PausedOperationStatusNode } from './nodes/pausedOperationStatusNode'; import type { PullRequestNode } from './nodes/pullRequestNode'; import type { RemoteNode } from './nodes/remoteNode'; import type { RepositoryNode } from './nodes/repositoryNode'; @@ -403,6 +404,15 @@ export class ViewCommands implements Disposable { true, ), + registerViewCommand('gitlens.views.abortPausedOperation', this.abortPausedOperation, this), + registerViewCommand('gitlens.views.continuePausedOperation', this.continuePausedOperation, this), + registerViewCommand('gitlens.views.skipPausedOperation', this.skipPausedOperation, this), + registerViewCommand( + 'gitlens.views.openPausedOperationInRebaseEditor', + this.openPausedOperationInRebaseEditor, + this, + ), + registerViewCommand( 'gitlens.views.setResultsCommitsFilterAuthors', n => this.setResultsCommitsFilter(n, true), @@ -704,6 +714,61 @@ export class ViewCommands implements Disposable { return executeCoreCommand('openInIntegratedTerminal', Uri.file(node.repoPath)); } + @log() + private async abortPausedOperation(node: PausedOperationStatusNode) { + if (!node.is('paused-operation-status')) return; + + const abortPausedOperation = this.container.git.status(node.pausedOpStatus.repoPath).abortPausedOperation; + if (abortPausedOperation == null) return; + + try { + await abortPausedOperation(); + } catch (ex) { + void window.showErrorMessage(ex.message); + } + } + + @log() + private async continuePausedOperation(node: PausedOperationStatusNode) { + if (!node.is('paused-operation-status')) return; + + const continuePausedOperation = this.container.git.status(node.pausedOpStatus.repoPath).continuePausedOperation; + if (continuePausedOperation == null) return; + + try { + await continuePausedOperation(); + } catch (ex) { + void window.showErrorMessage(ex.message); + } + } + + @log() + private async skipPausedOperation(node: PausedOperationStatusNode) { + if (!node.is('paused-operation-status')) return; + + const continuePausedOperation = this.container.git.status(node.pausedOpStatus.repoPath).continuePausedOperation; + if (continuePausedOperation == null) return; + + try { + await continuePausedOperation({ skip: true }); + } catch (ex) { + void window.showErrorMessage(ex.message); + } + } + + @log() + private async openPausedOperationInRebaseEditor(node: PausedOperationStatusNode) { + if (!node.is('paused-operation-status') || node.pausedOpStatus.type !== 'rebase') return; + + const gitDir = await this.container.git.getGitDir(node.repoPath); + if (gitDir == null) return; + + const rebaseTodoUri = Uri.joinPath(gitDir.uri, 'rebase-merge', 'git-rebase-todo'); + void executeCoreCommand('vscode.openWith', rebaseTodoUri, 'gitlens.rebase', { + preview: false, + }); + } + @log() private openPullRequest(node: PullRequestNode) { if (!node.is('pullrequest')) return Promise.resolve(); diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx index 2d7fcfd346f77..27516ce951dcb 100644 --- a/src/webviews/apps/plus/graph/GraphWrapper.tsx +++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx @@ -93,6 +93,7 @@ import type { import type { DateTimeFormat } from '../../shared/date'; import { formatDate, fromNow } from '../../shared/date'; import { emitTelemetrySentEvent } from '../../shared/telemetry'; +import { GlMergeConflictWarning } from '../shared/components/merge-rebase-status.react'; import { GitActionsButtons } from './actions/gitActionsButtons'; import { GlGraphHover } from './hover/graphHover.react'; import type { GraphMinimapDaySelectedEventDetail } from './minimap/minimap'; @@ -1138,7 +1139,9 @@ export function GraphWrapper({ disabled={repos.length < 2} onClick={() => handleChooseRepository()} > - {repo?.formattedName ?? 'none selected'} + + {repo?.formattedName ?? 'none selected'} + {repos.length > 1 && (