Skip to content

Commit 41600ec

Browse files
Opens all changes after open in worktree flow finished for PRs (#3701)
* Opens all changes after open in worktree flow finished for PRs * Update src/uris/deepLinks/deepLinkService.ts Co-authored-by: Ramin Tadayon <[email protected]> * Refinements - Only shows changes for "open in worktree" action - Removes unnecessary state transition - Only shows changes if worktree is created in flow - Applies to branches in views and graph --------- Co-authored-by: Ramin Tadayon <[email protected]> Co-authored-by: Ramin Tadayon <[email protected]>
1 parent f8d40c6 commit 41600ec

File tree

7 files changed

+150
-21
lines changed

7 files changed

+150
-21
lines changed

src/commands/git/switch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ interface Context {
3434

3535
interface State {
3636
repos: string | string[] | Repository | Repository[];
37-
onWorkspaceChanging?: (() => Promise<void>) | (() => void);
37+
onWorkspaceChanging?: ((isNewWorktree?: boolean) => Promise<void>) | ((isNewWorktree?: boolean) => void);
3838
reference: GitReference;
3939
createBranch?: string;
4040
fastForwardTo?: GitReference;

src/commands/git/worktree.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ interface CreateState {
102102
title?: string;
103103
};
104104

105-
onWorkspaceChanging?: (() => Promise<void>) | (() => void);
105+
onWorkspaceChanging?: ((isNewWorktree?: boolean) => Promise<void>) | ((isNewWorktree?: boolean) => void);
106106
skipWorktreeConfirmations?: boolean;
107107
}
108108

@@ -139,7 +139,8 @@ interface OpenState {
139139
};
140140
};
141141

142-
onWorkspaceChanging?: (() => Promise<void>) | (() => void);
142+
onWorkspaceChanging?: ((isNewWorktree?: boolean) => Promise<void>) | ((isNewWorktree?: boolean) => void);
143+
isNewWorktree?: boolean;
143144
skipWorktreeConfirmations?: boolean;
144145
}
145146

