Skip to content

Commit

Permalink
SCM - 💄 polish incoming/outgoing proposed API (#202766)
Browse files Browse the repository at this point in the history
  • Loading branch information
lszomoru committed Jan 18, 2024
1 parent 216a8e6 commit 9ed02ce
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 158 deletions.
112 changes: 56 additions & 56 deletions extensions/git/src/historyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>();
Expand All @@ -21,10 +29,15 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
readonly onDidChangeFileDecorations: Event<Uri[]> = 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();
}
Expand All @@ -41,30 +54,31 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
}

private async onDidRunGitStatus(): Promise<void> {
// 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
};
}
Expand Down Expand Up @@ -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);
Expand All @@ -148,40 +165,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
return historyItemChanges;
}

async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<SourceControlHistoryItemGroup | undefined> {
// 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) {
Expand All @@ -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<UpstreamRef | undefined> {
// 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 {
Expand Down
45 changes: 1 addition & 44 deletions src/vs/workbench/api/browser/mainThreadSCM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -141,48 +140,6 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {

constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { }

async resolveHistoryItemGroupDetails(historyItemGroup: ISCMHistoryItemGroup): Promise<ISCMHistoryItemGroupDetails | undefined> {
// 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<ISCMHistoryItemGroup | undefined> {
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);
}
Expand Down
8 changes: 1 addition & 7 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SCMHistoryItemGroupDto, 'base'>;
}

export interface SCMHistoryItemDto {
Expand Down Expand Up @@ -2241,7 +2236,6 @@ export interface ExtHostSCMShape {
$provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise<SCMHistoryItemDto[] | undefined>;
$provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemDto | undefined>;
$provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemChangeDto[] | undefined>;
$resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise<SCMHistoryItemGroupDto | undefined>;
$resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>;
}

Expand Down
7 changes: 1 addition & 6 deletions src/vs/workbench/api/common/extHostSCM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -960,11 +960,6 @@ export class ExtHostSCM implements ExtHostSCMShape {
return Promise.resolve(undefined);
}

async $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise<SCMHistoryItemGroupDto | undefined> {
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;
Expand Down
67 changes: 45 additions & 22 deletions src/vs/workbench/contrib/scm/browser/scmViewPane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3329,44 +3329,66 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
const historyProvider = scmProvider.historyProvider;
const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup;

if (!historyProvider || !currentHistoryItemGroup || (showIncomingChanges === 'never' && showOutgoingChanges === 'never')) {
if (!historyProvider || !currentHistoryItemGroup || !currentHistoryItemGroup.base || (showIncomingChanges === 'never' && showOutgoingChanges === 'never')) {
return [];
}

const children: SCMHistoryItemGroupTreeElement[] = [];
const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(element);
let historyItemGroupDetails = historyProviderCacheEntry?.historyItemGroupDetails;

if (!historyItemGroupDetails) {
historyItemGroupDetails = await historyProvider.resolveHistoryItemGroupDetails(currentHistoryItemGroup);
let incomingHistoryItemGroup = historyProviderCacheEntry?.incomingHistoryItemGroup;
let outgoingHistoryItemGroup = historyProviderCacheEntry?.outgoingHistoryItemGroup;

if (!incomingHistoryItemGroup || !outgoingHistoryItemGroup) {
// Common ancestor, ahead, behind
const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id);
if (!ancestor) {
return [];
}

incomingHistoryItemGroup = {
id: currentHistoryItemGroup.base.id,
label: currentHistoryItemGroup.base.label,
ariaLabel: localize('incomingChangesAriaLabel', "Incoming changes from {0}", currentHistoryItemGroup.base.label),
icon: Codicon.arrowCircleDown,
direction: 'incoming',
ancestor: ancestor.id,
count: ancestor.behind,
repository: element,
type: 'historyItemGroup'
};

outgoingHistoryItemGroup = {
id: currentHistoryItemGroup.id,
label: currentHistoryItemGroup.label,
ariaLabel: localize('outgoingChangesAriaLabel', "Outgoing changes to {0}", currentHistoryItemGroup.label),
icon: Codicon.arrowCircleUp,
direction: 'outgoing',
ancestor: ancestor.id,
count: ancestor.ahead,
repository: element,
type: 'historyItemGroup'
};

this.historyProviderCache.set(element, {
...historyProviderCacheEntry,
historyItemGroupDetails
incomingHistoryItemGroup,
outgoingHistoryItemGroup
});
}

// Incoming
if (historyItemGroupDetails?.incoming &&
if (incomingHistoryItemGroup &&
(showIncomingChanges === 'always' ||
(showIncomingChanges === 'auto' && (historyItemGroupDetails.incoming.count ?? 0) > 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;
Expand Down Expand Up @@ -3540,7 +3562,8 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement

private getHistoryProviderCacheEntry(repository: ISCMRepository): ISCMHistoryProviderCacheEntry {
return this.historyProviderCache.get(repository) ?? {
historyItemGroupDetails: undefined,
incomingHistoryItemGroup: undefined,
outgoingHistoryItemGroup: undefined,
historyItems: new Map<string, [ISCMHistoryItem | undefined, ISCMHistoryItem[]]>(),
historyItemChanges: new Map<string, ISCMHistoryItemChange[]>()
};
Expand Down

0 comments on commit 9ed02ce

Please sign in to comment.