Skip to content

Commit 9ed02ce

Browse files
authored
SCM - 💄 polish incoming/outgoing proposed API (microsoft#202766)
1 parent 216a8e6 commit 9ed02ce

File tree

7 files changed

+112
-158
lines changed

7 files changed

+112
-158
lines changed

extensions/git/src/historyProvider.ts

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,18 @@ import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider
88
import { Repository, Resource } from './repository';
99
import { IDisposable, filterEvent } from './util';
1010
import { toGitUri } from './uri';
11-
import { Branch, RefType, Status } from './api/git';
11+
import { Branch, RefType, UpstreamRef } from './api/git';
1212
import { emojify, ensureEmojis } from './emoji';
1313
import { Operation } from './operation';
1414

15+
function isBranchRefEqual(brach1: Branch | undefined, branch2: Branch | undefined): boolean {
16+
return brach1?.name === branch2?.name && brach1?.commit === branch2?.commit;
17+
}
18+
19+
function isUpstreamRefEqual(upstream1: UpstreamRef | undefined, upstream2: UpstreamRef | undefined): boolean {
20+
return upstream1?.name === upstream2?.name && upstream1?.remote === upstream2?.remote && upstream1?.commit === upstream2?.commit;
21+
}
22+
1523
export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable {
1624

1725
private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter<void>();
@@ -21,10 +29,15 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
2129
readonly onDidChangeFileDecorations: Event<Uri[]> = this._onDidChangeDecorations.event;
2230

2331
private _HEAD: Branch | undefined;
32+
private _HEADBase: UpstreamRef | undefined;
2433
private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined;
2534

2635
get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; }
2736
set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) {
37+
if (this._currentHistoryItemGroup === undefined && value === undefined) {
38+
return;
39+
}
40+
2841
this._currentHistoryItemGroup = value;
2942
this._onDidChangeCurrentHistoryItemGroup.fire();
3043
}
@@ -41,30 +54,31 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
4154
}
4255

4356
private async onDidRunGitStatus(): Promise<void> {
44-
// Check if HEAD has changed
45-
if (this._HEAD?.name === this.repository.HEAD?.name &&
46-
this._HEAD?.commit === this.repository.HEAD?.commit &&
47-
this._HEAD?.upstream?.name === this.repository.HEAD?.upstream?.name &&
48-
this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote &&
49-
this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) {
57+
// Check if HEAD does not support incoming/outgoing (detached commit, tag)
58+
if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) {
59+
this._HEAD = this._HEADBase = undefined;
60+
this.currentHistoryItemGroup = undefined;
5061
return;
5162
}
5263

53-
this._HEAD = this.repository.HEAD;
64+
// Resolve HEAD base
65+
const HEADBase = await this.resolveHEADBase(this.repository.HEAD);
5466

55-
// Check if HEAD supports incoming/outgoing (not a tag, not detached)
56-
if (!this._HEAD?.name || !this._HEAD?.commit || this._HEAD.type === RefType.Tag) {
57-
this.currentHistoryItemGroup = undefined;
67+
// Check if HEAD or HEADBase has changed
68+
if (isBranchRefEqual(this._HEAD, this.repository.HEAD) && isUpstreamRefEqual(this._HEADBase, HEADBase)) {
5869
return;
5970
}
6071

72+
this._HEAD = this.repository.HEAD;
73+
this._HEADBase = HEADBase;
74+
6175
this.currentHistoryItemGroup = {
62-
id: `refs/heads/${this._HEAD.name}`,
63-
label: this._HEAD.name,
64-
upstream: this._HEAD.upstream ?
76+
id: `refs/heads/${this._HEAD.name ?? ''}`,
77+
label: this._HEAD.name ?? '',
78+
base: this._HEADBase ?
6579
{
66-
id: `refs/remotes/${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`,
67-
label: `${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`,
80+
id: `refs/remotes/${this._HEADBase.remote}/${this._HEADBase.name}`,
81+
label: `${this._HEADBase.remote}/${this._HEADBase.name}`,
6882
} : undefined
6983
};
7084
}
@@ -138,7 +152,10 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
138152
});
139153

