mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
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:
parent
43b0558cc1
commit
e0b70e58b3
1
extensions/git/src/api/git.d.ts
vendored
1
extensions/git/src/api/git.d.ts
vendored
|
@ -36,6 +36,7 @@ export interface Ref {
|
|||
export interface UpstreamRef {
|
||||
readonly remote: string;
|
||||
readonly name: string;
|
||||
readonly commit?: string;
|
||||
}
|
||||
|
||||
export interface Branch extends Ref {
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}));
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
Loading…
Reference in a new issue