diff --git a/package.json b/package.json index 60ff94d..d0d7eb2 100644 --- a/package.json +++ b/package.json @@ -11613,6 +11613,21 @@ "group": "1_gitlens_actions@1" }, { + "command": "gitlens.graph.push", + "when": "webviewItem =~ /gitlens:upstreamStatus\\b/", + "group": "1_gitlens_actions@1" + }, + { + "command": "gitlens.graph.pull", + "when": "webviewItem =~ /gitlens:upstreamStatus\\b/", + "group": "1_gitlens_actions@2" + }, + { + "command": "gitlens.graph.fetch", + "when": "webviewItem =~ /gitlens:upstreamStatus\\b/", + "group": "1_gitlens_actions@3" + }, + { "command": "gitlens.graph.compareWithUpstream", "when": "!gitlens:hasVirtualFolders && webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+tracking\\b)/", "group": "4_gitlens_compare@1" diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index b2255a2..7e89331 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -702,11 +702,25 @@ export class GraphWebview extends WebviewBase { private onDoubleClick(e: DoubleClickedParams) { if (e.type === 'ref' && e.ref.context) { - const item = typeof e.ref.context === 'string' ? JSON.parse(e.ref.context) : e.ref.context; - if (!('webview' in item)) { - item.webview = this.id; - } + let item = this.getGraphItemContext(e.ref.context); if (isGraphItemRefContext(item)) { + if (e.metadata != null) { + item = this.getGraphItemContext(e.metadata.data.context); + if (e.metadata.type === 'upstream' && isGraphItemTypedContext(item, 'upstreamStatus')) { + const { ahead, behind, ref } = item.webviewItemValue; + if (behind > 0) { + return void RepoActions.pull(ref.repoPath, ref); + } + if (ahead > 0) { + return void RepoActions.push(ref.repoPath, false, ref); + } + } else if (e.metadata.type === 'pullRequest' && isGraphItemTypedContext(item, 'pullrequest')) { + return void this.openPullRequestOnRemote(item); + } + + return; + } + const { ref } = item.webviewItemValue; if (e.ref.refType === 'head' && e.ref.isCurrentHead) { return RepoActions.switchTo(ref.repoPath); @@ -817,8 +831,8 @@ export class GraphWebview extends WebviewBase { const pr = await branch?.getAssociatedPullRequest(); if (pr == null) { - if (metadata.pullRequests === undefined || metadata.pullRequests?.length === 0) { - metadata.pullRequests = null; + if (metadata.pullRequest === undefined || metadata.pullRequest?.length === 0) { + metadata.pullRequest = null; } this._refsMetadata.set(id, metadata); @@ -844,7 +858,7 @@ export class GraphWebview extends WebviewBase { }), }; - metadata.pullRequests = [prMetadata]; + metadata.pullRequest = [prMetadata]; this._refsMetadata.set(id, metadata); continue; @@ -864,6 +878,15 @@ export class GraphWebview extends WebviewBase { owner: getRemoteNameFromBranchName(upstream.name), ahead: branch.state.ahead, behind: branch.state.behind, + context: serializeWebviewItemContext({ + webviewItem: 'gitlens:upstreamStatus', + webviewItemValue: { + type: 'upstreamStatus', + ref: GitReference.fromBranch(branch), + ahead: branch.state.ahead, + behind: branch.state.behind, + }, + }), }; metadata.upstream = upstreamMetadata; @@ -1667,6 +1690,15 @@ export class GraphWebview extends WebviewBase { return [access, visibility] as const; } + private getGraphItemContext(context: unknown): unknown | undefined { + const item = typeof context === 'string' ? JSON.parse(context) : context; + // Add the `webview` prop to the context if its missing (e.g. when this context doesn't come through via the context menus) + if (item != null && !('webview' in item)) { + item.webview = this.id; + } + return item; + } + private async getWorkingTreeStats(): Promise { if (this.repository == null || this.container.git.repositoryCount === 0) return undefined; @@ -1937,18 +1969,21 @@ export class GraphWebview extends WebviewBase { } @debug() - private fetch() { - void RepoActions.fetch(this.repository); + private fetch(item?: GraphItemContext) { + const ref = this.getGraphItemRef(item, 'branch'); + void RepoActions.fetch(this.repository, ref); } @debug() - private pull() { - void RepoActions.pull(this.repository); + private pull(item?: GraphItemContext) { + const ref = this.getGraphItemRef(item, 'branch'); + void RepoActions.pull(this.repository, ref); } @debug() - private push() { - void RepoActions.push(this.repository); + private push(item?: GraphItemContext) { + const ref = this.getGraphItemRef(item); + void RepoActions.push(this.repository, undefined, ref); } @debug() @@ -2446,15 +2481,20 @@ export class GraphWebview extends WebviewBase { private getGraphItemRef(item?: GraphItemContext | unknown | undefined): GitReference | undefined; private getGraphItemRef( item: GraphItemContext | unknown | undefined, + refType: 'branch', + ): GitBranchReference | undefined; + private getGraphItemRef( + item: GraphItemContext | unknown | undefined, refType: 'revision', ): GitRevisionReference | undefined; private getGraphItemRef( item: GraphItemContext | unknown | undefined, refType: 'stash', ): GitStashReference | undefined; + private getGraphItemRef(item: GraphItemContext | unknown | undefined, refType: 'tag'): GitTagReference | undefined; private getGraphItemRef( item?: GraphItemContext | unknown, - refType?: 'revision' | 'stash', + refType?: 'branch' | 'revision' | 'stash' | 'tag', ): GitReference | undefined { if (item == null) { const ref = this.activeSelection; @@ -2462,10 +2502,16 @@ export class GraphWebview extends WebviewBase { } switch (refType) { + case 'branch': + return isGraphItemRefContext(item, 'branch') || isGraphItemTypedContext(item, 'upstreamStatus') + ? item.webviewItemValue.ref + : undefined; case 'revision': return isGraphItemRefContext(item, 'revision') ? item.webviewItemValue.ref : undefined; case 'stash': return isGraphItemRefContext(item, 'stash') ? item.webviewItemValue.ref : undefined; + case 'tag': + return isGraphItemRefContext(item, 'tag') ? item.webviewItemValue.ref : undefined; default: return isGraphItemRefContext(item) ? item.webviewItemValue.ref : undefined; } @@ -2504,7 +2550,10 @@ export interface GraphItemRefGroupContextValue { } export type GraphItemTypedContext = WebviewItemContext; -export type GraphItemTypedContextValue = GraphContributorContextValue | GraphPullRequestContextValue; +export type GraphItemTypedContextValue = + | GraphContributorContextValue + | GraphPullRequestContextValue + | GraphUpstreamStatusContextValue; export type GraphColumnsContextValue = string; @@ -2542,6 +2591,13 @@ export interface GraphTagContextValue { ref: GitTagReference; } +export interface GraphUpstreamStatusContextValue { + type: 'upstreamStatus'; + ref: GitBranchReference; + ahead: number; + behind: number; +} + function isGraphItemContext(item: unknown): item is GraphItemContext { if (item == null) return false; @@ -2564,6 +2620,10 @@ function isGraphItemTypedContext( ): item is GraphItemTypedContext; function isGraphItemTypedContext( item: unknown, + type: 'upstreamStatus', +): item is GraphItemTypedContext; +function isGraphItemTypedContext( + item: unknown, type: GraphItemTypedContextValue['type'], ): item is GraphItemTypedContext { if (item == null) return false; diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts index 23dc72b..aa21810 100644 --- a/src/plus/webviews/graph/protocol.ts +++ b/src/plus/webviews/graph/protocol.ts @@ -14,6 +14,7 @@ import type { IncludeOnlyRefsById, PullRequestMetadata, RefMetadata, + RefMetadataItem, RefMetadataType, Remote, Tag, @@ -38,6 +39,7 @@ export type GraphRefMetadata = RefMetadata | null; export type GraphUpstreamMetadata = UpstreamMetadata | null; export type GraphRefsMetadata = Record; export type GraphHostingServiceType = HostingServiceType; +export type GraphRefMetadataItem = RefMetadataItem; export type GraphRefMetadataType = RefMetadataType; export type GraphMissingRefsMetadataType = RefMetadataType; export type GraphMissingRefsMetadata = Record; @@ -45,7 +47,7 @@ export type GraphPullRequestMetadata = PullRequestMetadata; export enum GraphRefMetadataTypes { Upstream = 'upstream', - PullRequest = 'pullRequests', + PullRequest = 'pullRequest', } export const supportedRefMetadataTypes: GraphRefMetadataType[] = Object.values(GraphRefMetadataTypes); @@ -182,6 +184,7 @@ export type DoubleClickedParams = | { type: 'ref'; ref: GraphRef; + metadata?: GraphRefMetadataItem; } | { type: 'row'; diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx index e61e457..c56a32a 100644 --- a/src/webviews/apps/plus/graph/GraphWrapper.tsx +++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx @@ -31,6 +31,7 @@ import type { GraphExcludedRef, GraphExcludeTypes, GraphMissingRefsMetadata, + GraphRefMetadataItem, GraphRepository, GraphSearchResults, GraphSearchResultsError, @@ -81,7 +82,7 @@ export interface GraphWrapperProps { onChooseRepository?: () => void; onColumnsChange?: (colsSettings: GraphColumnsConfig) => void; onDimMergeCommits?: (dim: boolean) => void; - onDoubleClickRef?: (ref: GraphRef) => void; + onDoubleClickRef?: (ref: GraphRef, metadata?: GraphRefMetadataItem) => void; onDoubleClickRow?: (row: GraphRow, preserveFocus?: boolean) => void; onMissingAvatars?: (emails: { [email: string]: string }) => void; onMissingRefsMetadata?: (metadata: GraphMissingRefsMetadata) => void; @@ -835,9 +836,10 @@ export function GraphWrapper({ _event: React.MouseEvent, refGroup: GraphRefGroup, _row: GraphRow, + metadata?: GraphRefMetadataItem, ) => { if (refGroup.length > 0) { - onDoubleClickRef?.(refGroup[0]); + onDoubleClickRef?.(refGroup[0], metadata); } }; diff --git a/src/webviews/apps/plus/graph/graph.tsx b/src/webviews/apps/plus/graph/graph.tsx index dad999b..a3c8605 100644 --- a/src/webviews/apps/plus/graph/graph.tsx +++ b/src/webviews/apps/plus/graph/graph.tsx @@ -11,6 +11,7 @@ import type { GraphExcludedRef, GraphExcludeTypes, GraphMissingRefsMetadata, + GraphRefMetadataItem, InternalNotificationType, State, UpdateGraphConfigurationParams, @@ -101,7 +102,7 @@ export class GraphApp extends App { this.onRefsVisibilityChanged(refs, visible) } onChooseRepository={debounce(() => this.onChooseRepository(), 250)} - onDoubleClickRef={ref => this.onDoubleClickRef(ref)} + onDoubleClickRef={(ref, metadata) => this.onDoubleClickRef(ref, metadata)} onDoubleClickRow={(row, preserveFocus) => this.onDoubleClickRow(row, preserveFocus)} onMissingAvatars={(...params) => this.onGetMissingAvatars(...params)} onMissingRefsMetadata={(...params) => this.onGetMissingRefsMetadata(...params)} @@ -583,10 +584,11 @@ export class GraphApp extends App { }); } - private onDoubleClickRef(ref: GraphRef) { + private onDoubleClickRef(ref: GraphRef, metadata?: GraphRefMetadataItem) { this.sendCommand(DoubleClickedCommandType, { type: 'ref', ref: ref, + metadata: metadata, }); }