diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d9499..37f3cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Adds a `worktrees.openAfterCreate` setting to specify how and when to open a worktree after it is created - Ensures new worktrees are created from the "main" repo, if already in a worktree - Adds a new _remote_ command to the _Git Command Palette_ to add, prune, and remove remotes -- Adds a _Create Worktree for Pull Request via GitLens..._ context menu command on pull requests in the _GitHub Pull Requests and Issues_ extension's views +- Adds a _Open Worktree for Pull Request via GitLens..._ context menu command on pull requests in the _GitHub Pull Requests and Issues_ extension's views + - Opens an associated worktree, if one exists, otherwise it creates a new worktree for the pull request - Adds settings to control the format of commits in the GitLens views ### Changed diff --git a/package.json b/package.json index 89dce51..0fc2666 100644 --- a/package.json +++ b/package.json @@ -5571,8 +5571,8 @@ "enablement": "!operationInProgress" }, { - "command": "gitlens.ghpr.views.createWorktree", - "title": "Create Worktree for Pull Request via GitLens...", + "command": "gitlens.ghpr.views.openOrCreateWorktree", + "title": "Open Worktree for Pull Request via GitLens...", "category": "GitLens", "enablement": "!operationInProgress" }, @@ -8064,7 +8064,7 @@ "when": "false" }, { - "command": "gitlens.ghpr.views.createWorktree", + "command": "gitlens.ghpr.views.openOrCreateWorktree", "when": "false" }, { @@ -11483,7 +11483,7 @@ "group": "1_gitlens@0" }, { - "command": "gitlens.ghpr.views.createWorktree", + "command": "gitlens.ghpr.views.openOrCreateWorktree", "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && view == pr:github && viewItem =~ /pullrequest(:local)?:nonactive|description/", "group": "pullrequest@2" } diff --git a/src/commands.ts b/src/commands.ts index bf07659..07b9d88 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -17,7 +17,7 @@ export * from './commands/diffWithRevision'; export * from './commands/diffWithRevisionFrom'; export * from './commands/diffWithWorking'; export * from './commands/externalDiff'; -export * from './commands/ghpr/createWorktree'; +export * from './commands/ghpr/openOrCreateWorktree'; export * from './commands/gitCommands'; export * from './commands/inviteToLiveShare'; export * from './commands/logging'; diff --git a/src/commands/ghpr/createWorktree.ts b/src/commands/ghpr/openOrCreateWorktree.ts similarity index 90% rename from src/commands/ghpr/createWorktree.ts rename to src/commands/ghpr/openOrCreateWorktree.ts index 9571843..2b0fc12 100644 --- a/src/commands/ghpr/createWorktree.ts +++ b/src/commands/ghpr/openOrCreateWorktree.ts @@ -3,7 +3,7 @@ import { window } from 'vscode'; import { Commands } from '../../constants'; import type { Container } from '../../container'; import { add as addRemote } from '../../git/actions/remote'; -import { create as createWorktree } from '../../git/actions/worktree'; +import { create as createWorktree, open as openWorktree } from '../../git/actions/worktree'; import { GitReference } from '../../git/models/reference'; import type { GitRemote } from '../../git/models/remote'; import { parseGitRemoteUrl } from '../../git/parsers/remoteParser'; @@ -41,9 +41,9 @@ interface PullRequest { } @command() -export class CreateWorktreeCommand extends Command { +export class OpenOrCreateWorktreeCommand extends Command { constructor(private readonly container: Container) { - super(Commands.CreateWorktreeForGHPR); + super(Commands.OpenOrCreateWorktreeForGHPR); } async execute(...args: [PullRequestNode | PullRequest, ...unknown[]]) { @@ -79,6 +79,14 @@ export class CreateWorktreeCommand extends Command { return; } + const worktrees = await repo.getWorktrees(); + const worktree = worktrees.find(w => w.branch === ref); + if (worktree != null) { + void openWorktree(worktree); + + return; + } + const remoteUrl = remoteUri.toString(); const [, remoteDomain, remotePath] = parseGitRemoteUrl(remoteUrl); diff --git a/src/commands/git/worktree.ts b/src/commands/git/worktree.ts index eff4b99..1fa8cbf 100644 --- a/src/commands/git/worktree.ts +++ b/src/commands/git/worktree.ts @@ -1,10 +1,10 @@ import type { MessageItem } from 'vscode'; -import { QuickInputButtons, Uri, window } from 'vscode'; +import { QuickInputButtons, Uri, window, workspace } from 'vscode'; import type { Config } from '../../configuration'; import { configuration } from '../../configuration'; import type { Container } from '../../container'; import { PlusFeatures } from '../../features'; -import { open, reveal, revealInFileExplorer } from '../../git/actions/worktree'; +import { convertOpenFlagsToLocation, reveal, revealInFileExplorer } from '../../git/actions/worktree'; import { WorktreeCreateError, WorktreeCreateErrorReason, @@ -22,7 +22,7 @@ import type { FlagsQuickPickItem } from '../../quickpicks/items/flags'; import { createFlagsQuickPickItem } from '../../quickpicks/items/flags'; import { basename, isDescendent } from '../../system/path'; import { pluralize, truncateLeft } from '../../system/string'; -import { OpenWorkspaceLocation } from '../../system/utils'; +import { openWorkspace, OpenWorkspaceLocation } from '../../system/utils'; import type { ViewsWithRepositoryFolders } from '../../views/viewBase'; import type { AsyncStepResultGenerator, @@ -85,7 +85,7 @@ interface DeleteState { flags: DeleteFlags[]; } -type OpenFlags = '--new-window' | '--reveal-explorer'; +type OpenFlags = '--add-to-workspace' | '--new-window' | '--reveal-explorer'; interface OpenState { subcommand: 'open'; @@ -385,8 +385,9 @@ export class WorktreeGitCommand extends QuickCommand { ...(state.createBranch ?? state.reference.name).replace(/\\/g, '/').split('/'), ); + let worktree: GitWorktree | undefined; try { - const worktree = await state.repo.createWorktree(uri, { + worktree = await state.repo.createWorktree(uri, { commitish: state.reference?.name, createBranch: state.flags.includes('-b') ? state.createBranch : undefined, detach: state.flags.includes('--detach'), @@ -399,55 +400,6 @@ export class WorktreeGitCommand extends QuickCommand { focus: true, }); } - - if (worktree == null) return; - - type OpenAction = Config['worktrees']['openAfterCreate'] | 'addToWorkspace'; - let action: OpenAction = configuration.get('worktrees.openAfterCreate'); - if (action === 'never') return; - - queueMicrotask(async () => { - if (action === 'prompt') { - type ActionMessageItem = MessageItem & { action: OpenAction }; - const open: ActionMessageItem = { title: 'Open', action: 'always' }; - const openNewWindow: ActionMessageItem = { - title: 'Open in New Window', - action: 'alwaysNewWindow', - }; - const addToWorkspace: ActionMessageItem = { - title: 'Add to Workspace', - action: 'addToWorkspace', - }; - const cancel: ActionMessageItem = { title: 'Cancel', isCloseAffordance: true, action: 'never' }; - - const result = await window.showInformationMessage( - `Would you like to open the new worktree, or add it to the current workspace?`, - { modal: true }, - open, - openNewWindow, - addToWorkspace, - cancel, - ); - - action = result?.action ?? 'never'; - } - - switch (action) { - case 'always': - open(worktree, { - location: OpenWorkspaceLocation.CurrentWindow, - }); - break; - case 'alwaysNewWindow': - open(worktree, { location: OpenWorkspaceLocation.NewWindow }); - break; - case 'addToWorkspace': - open(worktree, { - location: OpenWorkspaceLocation.AddToWorkspace, - }); - break; - } - }); } catch (ex) { if ( WorktreeCreateError.is(ex, WorktreeCreateErrorReason.AlreadyCheckedOut) && @@ -495,6 +447,44 @@ export class WorktreeGitCommand extends QuickCommand { } endSteps(state); + if (worktree == null) break; + + type OpenAction = Config['worktrees']['openAfterCreate']; + const action: OpenAction = configuration.get('worktrees.openAfterCreate'); + if (action === 'never') break; + + if (action === 'prompt') { + yield* this.openCommandSteps( + { + subcommand: 'open', + repo: state.repo, + uri: worktree.uri, + counter: 3, + confirm: true, + } as OpenStepState, + context, + ); + + break; + } + + queueMicrotask(() => { + switch (action) { + case 'always': + openWorkspace(worktree!.uri, { location: OpenWorkspaceLocation.CurrentWindow }); + break; + case 'alwaysNewWindow': + openWorkspace(worktree!.uri, { location: OpenWorkspaceLocation.NewWindow }); + break; + case 'onlyWhenEmpty': + openWorkspace(worktree!.uri, { + location: workspace.workspaceFolders?.length + ? OpenWorkspaceLocation.CurrentWindow + : OpenWorkspaceLocation.NewWindow, + }); + break; + } + }); } } @@ -804,15 +794,13 @@ export class WorktreeGitCommand extends QuickCommand { endSteps(state); - const worktree = context.worktrees.find(wt => wt.uri.toString() === state.uri.toString())!; + const worktree = context.worktrees.find(wt => wt.uri.toString() === state.uri.toString()); + if (worktree == null) break; + if (state.flags.includes('--reveal-explorer')) { void revealInFileExplorer(worktree); } else { - open(worktree, { - location: state.flags.includes('--new-window') - ? OpenWorkspaceLocation.NewWindow - : OpenWorkspaceLocation.CurrentWindow, - }); + openWorkspace(worktree.uri, { location: convertOpenFlagsToLocation(state.flags) }); } } } @@ -823,19 +811,25 @@ export class WorktreeGitCommand extends QuickCommand { [ createFlagsQuickPickItem(state.flags, [], { label: context.title, - detail: `Will open, in the current window, the worktree in $(folder) ${GitWorktree.getFriendlyPath( + detail: `Will open in the current window, the worktree in $(folder) ${GitWorktree.getFriendlyPath( state.uri, )}`, }), createFlagsQuickPickItem(state.flags, ['--new-window'], { label: `${context.title} in a New Window`, - detail: `Will open, in a new window, the worktree in $(folder) ${GitWorktree.getFriendlyPath( + detail: `Will open in a new window, the worktree in $(folder) ${GitWorktree.getFriendlyPath( + state.uri, + )}`, + }), + createFlagsQuickPickItem(state.flags, ['--add-to-workspace'], { + label: `Add Worktree to Workspace`, + detail: `Will add into the current workspace, the worktree in $(folder) ${GitWorktree.getFriendlyPath( state.uri, )}`, }), createFlagsQuickPickItem(state.flags, ['--reveal-explorer'], { label: `Reveal in File Explorer`, - detail: `Will open, in the File Explorer, the worktree in $(folder) ${GitWorktree.getFriendlyPath( + detail: `Will open in the File Explorer, the worktree in $(folder) ${GitWorktree.getFriendlyPath( state.uri, )}`, }), diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts index 2067ab3..d05189c 100644 --- a/src/commands/quickCommand.steps.ts +++ b/src/commands/quickCommand.steps.ts @@ -89,7 +89,7 @@ import { formatPath } from '../system/formatPath'; import { map } from '../system/iterable'; import { getSettledValue } from '../system/promise'; import { pad, pluralize, truncate } from '../system/string'; -import { OpenWorkspaceLocation } from '../system/utils'; +import { openWorkspace, OpenWorkspaceLocation } from '../system/utils'; import type { ViewsWithRepositoryFolders } from '../views/viewBase'; import type { AsyncStepResultGenerator, @@ -1635,7 +1635,7 @@ export async function* pickWorktreeStep< onDidClickItemButton: (quickpick, button, { item }) => { switch (button) { case QuickCommandButtons.OpenInNewWindow: - WorktreeActions.open(item, { location: OpenWorkspaceLocation.NewWindow }); + openWorkspace(item.uri, { location: OpenWorkspaceLocation.NewWindow }); break; case QuickCommandButtons.RevealInSideBar: void WorktreeActions.reveal(item, { select: true, focus: false, expand: true }); @@ -1696,7 +1696,7 @@ export async function* pickWorktreesStep< onDidClickItemButton: (quickpick, button, { item }) => { switch (button) { case QuickCommandButtons.OpenInNewWindow: - WorktreeActions.open(item, { location: OpenWorkspaceLocation.NewWindow }); + openWorkspace(item.uri, { location: OpenWorkspaceLocation.NewWindow }); break; case QuickCommandButtons.RevealInSideBar: void WorktreeActions.reveal(item, { select: true, focus: false, expand: true }); diff --git a/src/constants.ts b/src/constants.ts index 42a571e..555a003 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -170,6 +170,7 @@ export const enum Commands { GitCommandsSwitch = 'gitlens.gitCommands.switch', GitCommandsTag = 'gitlens.gitCommands.tag', GitCommandsWorktree = 'gitlens.gitCommands.worktree', + OpenOrCreateWorktreeForGHPR = 'gitlens.ghpr.views.openOrCreateWorktree', PlusHide = 'gitlens.plus.hide', PlusLearn = 'gitlens.plus.learn', PlusLoginOrSignUp = 'gitlens.plus.loginOrSignUp', @@ -259,8 +260,6 @@ export const enum Commands { ViewsOpenDirectoryDiff = 'gitlens.views.openDirectoryDiff', ViewsOpenDirectoryDiffWithWorking = 'gitlens.views.openDirectoryDiffWithWorking', - CreateWorktreeForGHPR = 'gitlens.ghpr.views.createWorktree', - Deprecated_DiffHeadWith = 'gitlens.diffHeadWith', Deprecated_DiffWorkingWith = 'gitlens.diffWorkingWith', Deprecated_OpenBranchesInRemote = 'gitlens.openBranchesInRemote', diff --git a/src/git/actions/worktree.ts b/src/git/actions/worktree.ts index 5c69197..b7e2813 100644 --- a/src/git/actions/worktree.ts +++ b/src/git/actions/worktree.ts @@ -1,10 +1,10 @@ import type { Uri } from 'vscode'; +import type { WorktreeGitCommandArgs } from '../../commands/git/worktree'; import { CoreCommands } from '../../constants'; import { Container } from '../../container'; import { ensure } from '../../system/array'; import { executeCoreCommand } from '../../system/command'; -import type { OpenWorkspaceLocation } from '../../system/utils'; -import { openWorkspace } from '../../system/utils'; +import { OpenWorkspaceLocation } from '../../system/utils'; import { executeGitCommand } from '../actions'; import type { GitReference } from '../models/reference'; import type { Repository } from '../models/repository'; @@ -18,7 +18,15 @@ export function create(repo?: string | Repository, uri?: Uri, ref?: GitReference } export function open(worktree: GitWorktree, options?: { location?: OpenWorkspaceLocation }) { - return openWorkspace(worktree.uri, options); + return executeGitCommand({ + command: 'worktree', + state: { + subcommand: 'open', + repo: worktree.repoPath, + uri: worktree.uri, + flags: convertLocationToOpenFlags(options?.location), + }, + }); } export function remove(repo?: string | Repository, uri?: Uri) { @@ -48,3 +56,27 @@ export async function reveal( export async function revealInFileExplorer(worktree: GitWorktree) { void (await executeCoreCommand(CoreCommands.RevealInFileExplorer, worktree.uri)); } + +type OpenFlagsArray = Extract>, { subcommand: 'open' }>['flags']; + +export function convertLocationToOpenFlags(location: OpenWorkspaceLocation | undefined): OpenFlagsArray | undefined { + if (location == null) return undefined; + + switch (location) { + case OpenWorkspaceLocation.NewWindow: + return ['--new-window']; + case OpenWorkspaceLocation.AddToWorkspace: + return ['--add-to-workspace']; + case OpenWorkspaceLocation.CurrentWindow: + default: + return []; + } +} + +export function convertOpenFlagsToLocation(flags: OpenFlagsArray | undefined): OpenWorkspaceLocation | undefined { + if (flags == null) return undefined; + + if (flags.includes('--new-window')) return OpenWorkspaceLocation.NewWindow; + if (flags.includes('--add-to-workspace')) return OpenWorkspaceLocation.AddToWorkspace; + return OpenWorkspaceLocation.CurrentWindow; +} diff --git a/src/views/viewCommands.ts b/src/views/viewCommands.ts index 5fa86f5..8238680 100644 --- a/src/views/viewCommands.ts +++ b/src/views/viewCommands.ts @@ -33,7 +33,7 @@ import { } from '../system/command'; import { debug } from '../system/decorators/log'; import { sequentialize } from '../system/function'; -import { OpenWorkspaceLocation } from '../system/utils'; +import { openWorkspace, OpenWorkspaceLocation } from '../system/utils'; import type { BranchesNode } from './nodes/branchesNode'; import { BranchNode } from './nodes/branchNode'; import { BranchTrackingStatusNode } from './nodes/branchTrackingStatusNode'; @@ -572,7 +572,7 @@ export class ViewCommands { private openWorktree(node: WorktreeNode, options?: { location?: OpenWorkspaceLocation }) { if (!(node instanceof WorktreeNode)) return undefined; - return WorktreeActions.open(node.worktree, options); + return openWorkspace(node.worktree.uri, options); } @debug()