From 9ed02ced9256e9ff0571a479e82c8fa00ed805a2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:39:40 +0100 Subject: [PATCH] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20polish=20incoming/outg?= =?UTF-8?q?oing=20proposed=20API=20(#202766)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/historyProvider.ts | 112 +++++++++--------- src/vs/workbench/api/browser/mainThreadSCM.ts | 45 +------ .../workbench/api/common/extHost.protocol.ts | 8 +- src/vs/workbench/api/common/extHostSCM.ts | 7 +- .../contrib/scm/browser/scmViewPane.ts | 67 +++++++---- .../workbench/contrib/scm/common/history.ts | 26 +--- .../vscode.proposed.scmHistoryProvider.d.ts | 5 +- 7 files changed, 112 insertions(+), 158 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index b5c735586ccec..9560aef5e8ea2 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -8,10 +8,18 @@ import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider import { Repository, Resource } from './repository'; import { IDisposable, filterEvent } from './util'; import { toGitUri } from './uri'; -import { Branch, RefType, Status } from './api/git'; +import { Branch, RefType, UpstreamRef } from './api/git'; import { emojify, ensureEmojis } from './emoji'; import { Operation } from './operation'; +function isBranchRefEqual(brach1: Branch | undefined, branch2: Branch | undefined): boolean { + return brach1?.name === branch2?.name && brach1?.commit === branch2?.commit; +} + +function isUpstreamRefEqual(upstream1: UpstreamRef | undefined, upstream2: UpstreamRef | undefined): boolean { + return upstream1?.name === upstream2?.name && upstream1?.remote === upstream2?.remote && upstream1?.commit === upstream2?.commit; +} + export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable { private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); @@ -21,10 +29,15 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; private _HEAD: Branch | undefined; + private _HEADBase: UpstreamRef | undefined; private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined; get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { + if (this._currentHistoryItemGroup === undefined && value === undefined) { + return; + } + this._currentHistoryItemGroup = value; this._onDidChangeCurrentHistoryItemGroup.fire(); } @@ -41,30 +54,31 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } private async onDidRunGitStatus(): Promise { - // Check if HEAD has changed - if (this._HEAD?.name === this.repository.HEAD?.name && - this._HEAD?.commit === this.repository.HEAD?.commit && - this._HEAD?.upstream?.name === this.repository.HEAD?.upstream?.name && - this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote && - this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { + // Check if HEAD does not support incoming/outgoing (detached commit, tag) + if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) { + this._HEAD = this._HEADBase = undefined; + this.currentHistoryItemGroup = undefined; return; } - this._HEAD = this.repository.HEAD; + // Resolve HEAD base + const HEADBase = await this.resolveHEADBase(this.repository.HEAD); - // Check if HEAD supports incoming/outgoing (not a tag, not detached) - if (!this._HEAD?.name || !this._HEAD?.commit || this._HEAD.type === RefType.Tag) { - this.currentHistoryItemGroup = undefined; + // Check if HEAD or HEADBase has changed + if (isBranchRefEqual(this._HEAD, this.repository.HEAD) && isUpstreamRefEqual(this._HEADBase, HEADBase)) { return; } + this._HEAD = this.repository.HEAD; + this._HEADBase = HEADBase; + this.currentHistoryItemGroup = { - id: `refs/heads/${this._HEAD.name}`, - label: this._HEAD.name, - upstream: this._HEAD.upstream ? + id: `refs/heads/${this._HEAD.name ?? ''}`, + label: this._HEAD.name ?? '', + base: this._HEADBase ? { - id: `refs/remotes/${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, - label: `${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, + id: `refs/remotes/${this._HEADBase.remote}/${this._HEADBase.name}`, + label: `${this._HEADBase.remote}/${this._HEADBase.name}`, } : undefined }; } @@ -138,7 +152,10 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec }); // History item change decoration - const fileDecoration = this.getHistoryItemChangeFileDecoration(change.status); + const letter = Resource.getStatusLetter(change.status); + const tooltip = Resource.getStatusText(change.status); + const color = Resource.getStatusColor(change.status); + const fileDecoration = new FileDecoration(letter, tooltip, color); this.historyItemDecorations.set(historyItemUri.toString(), fileDecoration); historyItemChangesUri.push(historyItemUri); @@ -148,40 +165,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItemChanges; } - async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { - // TODO - support for all history item groups - if (historyItemGroupId !== this.currentHistoryItemGroup?.id) { - return undefined; - } - - if (this.currentHistoryItemGroup?.upstream) { - return this.currentHistoryItemGroup.upstream; - } - - // Branch base - try { - const branchBase = await this.repository.getBranchBase(historyItemGroupId); - - if (branchBase?.name && branchBase?.type === RefType.Head) { - return { - id: `refs/heads/${branchBase.name}`, - label: branchBase.name - }; - } - if (branchBase?.name && branchBase.remote && branchBase?.type === RefType.RemoteHead) { - return { - id: `refs/remotes/${branchBase.remote}/${branchBase.name}`, - label: `${branchBase.remote}/${branchBase.name}` - }; - } - } - catch (err) { - this.logger.error(`Failed to get branch base for '${historyItemGroupId}': ${err.message}`); - } - - return undefined; - } - async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { const ancestor = await this.repository.getMergeBase(refId1, refId2); if (!ancestor) { @@ -202,12 +185,29 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return this.historyItemDecorations.get(uri.toString()); } - private getHistoryItemChangeFileDecoration(status: Status): FileDecoration { - const letter = Resource.getStatusLetter(status); - const tooltip = Resource.getStatusText(status); - const color = Resource.getStatusColor(status); + private async resolveHEADBase(HEAD: Branch): Promise { + // Upstream + if (HEAD.upstream) { + return HEAD.upstream; + } - return new FileDecoration(letter, tooltip, color); + try { + const remoteBranch = await this.repository.getBranchBase(HEAD.name ?? ''); + if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { + return undefined; + } + + return { + name: remoteBranch.name, + remote: remoteBranch.remote, + commit: remoteBranch.commit + }; + } + catch (err) { + this.logger.error(`Failed to get branch base for '${HEAD.name}': ${err.message}`); + } + + return undefined; } dispose(): void { diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 81a43a35387e3..2c82aa6e2ada1 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -15,10 +15,9 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ThemeIcon } from 'vs/base/common/themables'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; -import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryItemGroupDetails, ISCMHistoryItemGroupEntry, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; import { ResourceTree } from 'vs/base/common/resourceTree'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { Codicon } from 'vs/base/common/codicons'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { basename } from 'vs/base/common/resources'; @@ -141,48 +140,6 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } - async resolveHistoryItemGroupDetails(historyItemGroup: ISCMHistoryItemGroup): Promise { - // History item group base - const historyItemGroupBase = await this.resolveHistoryItemGroupBase(historyItemGroup.id); - - if (!historyItemGroupBase) { - return undefined; - } - - // Common ancestor, ahead, behind - const ancestor = await this.resolveHistoryItemGroupCommonAncestor(historyItemGroup.id, historyItemGroupBase.id); - - if (!ancestor) { - return undefined; - } - - // Incoming - const incoming: ISCMHistoryItemGroupEntry = { - id: historyItemGroupBase.id, - label: historyItemGroupBase.label, - icon: Codicon.arrowCircleDown, - direction: 'incoming', - ancestor: ancestor.id, - count: ancestor.behind, - }; - - // Outgoing - const outgoing: ISCMHistoryItemGroupEntry = { - id: historyItemGroup.id, - label: historyItemGroup.label, - icon: Codicon.arrowCircleUp, - direction: 'outgoing', - ancestor: ancestor.id, - count: ancestor.ahead, - }; - - return { incoming, outgoing }; - } - - async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { - return this.proxy.$resolveHistoryItemGroupBase(this.handle, historyItemGroupId, CancellationToken.None); - } - async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 978e0179e80e1..f34388f48758d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1477,12 +1477,7 @@ export type SCMRawResourceSplices = [ export interface SCMHistoryItemGroupDto { readonly id: string; readonly label: string; - readonly upstream?: SCMRemoteHistoryItemGroupDto; -} - -export interface SCMRemoteHistoryItemGroupDto { - readonly id: string; - readonly label: string; + readonly base?: Omit; } export interface SCMHistoryItemDto { @@ -2241,7 +2236,6 @@ export interface ExtHostSCMShape { $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; - $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise; $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 3e40a0917aed5..da1e67ab60440 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -11,7 +11,7 @@ import { debounce } from 'vs/base/common/decorators'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemGroupDto } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto } from './extHost.protocol'; import { sortedDiff, equals } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import type * as vscode from 'vscode'; @@ -960,11 +960,6 @@ export class ExtHostSCM implements ExtHostSCMShape { return Promise.resolve(undefined); } - async $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise { - const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; - return await historyProvider?.resolveHistoryItemGroupBase(historyItemGroupId, token) ?? undefined; - } - async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2, token) ?? undefined; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 1f4a37bcc0702..5f53e86c9f40c 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3329,44 +3329,66 @@ class SCMTreeDataSource implements IAsyncDataSource 0))) { - children.push({ - ...historyItemGroupDetails.incoming, - ariaLabel: localize('incomingChangesAriaLabel', "Incoming changes from {0}", historyItemGroupDetails.incoming.label), - repository: element, - type: 'historyItemGroup' - }); + (showIncomingChanges === 'auto' && (incomingHistoryItemGroup.count ?? 0) > 0))) { + children.push(incomingHistoryItemGroup); } // Outgoing - if (historyItemGroupDetails?.outgoing && + if (outgoingHistoryItemGroup && (showOutgoingChanges === 'always' || - (showOutgoingChanges === 'auto' && (historyItemGroupDetails.outgoing.count ?? 0) > 0))) { - children.push({ - ...historyItemGroupDetails.outgoing, - ariaLabel: localize('outgoingChangesAriaLabel', "Outgoing changes from {0}", historyItemGroupDetails.outgoing.label), - repository: element, - type: 'historyItemGroup' - }); + (showOutgoingChanges === 'auto' && (outgoingHistoryItemGroup.count ?? 0) > 0))) { + children.push(outgoingHistoryItemGroup); } return children; @@ -3540,7 +3562,8 @@ class SCMTreeDataSource implements IAsyncDataSource(), historyItemChanges: new Map() }; diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index 385019d9ede77..b9ac2eed9944b 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -23,13 +23,12 @@ export interface ISCMHistoryProvider { provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise; - resolveHistoryItemGroupBase(historyItemGroupId: string): Promise; resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined>; - resolveHistoryItemGroupDetails(historyItemGroup: ISCMHistoryItemGroup): Promise; } export interface ISCMHistoryProviderCacheEntry { - readonly historyItemGroupDetails?: ISCMHistoryItemGroupDetails; + readonly incomingHistoryItemGroup: SCMHistoryItemGroupTreeElement | undefined; + readonly outgoingHistoryItemGroup: SCMHistoryItemGroupTreeElement | undefined; readonly historyItems: Map; readonly historyItemChanges: Map; } @@ -39,34 +38,21 @@ export interface ISCMHistoryOptions { readonly limit?: number | { id?: string }; } -export interface ISCMRemoteHistoryItemGroup { - readonly id: string; - readonly label: string; -} - export interface ISCMHistoryItemGroup { readonly id: string; readonly label: string; - readonly upstream?: ISCMRemoteHistoryItemGroup; -} - -export interface ISCMHistoryItemGroupDetails { - readonly incoming?: ISCMHistoryItemGroupEntry; - readonly outgoing: ISCMHistoryItemGroupEntry; + readonly base?: Omit; } -export interface ISCMHistoryItemGroupEntry { +export interface SCMHistoryItemGroupTreeElement { readonly id: string; readonly label: string; - readonly direction: 'incoming' | 'outgoing'; + readonly ariaLabel?: string; readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; readonly description?: string; + readonly direction: 'incoming' | 'outgoing'; readonly ancestor?: string; readonly count?: number; -} - -export interface SCMHistoryItemGroupTreeElement extends ISCMHistoryItemGroupEntry { - readonly ariaLabel?: string; readonly repository: ISCMRepository; readonly type: 'historyItemGroup'; } diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index b030e44ee98da..24f4dcd8453a6 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -28,8 +28,7 @@ declare module 'vscode' { provideHistoryItemSummary?(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; - resolveHistoryItemGroupBase(historyItemGroupId: string, token: CancellationToken): ProviderResult; - resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId: string, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; + resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; } export interface SourceControlHistoryOptions { @@ -40,7 +39,7 @@ declare module 'vscode' { export interface SourceControlHistoryItemGroup { readonly id: string; readonly label: string; - readonly upstream?: SourceControlRemoteHistoryItemGroup; + readonly base?: Omit; } export interface SourceControlRemoteHistoryItemGroup {