@@ -628,6 +629,7 @@ export class WorktreeGitCommand extends QuickCommand<State> {
628629
confirm: action === 'prompt',
629630
openOnly: true,
630631
overrides: { disallowBack: true },
632+
isNewWorktree: true,
631633
skipWorktreeConfirmations: state.skipWorktreeConfirmations,
632634
onWorkspaceChanging: state.onWorkspaceChanging,
633635
} satisfies OpenStepState,
@@ -1081,7 +1083,7 @@ export class WorktreeGitCommand extends QuickCommand<State> {
10811083

10821084
const location = convertOpenFlagsToLocation(state.flags);
10831085
if (location === 'currentWindow' || location === 'newWindow') {
1084-
await state.onWorkspaceChanging?.();
1086+
await state.onWorkspaceChanging?.(state.isNewWorktree);
10851087
}
10861088

10871089
openWorkspace(state.worktree.uri, { location: convertOpenFlagsToLocation(state.flags), name: name });

src/plus/launchpad/launchpadProvider.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,13 @@ export class LaunchpadProvider implements Disposable {
540540
? item.openRepository.localBranch.name
541541
: item.headRef.name;
542542

543-
return getPullRequestBranchDeepLink(this.container, branchName, item.repoIdentity.remote.url, action);
543+
return getPullRequestBranchDeepLink(
544+
this.container,
545+
branchName,
546+
item.repoIdentity.remote.url,
547+
action,
548+
item.underlyingPullRequest,
549+
);
544550
}
545551

546552
private async getMatchingOpenRepository(
@@ -1023,17 +1029,31 @@ export function getPullRequestBranchDeepLink(
10231029
headRefBranchName: string,
10241030
remoteUrl: string,
10251031
action?: DeepLinkActionType,
1032+
pr?: PullRequest,
10261033
) {
10271034
const schemeOverride = configuration.get('deepLinks.schemeOverride');
10281035
const scheme = typeof schemeOverride === 'string' ? schemeOverride : env.uriScheme;
1036+
1037+
const searchParams = new URLSearchParams({
1038+
url: ensureRemoteUrl(remoteUrl),
1039+
});
1040+
if (action) {
1041+
searchParams.set('action', action);
1042+
}
1043+
if (pr) {
1044+
searchParams.set('prId', pr.id);
1045+
searchParams.set('prTitle', pr.title);
1046+
}
1047+
if (pr?.refs) {
1048+
searchParams.set('prBaseRef', pr.refs.base.sha);
1049+
searchParams.set('prHeadRef', pr.refs.head.sha);
1050+
}
10291051
// TODO: Get the proper pull URL from the provider, rather than tacking .git at the end of the
10301052
// url from the head ref.
10311053
return Uri.parse(
10321054
`${scheme}://${container.context.extension.id}/${'link' satisfies UriTypes}/${DeepLinkType.Repository}/-/${
10331055
DeepLinkType.Branch
1034-
}/${encodeURIComponent(headRefBranchName)}?url=${encodeURIComponent(ensureRemoteUrl(remoteUrl))}${
1035-
action != null ? `&action=${action}` : ''
1036-
}`,
1056+
}/${encodeURIComponent(headRefBranchName)}?${searchParams.toString()}`,
10371057
);
10381058
}
10391059

src/plus/webviews/graph/graphWebview.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ import { GitContributor } from '../../../git/models/contributor';
6262
import type { GitGraph, GitGraphRowType } from '../../../git/models/graph';
6363
import { getGkProviderThemeIconString } from '../../../git/models/graph';
6464
import type { PullRequest } from '../../../git/models/pullRequest';
65-
import { getComparisonRefsForPullRequest, serializePullRequest } from '../../../git/models/pullRequest';
65+
import {
66+
getComparisonRefsForPullRequest,
67+
getRepositoryIdentityForPullRequest,
68+
serializePullRequest,
69+
} from '../../../git/models/pullRequest';
6670
import type {
6771
GitBranchReference,
6872
GitReference,
@@ -115,6 +119,7 @@ import { getContext, onDidChangeContext } from '../../../system/vscode/context';
115119
import type { OpenWorkspaceLocation } from '../../../system/vscode/utils';
116120
import { isDarkTheme, isLightTheme, openWorkspace } from '../../../system/vscode/utils';
117121
import { isWebviewItemContext, isWebviewItemGroupContext, serializeWebviewItemContext } from '../../../system/webview';
122+
import { DeepLinkActionType } from '../../../uris/deepLinks/deepLink';
118123
import { RepositoryFolderNode } from '../../../views/nodes/abstract/repositoryFolderNode';
119124
import type { IpcCallMessageType, IpcMessage, IpcNotification } from '../../../webviews/protocol';
120125
import type { WebviewHost, WebviewProvider, WebviewShowingArgs } from '../../../webviews/webviewProvider';
@@ -123,6 +128,7 @@ import { isSerializedState } from '../../../webviews/webviewsController';
123128
import type { SubscriptionChangeEvent } from '../../gk/account/subscriptionService';
124129
import type { ConnectionStateChangeEvent } from '../../integrations/integrationService';
125130
import { remoteProviderIdToIntegrationId } from '../../integrations/integrationService';
131+
import { getPullRequestBranchDeepLink } from '../../launchpad/launchpadProvider';
126132
import type {
127133
BranchState,
128134
DidChangeRefsVisibilityParams,
@@ -3643,6 +3649,24 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
36433649
private async openInWorktree(item?: GraphItemContext) {
36443650
if (isGraphItemRefContext(item, 'branch')) {
36453651
const { ref } = item.webviewItemValue;
3652+
const repo = this.container.git.getRepository(ref.repoPath);
3653+
const branch = await repo?.git.getBranch(ref.name);
3654+
const pr = await branch?.getAssociatedPullRequest();
3655+
if (branch != null && repo != null && pr != null) {
3656+
const remoteUrl = (await branch.getRemote())?.url ?? getRepositoryIdentityForPullRequest(pr).remote.url;
3657+
if (remoteUrl != null) {
3658+
const deepLink = getPullRequestBranchDeepLink(
3659+
this.container,
3660+
branch.getNameWithoutRemote(),
3661+
remoteUrl,
3662+
DeepLinkActionType.SwitchToPullRequestWorktree,
3663+
pr,
3664+
);
3665+
3666+
return this.container.deepLinks.processDeepLinkUri(deepLink, false, repo);
3667+
}
3668+
}
3669+
36463670
await executeGitCommand({
36473671
command: 'switch',
36483672
state: {

src/uris/deepLinks/deepLink.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export interface DeepLink {
100100
secondaryTargetId?: string;
101101
secondaryRemoteUrl?: string;
102102
action?: string;
103+
prId?: string;
103104
params?: URLSearchParams;
104105
}
105106

@@ -178,6 +179,7 @@ export function parseDeepLinkUri(uri: Uri): DeepLink | undefined {
178179
secondaryRemoteUrl: secondaryRemoteUrl,
179180
action: action,
180181
params: urlParams,
182+
prId: urlParams.get('prId') ?? undefined,
181183
};
182184
}
183185
case DeepLinkType.Draft: {
@@ -239,6 +241,7 @@ export const enum DeepLinkServiceState {
239241
OpenInspect,
240242
SwitchToRef,
241243
RunCommand,
244+
OpenAllPrChanges,
242245
}
243246

244247
export const enum DeepLinkServiceAction {
@@ -271,6 +274,7 @@ export const enum DeepLinkServiceAction {
271274
OpenFile,
272275
OpenInspect,
273276
OpenSwitch,
277+
OpenAllPrChanges,
274278
}
275279

276280
export type DeepLinkRepoOpenType = 'clone' | 'folder' | 'workspace' | 'current';
@@ -417,6 +421,12 @@ export const deepLinkStateTransitionTable: Record<string, Record<string, DeepLin
417421
[DeepLinkServiceAction.DeepLinkCancelled]: DeepLinkServiceState.Idle,
418422
},
419423
[DeepLinkServiceState.OpenInspect]: {
424+
[DeepLinkServiceAction.OpenAllPrChanges]: DeepLinkServiceState.OpenAllPrChanges,
425+
[DeepLinkServiceAction.DeepLinkResolved]: DeepLinkServiceState.Idle,
426+
[DeepLinkServiceAction.DeepLinkErrored]: DeepLinkServiceState.Idle,
427+
[DeepLinkServiceAction.DeepLinkCancelled]: DeepLinkServiceState.Idle,
428+
},
429+
[DeepLinkServiceState.OpenAllPrChanges]: {
420430
[DeepLinkServiceAction.DeepLinkResolved]: DeepLinkServiceState.Idle,
421431
[DeepLinkServiceAction.DeepLinkErrored]: DeepLinkServiceState.Idle,
422432
[DeepLinkServiceAction.DeepLinkCancelled]: DeepLinkServiceState.Idle,

src/uris/deepLinks/deepLinkService.ts

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Commands } from '../../constants.commands';
44
import type { StoredDeepLinkContext, StoredNamedRef } from '../../constants.storage';
55
import type { Container } from '../../container';
66
import { executeGitCommand } from '../../git/actions';
7-
import { openFileAtRevision } from '../../git/actions/commit';
7+
import { openComparisonChanges, openFileAtRevision } from '../../git/actions/commit';
88
import type { GitBranch } from '../../git/models/branch';
99
import { getBranchNameWithoutRemote } from '../../git/models/branch';
1010
import type { GitCommit } from '../../git/models/commit';
@@ -19,6 +19,7 @@ import { missingRepositoryId } from '../../gk/models/repositoryIdentities';
1919
import { ensureAccount, ensurePaidPlan } from '../../plus/utils';
2020
import type { ShowInCommitGraphCommandArgs } from '../../plus/webviews/graph/protocol';
2121
import { createQuickPickSeparator } from '../../quickpicks/items/common';
22+
import { debug } from '../../system/decorators/log';
2223
import { once } from '../../system/event';
2324
import { Logger } from '../../system/logger';
2425
import { normalizePath } from '../../system/path';
@@ -68,10 +69,7 @@ export class DeepLinkService implements Disposable {
6869
this._disposables.push(container.uri.onDidReceiveUri(async (uri: Uri) => this.processDeepLinkUri(uri)));
6970

7071
const pendingDeepLink = this.container.storage.get('deepLinks:pending');
71-
if (pendingDeepLink != null) {
72-
void this.container.storage.delete('deepLinks:pending');
73-
void this.processPendingDeepLink(pendingDeepLink);
74-
}
72+
void this.processPendingDeepLink(pendingDeepLink);
7573
}
7674

7775
dispose() {
@@ -241,9 +239,11 @@ export class DeepLinkService implements Disposable {
241239
}
242240
}
243241

244-
private async processPendingDeepLink(pendingDeepLink: StoredDeepLinkContext) {
245-
if (pendingDeepLink.url == null) return;
246-
242+
@debug()
243+
private async processPendingDeepLink(pendingDeepLink: StoredDeepLinkContext | undefined) {
244+
if (pendingDeepLink == null) return;
245+
void this.container.storage.delete('deepLinks:pending');
246+
if (pendingDeepLink?.url == null) return;
247247
const link = parseDeepLinkUri(Uri.parse(pendingDeepLink.url));
248248
if (link == null) return;
249249

@@ -509,6 +509,7 @@ export class DeepLinkService implements Disposable {
509509
// TODO @axosoft-ramint: Move all the logic for matching a repo, prompting to add repo, matching remote, etc. for a target (branch, PR, etc.)
510510
// to a separate service where it can be used outside of the context of deep linking. Then the deep link service should leverage it,
511511
// and we should stop using deep links to process things like Launchpad switch actions, Open in Worktree command, etc.
512+
@debug()
512513
private async processDeepLink(
513514
initialAction: DeepLinkServiceAction = DeepLinkServiceAction.DeepLinkEventFired,
514515
useProgress: boolean = true,
@@ -1078,7 +1079,6 @@ export class DeepLinkService implements Disposable {
10781079
}
10791080
case DeepLinkServiceState.GoToTarget: {
10801081
// Need to re-fetch the remotes in case we opened in a new window
1081-
10821082
if (targetType === DeepLinkType.Repository) {
10831083
if (
10841084
this._context.action === DeepLinkActionType.Switch ||
@@ -1321,9 +1321,25 @@ export class DeepLinkService implements Disposable {
13211321
state: this._context.state,
13221322
};
13231323

1324+
// Form a new link URL with PR info stripped out in case we are opening an existing PR worktree,
1325+
// in which case we do not want to advance to the "Open All PR Changes" step in the flow.
1326+
// We should only advance to that step if a worktree is newly created in the flow.
1327+
const oldUrl = Uri.parse(this._context.url ?? '').toString(true);
1328+
const urlParams = new URL(oldUrl).searchParams;
1329+
urlParams.delete('prId');
1330+
urlParams.delete('prTitle');
1331+
urlParams.delete('prBaseRef');
1332+
urlParams.delete('prHeadRef');
1333+
const newUrlParams = urlParams.toString();
1334+
const nonPrUrl =
1335+
newUrlParams.length > 0 ? `${oldUrl.split('?')[0]}?${newUrlParams}` : oldUrl.split('?')[0];
1336+
13241337
// Storing link info in case the switch causes a new window to open
1325-
const onWorkspaceChanging = async () =>
1326-
this.container.storage.store('deepLinks:pending', pendingDeepLink);
1338+
const onWorkspaceChanging = async (isNewWorktree?: boolean) =>
1339+
this.container.storage.store(
1340+
'deepLinks:pending',
1341+
isNewWorktree ? pendingDeepLink : { ...pendingDeepLink, url: nonPrUrl },
1342+
);
13271343

13281344
await executeGitCommand({
13291345
command: 'switch',
@@ -1373,7 +1389,6 @@ export class DeepLinkService implements Disposable {
13731389
case DeepLinkServiceState.OpenInspect: {
13741390
// If we arrive at this step, clear any stored data used for the "new window" option
13751391
await this.container.storage.delete('deepLinks:pending');
1376-
13771392
if (!repo) {
13781393
action = DeepLinkServiceAction.DeepLinkErrored;
13791394
message = 'Missing repository.';
@@ -1386,6 +1401,46 @@ export class DeepLinkService implements Disposable {
13861401
repository: repo,
13871402
source: 'launchpad',
13881403
} satisfies ShowWipArgs);
1404+
const { params } = this._context;
1405+
if (
1406+
this._context.action === DeepLinkActionType.SwitchToPullRequestWorktree &&
1407+
params != null &&
1408+
(params.get('prId') != null || params.get('prTitle') != null) &&
1409+
params.get('prBaseRef') != null &&
1410+
params.get('prHeadRef') != null
1411+
) {
1412+
action = DeepLinkServiceAction.OpenAllPrChanges;
1413+
break;
1414+
}
1415+
1416+
action = DeepLinkServiceAction.DeepLinkResolved;
1417+
break;
1418+
}
1419+
case DeepLinkServiceState.OpenAllPrChanges: {
1420+
const prId = this._context.params?.get('prId');
1421+
const prHeadRef = this._context.params?.get('prHeadRef');
1422+
const prBaseRef = this._context.params?.get('prBaseRef');
1423+
const prTitle = this._context.params?.get('prTitle');
1424+
if (!repoPath || (!prId && !prTitle) || !prHeadRef || !prBaseRef) {
1425+
action = DeepLinkServiceAction.DeepLinkErrored;
1426+
if (!repoPath) {
1427+
message = 'No repository path was provided.';
1428+
} else if (!prId) {
1429+
message = 'No pull request id provided.';
1430+
} else {
1431+
message = 'No pull request refs was provided.';
1432+
}
1433+
break;
1434+
}
1435+
await openComparisonChanges(
1436+
this.container,
1437+
{
1438+
repoPath: repoPath,
1439+
lhs: prBaseRef,
1440+
rhs: prHeadRef,
1441+
},
1442+
{ title: `Changes in Pull Request ${prTitle ? `"${prTitle}"` : `#${prId}`}` },
1443+
);
13891444
action = DeepLinkServiceAction.DeepLinkResolved;
13901445
break;
13911446
}

src/views/viewCommands.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,23 @@ export class ViewCommands {
786786
if (!node.is('branch') && !node.is('pullrequest') && !node.is('launchpad-item')) return;
787787

788788
if (node.is('branch')) {
789+
const pr = await node.branch.getAssociatedPullRequest();
790+
if (pr != null) {
791+
const remoteUrl =
792+
(await node.branch.getRemote())?.url ?? getRepositoryIdentityForPullRequest(pr).remote.url;
793+
if (remoteUrl != null) {
794+
const deepLink = getPullRequestBranchDeepLink(
795+
this.container,
796+
node.branch.getNameWithoutRemote(),
797+
remoteUrl,
798+
DeepLinkActionType.SwitchToPullRequestWorktree,
799+
pr,
800+
);
801+
802+
return this.container.deepLinks.processDeepLinkUri(deepLink, false, node.repo);
803+
}
804+
}
805+
789806
return executeGitCommand({
790807
command: 'switch',
791808
state: {
@@ -808,6 +825,7 @@ export class ViewCommands {
808825
pr.refs.head.branch,
809826
repoIdentity.remote.url,
810827
DeepLinkActionType.SwitchToPullRequestWorktree,
828+
pr,
811829
);
812830

813831
const prRepo = await getOrOpenPullRequestRepository(this.container, pr, {

0 commit comments

Comments
 (0)