Browse Source

Improves performance of reveal commit in view

Also adds cancellation and max timeout
main
Eric Amodio 5 years ago
parent
commit
baa42c42be
5 changed files with 194 additions and 25 deletions
  1. +4
    -3
      src/git/git.ts
  2. +11
    -0
      src/git/gitService.ts
  3. +103
    -1
      src/system/function.ts
  4. +49
    -10
      src/views/repositoriesView.ts
  5. +27
    -11
      src/views/viewBase.ts

+ 4
- 3
src/git/git.ts View File

@ -403,11 +403,12 @@ export namespace Git {
); );
} }
export function branch__contains(repoPath: string, ref: string, options: { remote: boolean } = { remote: false }) {
const params = ['branch', '--contains'];
if (options.remote) {
export function branch__contains(repoPath: string, ref: string, { remotes = false }: { remotes?: boolean } = {}) {
const params = ['branch'];
if (remotes) {
params.push('-r'); params.push('-r');
} }
params.push('--contains');
return git<string>({ cwd: repoPath, configs: ['-c', 'color.branch=false'] }, ...params, ref); return git<string>({ cwd: repoPath, configs: ['-c', 'color.branch=false'] }, ...params, ref);
} }

+ 11
- 0
src/git/gitService.ts View File

@ -1209,6 +1209,17 @@ export class GitService implements Disposable {
} }
@log() @log()
async getCommitBranches(repoPath: string, ref: string, options?: { remotes?: boolean }): Promise<string[]> {
const data = await Git.branch__contains(repoPath, ref, options);
if (!data) return [];
return data
.split('\n')
.map(b => b.substr(2).trim())
.filter(Boolean);
}
@log()
getCommitCount(repoPath: string, refs: string[]) { getCommitCount(repoPath: string, refs: string[]) {
return Git.rev_list(repoPath, refs, { count: true }); return Git.rev_list(repoPath, refs, { count: true });
} }

+ 103
- 1
src/system/function.ts View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
import { debounce as _debounce, once as _once } from 'lodash-es'; import { debounce as _debounce, once as _once } from 'lodash-es';
import { Disposable } from 'vscode';
import { CancellationToken, Disposable } from 'vscode';
export interface Deferrable { export interface Deferrable {
cancel(): void; cancel(): void;
@ -27,6 +27,59 @@ export namespace Functions {
}; };
} }
export function cancellable<T>(
promise: Thenable<T>,
timeoutOrToken: number | CancellationToken,
options: {
cancelMessage?: string;
onDidCancel?(
resolve: (value?: T | PromiseLike<T> | undefined) => void,
reject: (reason?: any) => void
): void;
} = {}
): Promise<T> {
return new Promise((resolve, reject) => {
let fulfilled = false;
let timer: NodeJS.Timer | undefined;
if (typeof timeoutOrToken === 'number') {
timer = setTimeout(() => {
if (typeof options.onDidCancel === 'function') {
options.onDidCancel(resolve, reject);
} else {
reject(new Error(options.cancelMessage || 'TIMED OUT'));
}
}, timeoutOrToken);
} else {
timeoutOrToken.onCancellationRequested(() => {
if (fulfilled) return;
if (typeof options.onDidCancel === 'function') {
options.onDidCancel(resolve, reject);
} else {
reject(new Error(options.cancelMessage || 'CANCELLED'));
}
});
}
promise.then(
() => {
fulfilled = true;
if (timer !== undefined) {
clearTimeout(timer);
}
resolve(promise);
},
ex => {
fulfilled = true;
if (timer !== undefined) {
clearTimeout(timer);
}
reject(ex);
}
);
});
}
export interface DebounceOptions { export interface DebounceOptions {
leading?: boolean; leading?: boolean;
maxWait?: number; maxWait?: number;
@ -76,6 +129,22 @@ export namespace Functions {
return tracked; return tracked;
} }
// export function debounceMemoized<T extends (...args: any[]) => any>(
// fn: T,
// wait?: number,
// options?: DebounceOptions & { resolver?(...args: any[]): any }
// ): T {
// const { resolver, ...opts } = options || ({} as DebounceOptions & { resolver?: T });
// const memo = _memoize(() => {
// return debounce(fn, wait, opts);
// }, resolver);
// return function(this: any, ...args: []) {
// return memo.apply(this, args).apply(this, args);
// } as T;
// }
const comma = ','; const comma = ',';
const emptyStr = ''; const emptyStr = '';
const equals = '='; const equals = '=';
@ -155,6 +224,39 @@ export namespace Functions {
return disposable; return disposable;
} }
export function progress<T>(promise: Promise<T>, intervalMs: number, onProgress: () => boolean): Promise<T> {
return new Promise((resolve, reject) => {
let timer: NodeJS.Timer | undefined;
timer = setInterval(() => {
if (onProgress()) {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
}
}, intervalMs);
promise.then(
() => {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
resolve(promise);
},
ex => {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
reject(ex);
}
);
});
}
export async function wait(ms: number) { export async function wait(ms: number) {
await new Promise(resolve => setTimeout(resolve, ms)); await new Promise(resolve => setTimeout(resolve, ms));
} }

