mirror of
https://github.com/Microsoft/vscode
synced 2024-09-29 07:51:27 +00:00
SCM Graph - better handling of graph refresh (#228329)
* Initial implementation * Removed refs should be also removed from the filter
This commit is contained in:
parent
edbf964e42
commit
1d3895d045
|
@ -6,11 +6,12 @@
|
|||
|
||||
import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent } from 'vscode';
|
||||
import { Repository, Resource } from './repository';
|
||||
import { IDisposable, deltaHistoryItemRefs, dispose } from './util';
|
||||
import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent } from './util';
|
||||
import { toGitUri } from './uri';
|
||||
import { Branch, LogOptions, Ref, RefType } from './api/git';
|
||||
import { emojify, ensureEmojis } from './emoji';
|
||||
import { Commit } from './git';
|
||||
import { OperationKind, OperationResult } from './operation';
|
||||
|
||||
function toSourceControlHistoryItemRef(ref: Ref): SourceControlHistoryItemRef {
|
||||
switch (ref.type) {
|
||||
|
@ -71,13 +72,15 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
|||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(protected readonly repository: Repository, private readonly logger: LogOutputChannel) {
|
||||
this.disposables.push(repository.onDidRunGitStatus(() => this.onDidRunGitStatus(), this));
|
||||
const onDidRunWriteOperation = filterEvent(repository.onDidRunOperation, e => !e.operation.readOnly);
|
||||
this.disposables.push(onDidRunWriteOperation(this.onDidRunWriteOperation, this));
|
||||
|
||||
this.disposables.push(window.registerFileDecorationProvider(this));
|
||||
}
|
||||
|
||||
private async onDidRunGitStatus(): Promise<void> {
|
||||
private async onDidRunWriteOperation(result: OperationResult): Promise<void> {
|
||||
if (!this.repository.HEAD) {
|
||||
this.logger.trace('[GitHistoryProvider][onDidRunGitStatus] repository.HEAD is undefined');
|
||||
this.logger.trace('[GitHistoryProvider][onDidRunWriteOperation] repository.HEAD is undefined');
|
||||
this._currentHistoryItemRef = this._currentHistoryItemRemoteRef = this._currentHistoryItemBaseRef = undefined;
|
||||
this._onDidChangeCurrentHistoryItemRefs.fire();
|
||||
|
||||
|
@ -111,7 +114,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
|||
mergeBase.name !== this.repository.HEAD.upstream?.name) ? {
|
||||
id: `refs/remotes/${mergeBase.remote}/${mergeBase.name}`,
|
||||
name: `${mergeBase.remote}/${mergeBase.name}`,
|
||||
revision: mergeBase.commit
|
||||
revision: mergeBase.commit,
|
||||
icon: new ThemeIcon('cloud')
|
||||
} : undefined;
|
||||
}
|
||||
} else {
|
||||
|
@ -145,24 +149,28 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
|||
};
|
||||
|
||||
this._onDidChangeCurrentHistoryItemRefs.fire();
|
||||
this.logger.trace(`[GitHistoryProvider][onDidRunGitStatus] currentHistoryItemRef: ${JSON.stringify(this._currentHistoryItemRef)}`);
|
||||
this.logger.trace(`[GitHistoryProvider][onDidRunGitStatus] currentHistoryItemRemoteRef: ${JSON.stringify(this._currentHistoryItemRemoteRef)}`);
|
||||
this.logger.trace(`[GitHistoryProvider][onDidRunGitStatus] currentHistoryItemBaseRef: ${JSON.stringify(this._currentHistoryItemBaseRef)}`);
|
||||
this.logger.trace(`[GitHistoryProvider][onDidRunWriteOperation] currentHistoryItemRef: ${JSON.stringify(this._currentHistoryItemRef)}`);
|
||||
this.logger.trace(`[GitHistoryProvider][onDidRunWriteOperation] currentHistoryItemRemoteRef: ${JSON.stringify(this._currentHistoryItemRemoteRef)}`);
|
||||
this.logger.trace(`[GitHistoryProvider][onDidRunWriteOperation] currentHistoryItemBaseRef: ${JSON.stringify(this._currentHistoryItemBaseRef)}`);
|
||||
|
||||
// Refs (alphabetically)
|
||||
const refs = await this.repository.getRefs({ sort: 'alphabetically' });
|
||||
const historyItemRefs = refs.map(ref => toSourceControlHistoryItemRef(ref));
|
||||
|
||||
// Auto-fetch
|
||||
const silent = result.operation.kind === OperationKind.Fetch && result.operation.showProgress === false;
|
||||
const delta = deltaHistoryItemRefs(this.historyItemRefs, historyItemRefs);
|
||||
this._onDidChangeHistoryItemRefs.fire(delta);
|
||||
this._onDidChangeHistoryItemRefs.fire({ ...delta, silent });
|
||||
|
||||
this.historyItemRefs = historyItemRefs;
|
||||
|
||||
const deltaLog = {
|
||||
added: delta.added.map(ref => ref.id),
|
||||
modified: delta.modified.map(ref => ref.id),
|
||||
removed: delta.removed.map(ref => ref.id)
|
||||
removed: delta.removed.map(ref => ref.id),
|
||||
silent
|
||||
};
|
||||
this.logger.trace(`[GitHistoryProvider][onDidRunGitStatus] historyItemRefs: ${JSON.stringify(deltaLog)}`);
|
||||
this.logger.trace(`[GitHistoryProvider][onDidRunWriteOperation] historyItemRefs: ${JSON.stringify(deltaLog)}`);
|
||||
}
|
||||
|
||||
async provideHistoryItemRefs(): Promise<SourceControlHistoryItemRef[]> {
|
||||
|
|
|
@ -190,7 +190,7 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
|
|||
}, undefined);
|
||||
get historyItemBaseRef(): IObservable<ISCMHistoryItemRef | undefined> { return this._historyItemBaseRef; }
|
||||
|
||||
private readonly _historyItemRefChanges = observableValue<ISCMHistoryItemRefsChangeEvent>(this, { added: [], modified: [], removed: [] });
|
||||
private readonly _historyItemRefChanges = observableValue<ISCMHistoryItemRefsChangeEvent>(this, { added: [], modified: [], removed: [], silent: false });
|
||||
get historyItemRefChanges(): IObservable<ISCMHistoryItemRefsChangeEvent> { return this._historyItemRefChanges; }
|
||||
|
||||
constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { }
|
||||
|
@ -232,7 +232,7 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
|
|||
const modified = historyItemRefs.modified.map(ref => toISCMHistoryItemRef(ref)!);
|
||||
const removed = historyItemRefs.removed.map(ref => toISCMHistoryItemRef(ref)!);
|
||||
|
||||
this._historyItemRefChanges.set({ added, modified, removed }, undefined);
|
||||
this._historyItemRefChanges.set({ added, modified, removed, silent: historyItemRefs.silent }, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1555,6 +1555,7 @@ export interface SCMHistoryItemRefsChangeEventDto {
|
|||
readonly added: readonly SCMHistoryItemRefDto[];
|
||||
readonly modified: readonly SCMHistoryItemRefDto[];
|
||||
readonly removed: readonly SCMHistoryItemRefDto[];
|
||||
readonly silent: boolean;
|
||||
}
|
||||
|
||||
export interface SCMHistoryItemDto {
|
||||
|
|
|
@ -612,7 +612,7 @@ class ExtHostSourceControl implements vscode.SourceControl {
|
|||
const modified = e.modified.map(ref => ({ ...ref, icon: getHistoryItemIconDto(ref.icon) }));
|
||||
const removed = e.removed.map(ref => ({ ...ref, icon: getHistoryItemIconDto(ref.icon) }));
|
||||
|
||||
this.#proxy.$onDidChangeHistoryProviderHistoryItemRefs(this.handle, { added, modified, removed });
|
||||
this.#proxy.$onDidChangeHistoryProviderHistoryItemRefs(this.handle, { added, modified, removed, silent: e.silent });
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -527,7 +527,7 @@
|
|||
background-color: var(--vscode-badge-background);
|
||||
color: var(--vscode-badge-foreground);
|
||||
border: 1px solid var(--vscode-contrastBorder);
|
||||
margin-left: 6px;
|
||||
margin: 0 6px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { fromNow } from '../../../../base/common/date.js';
|
|||
import { createMatches, FuzzyScore, IMatch } from '../../../../base/common/filters.js';
|
||||
import { MarkdownString } from '../../../../base/common/htmlContent.js';
|
||||
import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { autorun, autorunWithStore, autorunWithStoreHandleChanges, derived, IObservable, observableValue, waitForState, constObservable, latestChangedValue, observableFromEvent, runOnChange, signalFromObservable } from '../../../../base/common/observable.js';
|
||||
import { autorun, autorunWithStore, derived, IObservable, observableValue, waitForState, constObservable, latestChangedValue, observableFromEvent, runOnChange } from '../../../../base/common/observable.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
|
@ -1132,49 +1132,20 @@ export class SCMHistoryViewPane extends ViewPane {
|
|||
// Update context
|
||||
this._scmProviderCtx.set(repository.provider.contextValue);
|
||||
|
||||
// Publish
|
||||
const historyItemRemoteRefIdSignal = signalFromObservable(this, derived(reader => {
|
||||
return historyProvider.historyItemRemoteRef.read(reader)?.id;
|
||||
// HistoryItemId changed (checkout)
|
||||
const historyItemRefId = derived(reader => {
|
||||
return historyProvider.historyItemRef.read(reader)?.id;
|
||||
});
|
||||
store.add(runOnChange(historyItemRefId, () => {
|
||||
this.refresh();
|
||||
}));
|
||||
|
||||
// Fetch, Push
|
||||
const historyItemRemoteRefRevision = derived(reader => {
|
||||
return historyProvider.historyItemRemoteRef.read(reader)?.revision;
|
||||
});
|
||||
|
||||
// HistoryItemRefs changed
|
||||
store.add(
|
||||
autorunWithStoreHandleChanges<{ refresh: boolean | 'ifScrollTop' }>({
|
||||
owner: this,
|
||||
createEmptyChangeSummary: () => ({ refresh: false }),
|
||||
handleChange(context, changeSummary) {
|
||||
changeSummary.refresh = context.didChange(historyItemRemoteRefRevision) ? 'ifScrollTop' : true;
|
||||
return true;
|
||||
},
|
||||
}, (reader, changeSummary) => {
|
||||
historyItemRemoteRefIdSignal.read(reader);
|
||||
const historyItemRefValue = historyProvider.historyItemRef.read(reader);
|
||||
const historyItemRemoteRefRevisionValue = historyItemRemoteRefRevision.read(reader);
|
||||
|
||||
// Commit, Checkout, Publish, Pull
|
||||
if (changeSummary.refresh === true) {
|
||||
this.refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
if (changeSummary.refresh === 'ifScrollTop') {
|
||||
// If the history item remote revision has changed, but it matches the history
|
||||
// item revision, then it means that a Push operation was performed and it is
|
||||
// safe to refresh the graph.
|
||||
if (historyItemRefValue?.revision === historyItemRemoteRefRevisionValue) {
|
||||
this.refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the history item remote revision has changed, but it does not matches the
|
||||
// history item revision, then a Fetch operation was performed. This can be the
|
||||
// result of a user action (Fetch) or a background action (Auto Fetch). If the
|
||||
// tree is scrolled to the top, we can safely refresh the tree.
|
||||
store.add(runOnChange(historyProvider.historyItemRefChanges, changes => {
|
||||
if (changes.silent) {
|
||||
// The history item reference changes occurred in the background (ex: Auto Fetch)
|
||||
// If tree is scrolled to the top, we can safely refresh the tree, otherwise we
|
||||
// will show a visual cue that the view is outdated.
|
||||
if (this._tree.scrollTop === 0) {
|
||||
this.refresh();
|
||||
return;
|
||||
|
@ -1182,7 +1153,37 @@ export class SCMHistoryViewPane extends ViewPane {
|
|||
|
||||
// Show the "Outdated" badge on the view
|
||||
this._repositoryOutdated.set(true, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are any removed history item references we need to check whether they are
|
||||
// currently being used in the filter. If they are, we need to update the filter which
|
||||
// will result in the graph being refreshed.
|
||||
if (changes.removed.length !== 0) {
|
||||
const historyItemsFilter = this._treeViewModel.historyItemsFilter.get();
|
||||
|
||||
if (historyItemsFilter !== 'all' && historyItemsFilter !== 'auto') {
|
||||
let updateFilter = false;
|
||||
const historyItemRefs = [...historyItemsFilter];
|
||||
|
||||
for (const ref of changes.removed) {
|
||||
const index = historyItemRefs
|
||||
.findIndex(item => item.id === ref.id);
|
||||
|
||||
if (index !== -1) {
|
||||
historyItemRefs.splice(index, 1);
|
||||
updateFilter = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updateFilter) {
|
||||
this._treeViewModel.setHistoryItemsFilter(historyItemRefs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}));
|
||||
|
||||
// HistoryItemRefs filter changed
|
||||
|
|
|
@ -53,6 +53,7 @@ export interface ISCMHistoryItemRefsChangeEvent {
|
|||
readonly added: readonly ISCMHistoryItemRef[];
|
||||
readonly removed: readonly ISCMHistoryItemRef[];
|
||||
readonly modified: readonly ISCMHistoryItemRef[];
|
||||
readonly silent: boolean;
|
||||
}
|
||||
|
||||
export interface ISCMHistoryItem {
|
||||
|
|
|
@ -76,5 +76,13 @@ declare module 'vscode' {
|
|||
readonly added: readonly SourceControlHistoryItemRef[];
|
||||
readonly removed: readonly SourceControlHistoryItemRef[];
|
||||
readonly modified: readonly SourceControlHistoryItemRef[];
|
||||
|
||||
/**
|
||||
* Flag to indicate if the operation that caused the event to trigger was due
|
||||
* to a user action or a background operation (ex: Auto Fetch). The flag is used
|
||||
* to determine whether to automatically refresh the user interface or present
|
||||
* the user with a visual cue that the user interface is outdated.
|
||||
*/
|
||||
readonly silent: boolean;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue