diff --git a/extensions/git/package.json b/extensions/git/package.json index 1851ef87038..982baf17e5b 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -453,6 +453,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.checkoutRef", + "title": "%command.checkoutRef%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.checkoutRefDetached", "title": "%command.checkoutRefDetached%", @@ -1470,6 +1476,10 @@ "command": "git.copyCommitMessage", "when": "false" }, + { + "command": "git.checkoutRef", + "when": "false" + }, { "command": "git.checkoutRefDetached", "when": "false" @@ -2028,6 +2038,13 @@ "group": "9_copy@2" } ], + "scm/historyItemRef/context": [ + { + "command": "git.checkoutRef", + "when": "scmProvider == git", + "group": "1_checkout@1" + } + ], "editor/title": [ { "command": "git.openFile", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 1f6775a2da5..d706b650c8a 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -62,6 +62,7 @@ "command.undoCommit": "Undo Last Commit", "command.checkout": "Checkout to...", "command.checkoutDetached": "Checkout to (Detached)...", + "command.checkoutRef": "Checkout", "command.checkoutRefDetached": "Checkout (Detached)", "command.branch": "Create Branch...", "command.branchFrom": "Create Branch From...", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index cc85c83728a..5266955198f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, SourceControlHistoryItemRef } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; @@ -2505,8 +2505,18 @@ export class CommandCenter { return this._checkout(repository, { detached: true, treeish }); } + @command('git.checkoutRef', { repository: true }) + async checkoutRef(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { + const historyItemRef = historyItem?.references?.find(r => r.id === historyItemRefId); + if (!historyItemRef) { + return false; + } + + return this._checkout(repository, { treeish: historyItemRefId }); + } + @command('git.checkoutRefDetached', { repository: true }) - async checkoutRefDetached(repository: Repository, historyItem?: SourceControlHistoryItemRef): Promise { + async checkoutRefDetached(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { if (!historyItem) { return false; } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index cdc635fb5b2..7f00003c0f3 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -124,6 +124,7 @@ export class MenuId { static readonly SCMSourceControlInline = new MenuId('SCMSourceControlInline'); static readonly SCMSourceControlTitle = new MenuId('SCMSourceControlTitle'); static readonly SCMHistoryTitle = new MenuId('SCMHistoryTitle'); + static readonly SCMHistoryItemRefContext = new MenuId('SCMHistoryItemRefContext'); static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); static readonly SearchActionMenu = new MenuId('SearchActionContext'); diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 8b786a6d407..008b66f029a 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -40,11 +40,11 @@ import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/lis import { stripIcons } from '../../../../base/common/iconLabels.js'; import { IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; -import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { Sequencer, Throttler } from '../../../../base/common/async.js'; import { URI } from '../../../../base/common/uri.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { ActionRunner, IAction, IActionRunner } from '../../../../base/common/actions.js'; +import { ActionRunner, IAction, IActionRunner, Separator, SubmenuAction } from '../../../../base/common/actions.js'; import { delta, groupBy } from '../../../../base/common/arrays.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { IProgressService } from '../../../../platform/progress/common/progress.js'; @@ -63,6 +63,7 @@ import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hover import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { groupBy as groupBy2 } from '../../../../base/common/collections.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; const PICK_REPOSITORY_ACTION_ID = 'workbench.scm.action.graph.pickRepository'; const PICK_HISTORY_ITEM_REFS_ACTION_ID = 'workbench.scm.action.graph.pickHistoryItemRefs'; @@ -1213,6 +1214,7 @@ export class SCMHistoryViewPane extends ViewPane { options: IViewPaneOptions, @ICommandService private readonly _commandService: ICommandService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IMenuService private readonly _menuService: IMenuService, @IProgressService private readonly _progressService: IProgressService, @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @@ -1570,14 +1572,40 @@ export class SCMHistoryViewPane extends ViewPane { return; } - this.contextMenuService.showContextMenu({ - contextKeyService: this.scopedContextKeyService, - menuId: MenuId.SCMChangesContext, - menuActionOptions: { + const actions: IAction[] = []; + + const historyItemMenu = this._menuService.createMenu(MenuId.SCMChangesContext, this.contextKeyService); + const historyItemMenuActions = historyItemMenu.getActions({ + arg: element.repository.provider, + shouldForwardArgs: true + }); + + actions.push(...getFlatContextMenuActions(historyItemMenuActions)); + if (element.historyItemViewModel.historyItem.references?.length) { + actions.push(new Separator()); + } + + for (const ref of element.historyItemViewModel.historyItem.references ?? []) { + const contextKeyService = this.contextKeyService.createOverlay([ + ['scmHistoryItemRef', ref.id] + ]); + + const historyItemRefMenu = this._menuService.createMenu(MenuId.SCMHistoryItemRefContext, contextKeyService); + const historyItemRefMenuActions = historyItemRefMenu.getActions({ arg: element.repository.provider, shouldForwardArgs: true - }, + }); + + const subMenuActions = getFlatContextMenuActions(historyItemRefMenuActions) + .map(action => ({ ...action, run: (...args: unknown[]) => action.run(...args, ref.id) })); + + actions.push(new SubmenuAction(`scm.historyItemRef.${ref.id}`, ref.name, subMenuActions)); + } + + this.contextMenuService.showContextMenu({ + contextKeyService: this.scopedContextKeyService, getAnchor: () => e.anchor, + getActions: () => actions, getActionsContext: () => element.historyItemViewModel.historyItem }); } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 683d0a92452..7b6dff93618 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -187,6 +187,12 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.historyItemContext', "The Source Control history item context menu"), proposed: 'contribSourceControlHistoryItemMenu' }, + { + key: 'scm/historyItemRef/context', + id: MenuId.SCMHistoryItemRefContext, + description: localize('menus.historyItemRefContext', "The Source Control history item reference context menu"), + proposed: 'contribSourceControlHistoryItemMenu' + }, { key: 'statusBar/remoteIndicator', id: MenuId.StatusBarRemoteIndicatorMenu,