+ 49
- 10
src/views/repositoriesView.ts View File

@ -24,6 +24,8 @@ import {
BranchNode, BranchNode,
BranchOrTagFolderNode, BranchOrTagFolderNode,
CompareBranchNode, CompareBranchNode,
RemoteNode,
RemotesNode,
RepositoriesNode, RepositoriesNode,
RepositoryNode, RepositoryNode,
StashesNode, StashesNode,
@ -147,9 +149,12 @@ export class RepositoriesView extends ViewBase {
return { ...Container.config.views, ...Container.config.views.repositories }; return { ...Container.config.views, ...Container.config.views.repositories };
} }
findCommitNode(commit: GitLogCommit | { repoPath: string; ref: string }, token?: CancellationToken) {
async findCommit(commit: GitLogCommit | { repoPath: string; ref: string }, token?: CancellationToken) {
const repoNodeId = RepositoryNode.getId(commit.repoPath); const repoNodeId = RepositoryNode.getId(commit.repoPath);
// Get all the branches the commit is on
let branches = await Container.git.getCommitBranches(commit.repoPath, commit.ref);
if (branches.length !== 0) {
return this.findNode((n: any) => n.commit !== undefined && n.commit.ref === commit.ref, { return this.findNode((n: any) => n.commit !== undefined && n.commit.ref === commit.ref, {
allowPaging: true, allowPaging: true,
maxDepth: 6, maxDepth: 6,
@ -157,11 +162,14 @@ export class RepositoriesView extends ViewBase {
// Only search for commit nodes in the same repo within BranchNodes // Only search for commit nodes in the same repo within BranchNodes
if (n instanceof RepositoriesNode) return true; if (n instanceof RepositoriesNode) return true;
if (n instanceof BranchNode) {
return n.id.startsWith(repoNodeId) && branches.includes(n.branch.name);
}
if ( if (
n instanceof RepositoryNode || n instanceof RepositoryNode ||
n instanceof BranchesNode || n instanceof BranchesNode ||
n instanceof BranchOrTagFolderNode ||
n instanceof BranchNode
n instanceof BranchOrTagFolderNode
) { ) {
return n.id.startsWith(repoNodeId); return n.id.startsWith(repoNodeId);
} }
@ -172,7 +180,38 @@ export class RepositoriesView extends ViewBase {
}); });
} }
findStashNode(stash: GitStashCommit | { repoPath: string; ref: string }, token?: CancellationToken) {
// If we didn't find the commit on any local branches, check remote branches
branches = await Container.git.getCommitBranches(commit.repoPath, commit.ref, { remotes: true });
if (branches.length === 0) return undefined;
const remotes = branches.map(b => b.split('/', 1)[0]);
return this.findNode((n: any) => n.commit !== undefined && n.commit.ref === commit.ref, {
allowPaging: true,
maxDepth: 8,
canTraverse: n => {
// Only search for commit nodes in the same repo within BranchNodes
if (n instanceof RepositoriesNode) return true;
if (n instanceof RemoteNode) {
return n.id.startsWith(repoNodeId) && remotes.includes(n.remote.name);
}
if (n instanceof BranchNode) {
return n.id.startsWith(repoNodeId) && branches.includes(n.branch.name);
}
if (n instanceof RepositoryNode || n instanceof RemotesNode || n instanceof BranchOrTagFolderNode) {
return n.id.startsWith(repoNodeId);
}
return false;
},
token: token
});
}
findStash(stash: GitStashCommit | { repoPath: string; ref: string }, token?: CancellationToken) {
const repoNodeId = RepositoryNode.getId(stash.repoPath); const repoNodeId = RepositoryNode.getId(stash.repoPath);
return this.findNode(StashNode.getId(stash.repoPath, stash.ref), { return this.findNode(StashNode.getId(stash.repoPath, stash.ref), {
@ -191,8 +230,8 @@ export class RepositoriesView extends ViewBase {
}); });
} }
@gate<RepositoriesView['revealCommit']>(() => '')
revealCommit(
@gate(() => '')
async revealCommit(
commit: GitLogCommit | { repoPath: string; ref: string }, commit: GitLogCommit | { repoPath: string; ref: string },
options?: { options?: {
select?: boolean; select?: boolean;
@ -207,7 +246,7 @@ export class RepositoriesView extends ViewBase {
cancellable: true cancellable: true
}, },
async (progress, token) => { async (progress, token) => {
const node = await this.findCommitNode(commit, token);
const node = await this.findCommit(commit, token);
if (node === undefined) return node; if (node === undefined) return node;
// Not sure why I need to reveal each parent, but without it the node won't be revealed // Not sure why I need to reveal each parent, but without it the node won't be revealed
@ -231,7 +270,7 @@ export class RepositoriesView extends ViewBase {
); );
} }
@gate<RepositoriesView['revealStash']>(() => '')
@gate(() => '')
async revealStash( async revealStash(
stash: GitStashCommit | { repoPath: string; ref: string; stashName: string }, stash: GitStashCommit | { repoPath: string; ref: string; stashName: string },
options?: { options?: {
@ -247,7 +286,7 @@ export class RepositoriesView extends ViewBase {
cancellable: true cancellable: true
}, },
async (progress, token) => { async (progress, token) => {
const node = await this.findStashNode(stash, token);
const node = await this.findStash(stash, token);
if (node !== undefined) { if (node !== undefined) {
await this.reveal(node, options); await this.reveal(node, options);
} }
@ -257,7 +296,7 @@ export class RepositoriesView extends ViewBase {
); );
} }
@gate<RepositoriesView['revealStashes']>(() => '')
@gate(() => '')
async revealStashes( async revealStashes(
repoPath: string, repoPath: string,
options?: { options?: {

+ 27
- 11
src/views/viewBase.ts View File

@ -208,6 +208,8 @@ export abstract class ViewBase> implements TreeData
token?: CancellationToken; token?: CancellationToken;
} = {} } = {}
): Promise<ViewNode | undefined> { ): Promise<ViewNode | undefined> {
const cc = Logger.getCorrelationContext();
// If we have no root (e.g. never been initialized) force it so the tree will load properly // If we have no root (e.g. never been initialized) force it so the tree will load properly
if (this._root === undefined) { if (this._root === undefined) {
await this.show(); await this.show();
@ -221,16 +223,21 @@ export abstract class ViewBase> implements TreeData
// maxDepth // maxDepth
// ); // );
const node = await this.findNodeCoreBFS(
typeof predicate === 'string' ? n => n.id === predicate : predicate,
this.ensureRoot(),
allowPaging,
canTraverse,
maxDepth,
token
);
return node;
try {
const node = await this.findNodeCoreBFS(
typeof predicate === 'string' ? n => n.id === predicate : predicate,
this.ensureRoot(),
allowPaging,
canTraverse,
maxDepth,
token
);
return node;
} catch (ex) {
Logger.error(ex, cc);
return undefined;
}
} }
// private async findNodeCoreDFS( // private async findNodeCoreDFS(
@ -295,6 +302,7 @@ export abstract class ViewBase> implements TreeData
let depth = 0; let depth = 0;
let node: ViewNode | undefined; let node: ViewNode | undefined;
let children: ViewNode[]; let children: ViewNode[];
let pagedChildren: ViewNode[];
while (queue.length > 1) { while (queue.length > 1) {
if (token !== undefined && token.isCancellationRequested) return undefined; if (token !== undefined && token.isCancellationRequested) return undefined;
@ -325,7 +333,15 @@ export abstract class ViewBase> implements TreeData
await this.showMoreNodeChildren(node, pageSize); await this.showMoreNodeChildren(node, pageSize);
child = (await node.getChildren()).find(predicate);
pagedChildren = await Functions.cancellable(
Promise.resolve(node.getChildren()),
token || 60000,
{
onDidCancel: resolve => resolve([])
}
);
child = pagedChildren.find(predicate);
if (child !== undefined) return child; if (child !== undefined) return child;
if (pageSize === 0) break; if (pageSize === 0) break;

Loading…
Cancel
Save