SCM - add caching layer to incoming/outgoing tree nodes (#198306)

* Upstream commit + improve onDidChangeCurrentHistoryItemGroup

* Refactor expanding a history item group

* Wire up caching

* Invoking the git.refresh command invalidates the cache

* Clean up cache data structure
This commit is contained in:
Ladislau Szomoru 2023-11-15 15:09:18 +01:00 committed by GitHub
parent 43b0558cc1
commit e0b70e58b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 181 additions and 72 deletions

View file

@ -36,6 +36,7 @@ export interface Ref {
export interface UpstreamRef {
readonly remote: string;
readonly name: string;
readonly commit?: string;
}
export interface Branch extends Ref {

View file

@ -454,7 +454,7 @@ export class CommandCenter {
@command('git.refresh', { repository: true })
async refresh(repository: Repository): Promise<void> {
await repository.status();
await repository.refresh();
}
@command('git.openResource')

View file

@ -2193,6 +2193,13 @@ export class Repository {
if (HEAD.name) {
// Branch
HEAD = await this.getBranch(HEAD.name);
// Upstream commit
if (HEAD && HEAD.upstream) {
const ref = `refs/remotes/${HEAD.upstream.remote}/${HEAD.upstream.name}`;
const commit = await this.revParse(ref);
HEAD = { ...HEAD, upstream: { ...HEAD.upstream, commit } };
}
} else if (HEAD.commit) {
// Tag || Commit
const tags = await this.getRefs({ pattern: 'refs/tags' });
@ -2596,6 +2603,13 @@ export class Repository {
}
async revParse(ref: string): Promise<string | undefined> {
try {
const result = await fs.readFile(path.join(this.dotGit.path, ref), 'utf8');
return result.trim();
} catch (err) {
this.logger.warn(err.message);
}
try {
const result = await this.exec(['rev-parse', ref]);
if (result.stderr) {

View file

@ -6,11 +6,12 @@
import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlActionButton, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, l10n } from 'vscode';
import { Repository, Resource } from './repository';
import { IDisposable } from './util';
import { IDisposable, filterEvent } from './util';
import { toGitUri } from './uri';
import { SyncActionButton } from './actionButton';
import { RefType, Status } from './api/git';
import { Branch, RefType, Status } from './api/git';
import { emojify, ensureEmojis } from './emoji';
import { Operation } from './operation';
export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable {
@ -30,6 +31,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
this._onDidChangeActionButton.fire();
}
private _HEAD: Branch | undefined;
private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined;
get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; }
@ -47,15 +49,30 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
this.actionButton = actionButton.button;
this.disposables.push(actionButton);
this.disposables.push(repository.onDidRunGitStatus(this.onDidRunGitStatus, this));
this.disposables.push(actionButton.onDidChange(() => this.actionButton = actionButton.button));
this.disposables.push(repository.onDidRunGitStatus(this.onDidRunGitStatus, this));
this.disposables.push(filterEvent(repository.onDidRunOperation, e => e.operation === Operation.Refresh)(() => this._onDidChangeCurrentHistoryItemGroup.fire()));
this.disposables.push(window.registerFileDecorationProvider(this));
}
private async onDidRunGitStatus(): Promise<void> {
if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit) { return; }
// Check if HEAD has changed
const HEAD = this.repository.HEAD;
if (this._HEAD?.name === HEAD.name &&
this._HEAD?.commit === HEAD.commit &&
this._HEAD?.upstream?.name === HEAD.upstream?.name &&
this._HEAD?.upstream?.remote === HEAD.upstream?.remote &&
this._HEAD?.commit === HEAD.commit) {
return;
}
this._HEAD = this.repository.HEAD;
this.currentHistoryItemGroup = {
id: `refs/heads/${this.repository.HEAD.name}`,
label: this.repository.HEAD.name,

View file

@ -48,6 +48,7 @@ export const enum OperationKind {
Rebase = 'Rebase',
RebaseAbort = 'RebaseAbort',
RebaseContinue = 'RebaseContinue',
Refresh = 'Refresh',
RevertFiles = 'RevertFiles',
RevList = 'RevList',
RevParse = 'RevParse',
@ -67,9 +68,9 @@ export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchO
GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation |
HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation |
MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation |
ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | RevListOperation | RevParseOperation |
SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation |
TagOperation;
ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RefreshOperation | RevertFilesOperation | RevListOperation |
RevParseOperation | SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation |
SyncOperation | TagOperation;
type BaseOperation = { kind: OperationKind; blocking: boolean; readOnly: boolean; remote: boolean; retry: boolean; showProgress: boolean };
export type AddOperation = BaseOperation & { kind: OperationKind.Add };
@ -114,6 +115,7 @@ export type ResetOperation = BaseOperation & { kind: OperationKind.Reset };
export type RebaseOperation = BaseOperation & { kind: OperationKind.Rebase };
export type RebaseAbortOperation = BaseOperation & { kind: OperationKind.RebaseAbort };
export type RebaseContinueOperation = BaseOperation & { kind: OperationKind.RebaseContinue };
export type RefreshOperation = BaseOperation & { kind: OperationKind.Refresh };
export type RevertFilesOperation = BaseOperation & { kind: OperationKind.RevertFiles };
export type RevListOperation = BaseOperation & { kind: OperationKind.RevList };
export type RevParseOperation = BaseOperation & { kind: OperationKind.RevParse };
@ -169,6 +171,7 @@ export const Operation = {
Rebase: { kind: OperationKind.Rebase, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseOperation,
RebaseAbort: { kind: OperationKind.RebaseAbort, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseAbortOperation,
RebaseContinue: { kind: OperationKind.RebaseContinue, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseContinueOperation,
Refresh: { kind: OperationKind.Refresh, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RefreshOperation,
RevertFiles: (showProgress: boolean) => ({ kind: OperationKind.RevertFiles, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as RevertFilesOperation),
RevList: { kind: OperationKind.RevList, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevListOperation,
RevParse: { kind: OperationKind.RevParse, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevParseOperation,

View file

@ -1142,6 +1142,11 @@ export class Repository implements Disposable {
await this.run(Operation.Status);
}
@throttle
async refresh(): Promise<void> {
await this.run(Operation.Refresh);
}
diff(cached?: boolean): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diff(cached));
}

View file

@ -15,7 +15,7 @@ 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, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history';
import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryItemGroupDetails, ISCMHistoryItemGroupEntry, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history';
import { ResourceTree } from 'vs/base/common/resourceTree';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
@ -171,6 +171,38 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { }
async resolveHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup): Promise<ISCMHistoryItemGroupDetails | undefined> {
// History item group base
const historyItemGroupBase = await this.resolveHistoryItemGroupBase(historyItemGroup.id);
// Common ancestor, ahead, behind
const ancestor = historyItemGroupBase ?
await this.resolveHistoryItemGroupCommonAncestor(historyItemGroup.id, historyItemGroupBase.id) : undefined;
// Incoming
let incoming: ISCMHistoryItemGroupEntry | undefined;
if (historyItemGroupBase) {
incoming = {
id: historyItemGroupBase.id,
label: `$(cloud-download) ${historyItemGroupBase.label}`,
// description: localize('incoming', "Incoming Changes"),
ancestor: ancestor?.id,
count: ancestor?.behind ?? 0,
};
}
// Outgoing
const outgoing: ISCMHistoryItemGroupEntry = {
id: historyItemGroup.id,
label: `$(cloud-upload) ${historyItemGroup.label}`,
// description: localize('outgoing', "Outgoing Changes"),
ancestor: ancestor?.id,
count: ancestor?.ahead ?? 0,
};
return { incoming, outgoing };
}
async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<ISCMHistoryItemGroup | undefined> {
return this.proxy.$resolveHistoryItemGroupBase(this.handle, historyItemGroupId, CancellationToken.None);
}

View file

@ -253,18 +253,6 @@ function commandListEquals(a: readonly vscode.Command[], b: readonly vscode.Comm
return equals(a, b, commandEquals);
}
function historyItemGroupEquals(a: vscode.SourceControlHistoryItemGroup | undefined, b: vscode.SourceControlHistoryItemGroup | undefined): boolean {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
return a.id === b.id && a.label === b.label && a.upstream?.id === b.upstream?.id && a.upstream?.label === b.upstream?.label;
}
export interface IValidateInput {
(value: string, cursorPosition: number): vscode.ProviderResult<vscode.SourceControlInputBoxValidation | undefined | null>;
}
@ -618,10 +606,6 @@ class ExtHostSourceControl implements vscode.SourceControl {
if (historyProvider) {
this._historyProviderDisposable.value.add(historyProvider.onDidChangeCurrentHistoryItemGroup(() => {
if (historyItemGroupEquals(this._historyProviderCurrentHistoryItemGroup, historyProvider?.currentHistoryItemGroup)) {
return;
}
this._historyProviderCurrentHistoryItemGroup = historyProvider?.currentHistoryItemGroup;
this.#proxy.$onDidChangeHistoryProviderCurrentHistoryItemGroup(this.handle, this._historyProviderCurrentHistoryItemGroup);
}));

View file

@ -10,7 +10,7 @@ import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose,
import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
import { append, $, Dimension, asCSSUrl, trackFocus, clearNode, prepend } from 'vs/base/browser/dom';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history';
import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryProviderCacheEntry, SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history';
import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
import { ResourceLabels, IResourceLabel, IFileLabelOptions } from 'vs/workbench/browser/labels';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
@ -2258,6 +2258,7 @@ export class SCMViewPane extends ViewPane {
private treeScrollTop: number | undefined;
private treeContainer!: HTMLElement;
private tree!: WorkbenchCompressibleAsyncDataTree<ISCMViewService, TreeElement, FuzzyScore>;
private treeDataSource!: SCMTreeDataSource;
private listLabels!: ResourceLabels;
private inputRenderer!: InputRenderer;
@ -2436,10 +2437,11 @@ export class SCMViewPane extends ViewPane {
this._alwaysShowRepositories = this.configurationService.getValue<boolean>('scm.alwaysShowRepositories');
this._showSyncInformation = this.configurationService.getValue<{ incoming: boolean; outgoing: boolean }>('scm.experimental.showSyncInformation');
this.updateChildren();
if (e?.affectsConfiguration('scm.alwaysShowRepositories')) {
this.updateActions();
}
this.updateChildren();
}
};
this.configurationService.onDidChangeConfiguration(onDidChangeConfiguration, this, this.visibilityDisposables);
@ -2483,6 +2485,8 @@ export class SCMViewPane extends ViewPane {
actionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables);
this.disposables.add(actionRunner);
this.treeDataSource = this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode, () => this.alwaysShowRepositories, () => this.showActionButton, () => this.showSyncInformation);
this.tree = this.instantiationService.createInstance(
WorkbenchCompressibleAsyncDataTree,
'SCM Tree Repo',
@ -2500,7 +2504,7 @@ export class SCMViewPane extends ViewPane {
this.instantiationService.createInstance(HistoryItemChangeRenderer, () => this.viewMode, this.listLabels),
this.instantiationService.createInstance(SeparatorRenderer)
],
this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode, () => this.alwaysShowRepositories, () => this.showActionButton, () => this.showSyncInformation),
this.treeDataSource,
{
horizontalScrolling: false,
setRowLineHeight: false,
@ -2668,6 +2672,13 @@ export class SCMViewPane extends ViewPane {
this.onDidActiveEditorChange();
}));
if (repository.provider.historyProvider) {
repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => {
this.treeDataSource.deleteCacheEntry(repository);
this.updateChildren(repository);
}));
}
const resourceGroupDisposables = repositoryDisposables.add(new DisposableMap<ISCMResourceGroup, IDisposable>());
const onDidChangeResourceGroups = () => {
@ -2699,6 +2710,7 @@ export class SCMViewPane extends ViewPane {
// Removed repositories
for (const repository of removed) {
this.treeDataSource.deleteCacheEntry(repository);
this.items.deleteAndDispose(repository);
}
@ -2916,6 +2928,8 @@ export class SCMViewPane extends ViewPane {
class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement> {
private readonly historyProviderCache = new Map<ISCMRepository, ISCMHistoryProviderCacheEntry>();
constructor(
private readonly viewMode: () => ViewMode,
private readonly alwaysShowRepositories: () => boolean,
@ -2988,25 +3002,18 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
}
// History item groups
if (this.showSyncInformation().incoming || this.showSyncInformation().outgoing) {
const historyProvider = inputOrElement.provider.historyProvider;
const historyItemGroup = historyProvider?.currentHistoryItemGroup;
if (historyProvider && historyItemGroup) {
const historyItemGroups = await this.getHistoryItemGroups(inputOrElement);
if (historyItemGroups.some(h => h.count ?? 0 > 0)) {
// Separator
children.push({
label: localize('syncSeparatorHeader', "Incoming/Outgoing"),
repository: inputOrElement,
type: 'separator'
} as SCMViewSeparatorElement);
}
children.push(...historyItemGroups);
}
const historyItemGroups = await this.getHistoryItemGroups(inputOrElement);
if (historyItemGroups.some(h => h.count ?? 0 > 0)) {
// Separator
children.push({
label: localize('syncSeparatorHeader', "Incoming/Outgoing"),
repository: inputOrElement,
type: 'separator'
} as SCMViewSeparatorElement);
}
children.push(...historyItemGroups);
return children;
} else if (isSCMResourceGroup(inputOrElement)) {
if (this.viewMode() === ViewMode.List) {
@ -3049,37 +3056,31 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
return [];
}
// History item group base
const historyItemGroupBase = await historyProvider.resolveHistoryItemGroupBase(currentHistoryItemGroup.id);
if (!historyItemGroupBase) {
return [];
const children: SCMHistoryItemGroupTreeElement[] = [];
const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(element);
let historyItemGroupDetails = historyProviderCacheEntry?.historyItemGroupDetails;
if (!historyItemGroupDetails) {
historyItemGroupDetails = await historyProvider.resolveHistoryItemGroup(currentHistoryItemGroup);
this.historyProviderCache.set(element, {
...historyProviderCacheEntry,
historyItemGroupDetails
});
}
// Common ancestor, ahead, behind
const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, historyItemGroupBase.id);
const children: SCMHistoryItemGroupTreeElement[] = [];
// Incoming
if (historyItemGroupBase && this.showSyncInformation().incoming) {
if (this.showSyncInformation().incoming && historyItemGroupDetails?.incoming) {
children.push({
id: historyItemGroupBase.id,
label: `$(cloud-download) ${historyItemGroupBase.label}`,
// description: localize('incoming', "Incoming Changes"),
ancestor: ancestor?.id,
count: ancestor?.behind ?? 0,
...historyItemGroupDetails.incoming,
repository: element,
type: 'historyItemGroup'
});
}
// Outgoing
if (this.showSyncInformation().outgoing) {
if (this.showSyncInformation().outgoing && historyItemGroupDetails?.outgoing) {
children.push({
id: currentHistoryItemGroup.id,
label: `$(cloud-upload) ${currentHistoryItemGroup.label}`,
// description: localize('outgoing', "Outgoing Changes"),
ancestor: ancestor?.id,
count: ancestor?.ahead ?? 0,
...historyItemGroupDetails.outgoing,
repository: element,
type: 'historyItemGroup'
});
@ -3089,14 +3090,25 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
}
private async getHistoryItems(element: SCMHistoryItemGroupTreeElement): Promise<SCMHistoryItemTreeElement[]> {
const scmProvider = element.repository.provider;
const historyProvider = scmProvider.historyProvider;
const repository = element.repository;
const historyProvider = repository.provider.historyProvider;
if (!historyProvider) {
return [];
}
const historyItems = await historyProvider.provideHistoryItems(element.id, { limit: { id: element.ancestor } }) ?? [];
const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(repository);
const historyItemsMap = historyProviderCacheEntry.historyItems;
let historyItems = historyProviderCacheEntry.historyItems.get(element.id);
if (!historyItems) {
historyItems = await historyProvider.provideHistoryItems(element.id, { limit: { id: element.ancestor } }) ?? [];
this.historyProviderCache.set(repository, {
...historyProviderCacheEntry,
historyItems: historyItemsMap.set(element.id, historyItems)
});
}
return historyItems.map(historyItem => ({
...historyItem,
historyItemGroup: element,
@ -3112,12 +3124,21 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
return [];
}
// History Item Changes
const changes = await historyProvider.provideHistoryItemChanges(element.id) ?? [];
const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(repository);
const historyItemChangesMap = historyProviderCacheEntry.historyItemChanges;
let historyItemChanges = historyItemChangesMap.get(element.id);
if (!historyItemChanges) {
historyItemChanges = await historyProvider.provideHistoryItemChanges(element.id) ?? [];
this.historyProviderCache.set(repository, {
...historyProviderCacheEntry,
historyItemChanges: historyItemChangesMap.set(element.id, historyItemChanges)
});
}
if (this.viewMode() === ViewMode.List) {
// List
return changes.map(change => ({
return historyItemChanges.map(change => ({
...change,
historyItem: element,
type: 'historyItemChange'
@ -3126,7 +3147,7 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
// Tree
const tree = new ResourceTree<SCMHistoryItemChangeTreeElement, SCMHistoryItemTreeElement>(element, repository.provider.rootUri ?? URI.file('/'), this.uriIdentityService.extUri);
for (const change of changes) {
for (const change of historyItemChanges) {
tree.add(change.uri, {
...change,
historyItem: element,
@ -3142,6 +3163,13 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
return children;
}
private getHistoryProviderCacheEntry(repository: ISCMRepository): ISCMHistoryProviderCacheEntry {
return this.historyProviderCache.get(repository) ?? {
historyItems: new Map<string, ISCMHistoryItem[]>(),
historyItemChanges: new Map<string, ISCMHistoryItemChange[]>()
};
}
getParent(element: TreeElement): ISCMViewService | TreeElement {
if (isSCMResourceNode(element)) {
if (element === element.context.resourceTree.root) {
@ -3170,6 +3198,10 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
throw new Error('Unexpected call to getParent');
}
}
deleteCacheEntry(repository: ISCMRepository): void {
this.historyProviderCache.delete(repository);
}
}
export class SCMActionButton implements IDisposable {

View file

@ -26,10 +26,17 @@ export interface ISCMHistoryProvider {
provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined>;
provideHistoryItemChanges(historyItemId: string): Promise<ISCMHistoryItemChange[] | undefined>;
resolveHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup): Promise<ISCMHistoryItemGroupDetails | undefined>;
resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<ISCMHistoryItemGroup | undefined>;
resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined>;
}
export interface ISCMHistoryProviderCacheEntry {
readonly historyItemGroupDetails?: ISCMHistoryItemGroupDetails;
readonly historyItems: Map<string, ISCMHistoryItem[]>;
readonly historyItemChanges: Map<string, ISCMHistoryItemChange[]>;
}
export interface ISCMHistoryOptions {
readonly cursor?: string;
readonly limit?: number | { id?: string };
@ -46,6 +53,19 @@ export interface ISCMHistoryItemGroup {
readonly upstream?: ISCMRemoteHistoryItemGroup;
}
export interface ISCMHistoryItemGroupDetails {
readonly incoming?: ISCMHistoryItemGroupEntry;
readonly outgoing: ISCMHistoryItemGroupEntry;
}
export interface ISCMHistoryItemGroupEntry {
readonly id: string;
readonly label: string;
readonly description?: string;
readonly ancestor?: string;
readonly count?: number;
}
export interface SCMHistoryItemGroupTreeElement extends ISCMHistoryItemGroup {
readonly description?: string;
readonly ancestor?: string;

View file

@ -20,7 +20,8 @@ declare module 'vscode' {
onDidChangeActionButton: Event<void>;
/**
* Fires when the current history item group changes (ex: checkout)
* Fires when the current history item group changes after
* a user action (ex: commit, checkout, fetch, pull, push)
*/
onDidChangeCurrentHistoryItemGroup: Event<void>;