140154
// History item change decoration
141-
const fileDecoration = this.getHistoryItemChangeFileDecoration(change.status);
155+
const letter = Resource.getStatusLetter(change.status);
156+
const tooltip = Resource.getStatusText(change.status);
157+
const color = Resource.getStatusColor(change.status);
158+
const fileDecoration = new FileDecoration(letter, tooltip, color);
142159
this.historyItemDecorations.set(historyItemUri.toString(), fileDecoration);
143160

144161
historyItemChangesUri.push(historyItemUri);
@@ -148,40 +165,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
148165
return historyItemChanges;
149166
}
150167

151-
async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<SourceControlHistoryItemGroup | undefined> {
152-
// TODO - support for all history item groups
153-
if (historyItemGroupId !== this.currentHistoryItemGroup?.id) {
154-
return undefined;
155-
}
156-
157-
if (this.currentHistoryItemGroup?.upstream) {
158-
return this.currentHistoryItemGroup.upstream;
159-
}
160-
161-
// Branch base
162-
try {
163-
const branchBase = await this.repository.getBranchBase(historyItemGroupId);
164-
165-
if (branchBase?.name && branchBase?.type === RefType.Head) {
166-
return {
167-
id: `refs/heads/${branchBase.name}`,
168-
label: branchBase.name
169-
};
170-
}
171-
if (branchBase?.name && branchBase.remote && branchBase?.type === RefType.RemoteHead) {
172-
return {
173-
id: `refs/remotes/${branchBase.remote}/${branchBase.name}`,
174-
label: `${branchBase.remote}/${branchBase.name}`
175-
};
176-
}
177-
}
178-
catch (err) {
179-
this.logger.error(`Failed to get branch base for '${historyItemGroupId}': ${err.message}`);
180-
}
181-
182-
return undefined;
183-
}
184-
185168
async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> {
186169
const ancestor = await this.repository.getMergeBase(refId1, refId2);
187170
if (!ancestor) {
@@ -202,12 +185,29 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
202185
return this.historyItemDecorations.get(uri.toString());
203186
}
204187

205-
private getHistoryItemChangeFileDecoration(status: Status): FileDecoration {
206-
const letter = Resource.getStatusLetter(status);
207-
const tooltip = Resource.getStatusText(status);
208-
const color = Resource.getStatusColor(status);
188+
private async resolveHEADBase(HEAD: Branch): Promise<UpstreamRef | undefined> {
189+
// Upstream
190+
if (HEAD.upstream) {
191+
return HEAD.upstream;
192+
}
209193

210-
return new FileDecoration(letter, tooltip, color);
194+
try {
195+
const remoteBranch = await this.repository.getBranchBase(HEAD.name ?? '');
196+
if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) {
197+
return undefined;
198+
}
199+
200+
return {
201+
name: remoteBranch.name,
202+
remote: remoteBranch.remote,
203+
commit: remoteBranch.commit
204+
};
205+
}
206+
catch (err) {
207+
this.logger.error(`Failed to get branch base for '${HEAD.name}': ${err.message}`);
208+
}
209+
210+
return undefined;
211211
}
212212

213213
dispose(): void {

src/vs/workbench/api/browser/mainThreadSCM.ts

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ import { MarshalledId } from 'vs/base/common/marshallingIds';
1515
import { ThemeIcon } from 'vs/base/common/themables';
1616
import { IMarkdownString } from 'vs/base/common/htmlContent';
1717
import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff';
18-
import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryItemGroupDetails, ISCMHistoryItemGroupEntry, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history';
18+
import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history';
1919
import { ResourceTree } from 'vs/base/common/resourceTree';
2020
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
21-
import { Codicon } from 'vs/base/common/codicons';
2221
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
2322
import { basename } from 'vs/base/common/resources';
2423

@@ -141,48 +140,6 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
141140

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

144-
async resolveHistoryItemGroupDetails(historyItemGroup: ISCMHistoryItemGroup): Promise<ISCMHistoryItemGroupDetails | undefined> {
145-
// History item group base
146-
const historyItemGroupBase = await this.resolveHistoryItemGroupBase(historyItemGroup.id);
147-
148-
if (!historyItemGroupBase) {
149-
return undefined;
150-
}
151-
152-
// Common ancestor, ahead, behind
153-
const ancestor = await this.resolveHistoryItemGroupCommonAncestor(historyItemGroup.id, historyItemGroupBase.id);
154-
155-
if (!ancestor) {
156-
return undefined;
157-
}
158-
159-
// Incoming
160-
const incoming: ISCMHistoryItemGroupEntry = {
161-
id: historyItemGroupBase.id,
162-
label: historyItemGroupBase.label,
163-
icon: Codicon.arrowCircleDown,
164-
direction: 'incoming',
165-
ancestor: ancestor.id,
166-
count: ancestor.behind,
167-
};
168-
169-
// Outgoing
170-
const outgoing: ISCMHistoryItemGroupEntry = {
171-
id: historyItemGroup.id,
172-
label: historyItemGroup.label,
173-
icon: Codicon.arrowCircleUp,
174-
direction: 'outgoing',
175-
ancestor: ancestor.id,
176-
count: ancestor.ahead,
177-
};
178-
179-
return { incoming, outgoing };
180-
}
181-
182-
async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<ISCMHistoryItemGroup | undefined> {
183-
return this.proxy.$resolveHistoryItemGroupBase(this.handle, historyItemGroupId, CancellationToken.None);
184-
}
185-
186143
async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> {
187144
return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None);
188145
}

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,12 +1477,7 @@ export type SCMRawResourceSplices = [
14771477
export interface SCMHistoryItemGroupDto {
14781478
readonly id: string;
14791479
readonly label: string;
1480-
readonly upstream?: SCMRemoteHistoryItemGroupDto;
1481-
}
1482-
1483-
export interface SCMRemoteHistoryItemGroupDto {
1484-
readonly id: string;
1485-
readonly label: string;
1480+
readonly base?: Omit<SCMHistoryItemGroupDto, 'base'>;
14861481
}
14871482

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

src/vs/workbench/api/common/extHostSCM.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { debounce } from 'vs/base/common/decorators';
1111
import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
1212
import { asPromise } from 'vs/base/common/async';
1313
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
14-
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemGroupDto } from './extHost.protocol';
14+
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto } from './extHost.protocol';
1515
import { sortedDiff, equals } from 'vs/base/common/arrays';
1616
import { comparePaths } from 'vs/base/common/comparers';
1717
import type * as vscode from 'vscode';
@@ -960,11 +960,6 @@ export class ExtHostSCM implements ExtHostSCMShape {
960960
return Promise.resolve(undefined);
961961
}
962962

963-
async $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise<SCMHistoryItemGroupDto | undefined> {
964-
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
965-
return await historyProvider?.resolveHistoryItemGroupBase(historyItemGroupId, token) ?? undefined;
966-
}
967-
968963
async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> {
969964
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
970965
return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2, token) ?? undefined;

src/vs/workbench/contrib/scm/browser/scmViewPane.ts

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3329,44 +3329,66 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
33293329
const historyProvider = scmProvider.historyProvider;
33303330
const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup;
33313331

3332-
if (!historyProvider || !currentHistoryItemGroup || (showIncomingChanges === 'never' && showOutgoingChanges === 'never')) {
3332+
if (!historyProvider || !currentHistoryItemGroup || !currentHistoryItemGroup.base || (showIncomingChanges === 'never' && showOutgoingChanges === 'never')) {
33333333
return [];
33343334
}
33353335

33363336
const children: SCMHistoryItemGroupTreeElement[] = [];
33373337
const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(element);
3338-
let historyItemGroupDetails = historyProviderCacheEntry?.historyItemGroupDetails;
33393338

3340-
if (!historyItemGroupDetails) {
3341-
historyItemGroupDetails = await historyProvider.resolveHistoryItemGroupDetails(currentHistoryItemGroup);
3339+
let incomingHistoryItemGroup = historyProviderCacheEntry?.incomingHistoryItemGroup;
3340+
let outgoingHistoryItemGroup = historyProviderCacheEntry?.outgoingHistoryItemGroup;
3341+
3342+
if (!incomingHistoryItemGroup || !outgoingHistoryItemGroup) {
3343+
// Common ancestor, ahead, behind
3344+
const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id);
3345+
if (!ancestor) {
3346+
return [];
3347+
}
3348+
3349+
incomingHistoryItemGroup = {
3350+
id: currentHistoryItemGroup.base.id,
3351+
label: currentHistoryItemGroup.base.label,
3352+
ariaLabel: localize('incomingChangesAriaLabel', "Incoming changes from {0}", currentHistoryItemGroup.base.label),
3353+
icon: Codicon.arrowCircleDown,
3354+
direction: 'incoming',
3355+
ancestor: ancestor.id,
3356+
count: ancestor.behind,
3357+
repository: element,
3358+
type: 'historyItemGroup'
3359+
};
3360+
3361+
outgoingHistoryItemGroup = {
3362+
id: currentHistoryItemGroup.id,
3363+
label: currentHistoryItemGroup.label,
3364+
ariaLabel: localize('outgoingChangesAriaLabel', "Outgoing changes to {0}", currentHistoryItemGroup.label),
3365+
icon: Codicon.arrowCircleUp,
3366+
direction: 'outgoing',
3367+
ancestor: ancestor.id,
3368+
count: ancestor.ahead,
3369+
repository: element,
3370+
type: 'historyItemGroup'
3371+
};
3372+
33423373
this.historyProviderCache.set(element, {
33433374
...historyProviderCacheEntry,
3344-
historyItemGroupDetails
3375+
incomingHistoryItemGroup,
3376+
outgoingHistoryItemGroup
33453377
});
33463378
}
33473379

33483380
// Incoming
3349-
if (historyItemGroupDetails?.incoming &&
3381+
if (incomingHistoryItemGroup &&
33503382
(showIncomingChanges === 'always' ||
3351-
(showIncomingChanges === 'auto' && (historyItemGroupDetails.incoming.count ?? 0) > 0))) {
3352-
children.push({
3353-
...historyItemGroupDetails.incoming,
3354-
ariaLabel: localize('incomingChangesAriaLabel', "Incoming changes from {0}", historyItemGroupDetails.incoming.label),
3355-
repository: element,
3356-
type: 'historyItemGroup'
3357-
});
3383+
(showIncomingChanges === 'auto' && (incomingHistoryItemGroup.count ?? 0) > 0))) {
3384+
children.push(incomingHistoryItemGroup);
33583385
}
33593386

33603387
// Outgoing
3361-
if (historyItemGroupDetails?.outgoing &&
3388+
if (outgoingHistoryItemGroup &&
33623389
(showOutgoingChanges === 'always' ||
3363-
(showOutgoingChanges === 'auto' && (historyItemGroupDetails.outgoing.count ?? 0) > 0))) {
3364-
children.push({
3365-
...historyItemGroupDetails.outgoing,
3366-
ariaLabel: localize('outgoingChangesAriaLabel', "Outgoing changes from {0}", historyItemGroupDetails.outgoing.label),
3367-
repository: element,
3368-
type: 'historyItemGroup'
3369-
});
3390+
(showOutgoingChanges === 'auto' && (outgoingHistoryItemGroup.count ?? 0) > 0))) {
3391+
children.push(outgoingHistoryItemGroup);
33703392
}
33713393

33723394
return children;
@@ -3540,7 +3562,8 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
35403562

35413563
private getHistoryProviderCacheEntry(repository: ISCMRepository): ISCMHistoryProviderCacheEntry {
35423564
return this.historyProviderCache.get(repository) ?? {
3543-
historyItemGroupDetails: undefined,
3565+
incomingHistoryItemGroup: undefined,
3566+
outgoingHistoryItemGroup: undefined,
35443567
historyItems: new Map<string, [ISCMHistoryItem | undefined, ISCMHistoryItem[]]>(),
35453568
historyItemChanges: new Map<string, ISCMHistoryItemChange[]>()
35463569
};

0 commit comments

Comments
 (0)