mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-01-14 01:25:43 -05:00
Refactors git command caching
Now caching many more commands to reduce git/parsing roundtrips and increase performance
This commit is contained in:
@@ -6,7 +6,7 @@ import { BlameAnnotationFormat, BlameAnnotationFormatter } from './blameAnnotati
|
||||
import { TextEditorComparer } from './comparers';
|
||||
import { IBlameConfig, IConfig, StatusBarCommand } from './configuration';
|
||||
import { DocumentSchemes, ExtensionKey } from './constants';
|
||||
import { BlameabilityChangeEvent, GitCommit, GitContextTracker, GitService, GitUri, IGitBlame, IGitCommitLine } from './gitService';
|
||||
import { BlameabilityChangeEvent, GitCommit, GitContextTracker, GitService, GitUri, IGitCommitLine } from './gitService';
|
||||
import * as moment from 'moment';
|
||||
|
||||
const activeLineDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
|
||||
@@ -18,7 +18,6 @@ const activeLineDecoration: TextEditorDecorationType = window.createTextEditorDe
|
||||
export class BlameActiveLineController extends Disposable {
|
||||
|
||||
private _activeEditorLineDisposable: Disposable | undefined;
|
||||
private _blame: Promise<IGitBlame> | undefined;
|
||||
private _blameable: boolean;
|
||||
private _config: IConfig;
|
||||
private _currentLine: number = -1;
|
||||
@@ -27,7 +26,6 @@ export class BlameActiveLineController extends Disposable {
|
||||
private _statusBarItem: StatusBarItem | undefined;
|
||||
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
|
||||
private _uri: GitUri;
|
||||
private _useCaching: boolean;
|
||||
|
||||
constructor(context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker, private annotationController: BlameAnnotationController) {
|
||||
super(() => this.dispose());
|
||||
@@ -135,13 +133,11 @@ export class BlameActiveLineController extends Disposable {
|
||||
this._blameable = editor !== undefined && editor.document !== undefined && !editor.document.isDirty;
|
||||
this._editor = editor;
|
||||
this._uri = await GitUri.fromUri(editor.document.uri, this.git);
|
||||
|
||||
const maxLines = this._config.advanced.caching.statusBar.maxLines;
|
||||
this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines);
|
||||
if (this._useCaching) {
|
||||
this._blame = this.git.getBlameForFile(this._uri);
|
||||
}
|
||||
else {
|
||||
this._blame = undefined;
|
||||
// If caching is on and the file is small enough -- kick off a blame for the whole file
|
||||
if (this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines)) {
|
||||
this.git.getBlameForFile(this._uri);
|
||||
}
|
||||
|
||||
this._updateBlame(editor.selection.active.line, editor);
|
||||
@@ -165,7 +161,6 @@ export class BlameActiveLineController extends Disposable {
|
||||
}
|
||||
|
||||
private _onGitCacheChanged() {
|
||||
this._blame = undefined;
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
@@ -191,22 +186,9 @@ export class BlameActiveLineController extends Disposable {
|
||||
let commitLine: IGitCommitLine | undefined = undefined;
|
||||
// Since blame information isn't valid when there are unsaved changes -- don't show any status
|
||||
if (this._blameable && line >= 0) {
|
||||
if (this._useCaching) {
|
||||
const blame = this._blame && await this._blame;
|
||||
if (blame === undefined || !blame.lines.length) {
|
||||
this.clear(editor);
|
||||
return;
|
||||
}
|
||||
|
||||
commitLine = blame.lines[line];
|
||||
const sha = commitLine === undefined ? undefined : commitLine.sha;
|
||||
commit = sha === undefined ? undefined : blame.commits.get(sha);
|
||||
}
|
||||
else {
|
||||
const blameLine = await this.git.getBlameForLine(this._uri, line);
|
||||
commitLine = blameLine === undefined ? undefined : blameLine.line;
|
||||
commit = blameLine === undefined ? undefined : blameLine.commit;
|
||||
}
|
||||
const blameLine = await this.git.getBlameForLine(this._uri, line);
|
||||
commitLine = blameLine === undefined ? undefined : blameLine.line;
|
||||
commit = blameLine === undefined ? undefined : blameLine.commit;
|
||||
}
|
||||
|
||||
if (commit !== undefined && commitLine !== undefined) {
|
||||
|
||||
@@ -25,15 +25,21 @@ class UriCacheEntry {
|
||||
|
||||
class GitCacheEntry {
|
||||
|
||||
blame?: ICachedBlame;
|
||||
log?: ICachedLog;
|
||||
|
||||
get hasErrors(): boolean {
|
||||
return (this.blame !== undefined && this.blame.errorMessage !== undefined) ||
|
||||
(this.log !== undefined && this.log.errorMessage !== undefined);
|
||||
}
|
||||
private cache: Map<string, ICachedBlame | ICachedLog> = new Map();
|
||||
|
||||
constructor(public key: string) { }
|
||||
|
||||
get hasErrors(): boolean {
|
||||
return Iterables.every(this.cache.values(), _ => _.errorMessage !== undefined);
|
||||
}
|
||||
|
||||
get<T extends ICachedBlame | ICachedLog > (key: string): T | undefined {
|
||||
return this.cache.get(key) as T;
|
||||
}
|
||||
|
||||
set<T extends ICachedBlame | ICachedLog > (key: string, value: T) {
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
interface ICachedItem<T> {
|
||||
@@ -312,38 +318,65 @@ export class GitService extends Disposable {
|
||||
}
|
||||
|
||||
async getBlameForFile(uri: GitUri): Promise<IGitBlame | undefined> {
|
||||
Logger.log(`getBlameForFile('${uri.repoPath}', '${uri.fsPath}', ${uri.sha})`);
|
||||
let key: string = 'blame';
|
||||
if (uri.sha !== undefined) {
|
||||
key += `:${uri.sha}`;
|
||||
}
|
||||
|
||||
const fileName = uri.fsPath;
|
||||
|
||||
let entry: GitCacheEntry | undefined;
|
||||
if (this.UseCaching && !uri.sha) {
|
||||
if (this.UseCaching) {
|
||||
const cacheKey = this.getCacheEntryKey(fileName);
|
||||
entry = this._gitCache.get(cacheKey);
|
||||
|
||||
if (entry !== undefined && entry.blame !== undefined) return entry.blame.item;
|
||||
if (entry !== undefined) {
|
||||
const cachedBlame = entry.get<ICachedBlame>(key);
|
||||
if (cachedBlame !== undefined) {
|
||||
Logger.log(`Cached(${key}): getBlameForFile('${uri.repoPath}', '${uri.fsPath}', ${uri.sha})`);
|
||||
return cachedBlame.item;
|
||||
}
|
||||
|
||||
if (key !== 'blame') {
|
||||
// Since we are looking for partial blame, see if we have the blame of the whole file
|
||||
const cachedBlame = entry.get<ICachedBlame>('blame');
|
||||
if (cachedBlame !== undefined) {
|
||||
Logger.log(`? Cache(${key}): getBlameForFile('${uri.repoPath}', '${uri.fsPath}', ${uri.sha})`);
|
||||
const blame = await cachedBlame.item;
|
||||
if (blame !== undefined && blame.commits.has(uri.sha!)) {
|
||||
Logger.log(`Cached(${key}): getBlameForFile('${uri.repoPath}', '${uri.fsPath}', ${uri.sha})`);
|
||||
return cachedBlame.item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.log(`Not Cached(${key}): getBlameForFile('${uri.repoPath}', '${uri.fsPath}', ${uri.sha})`);
|
||||
|
||||
if (entry === undefined) {
|
||||
entry = new GitCacheEntry(cacheKey);
|
||||
this._gitCache.set(entry.key, entry);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Logger.log(`getBlameForFile('${uri.repoPath}', '${uri.fsPath}', ${uri.sha})`);
|
||||
}
|
||||
|
||||
const promise = this._getBlameForFile(uri, fileName, entry);
|
||||
const promise = this._getBlameForFile(uri, fileName, entry, key);
|
||||
|
||||
if (entry) {
|
||||
Logger.log(`Add blame cache for '${entry.key}'`);
|
||||
Logger.log(`Add blame cache for '${entry.key}:${key}'`);
|
||||
|
||||
entry.blame = {
|
||||
entry.set<ICachedBlame>(key, {
|
||||
//date: new Date(),
|
||||
item: promise
|
||||
} as ICachedBlame;
|
||||
|
||||
this._gitCache.set(entry.key, entry);
|
||||
} as ICachedBlame);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private async _getBlameForFile(uri: GitUri, fileName: string, entry: GitCacheEntry | undefined): Promise<IGitBlame | undefined> {
|
||||
private async _getBlameForFile(uri: GitUri, fileName: string, entry: GitCacheEntry | undefined, key: string): Promise<IGitBlame | undefined> {
|
||||
const [file, root] = Git.splitPath(fileName, uri.repoPath, false);
|
||||
|
||||
const ignore = await this._gitignore;
|
||||
@@ -363,16 +396,15 @@ export class GitService extends Disposable {
|
||||
// Trap and cache expected blame errors
|
||||
if (entry) {
|
||||
const msg = ex && ex.toString();
|
||||
Logger.log(`Replace blame cache with empty promise for '${entry.key}'`);
|
||||
Logger.log(`Replace blame cache with empty promise for '${entry.key}:${key}'`);
|
||||
|
||||
entry.blame = {
|
||||
entry.set<ICachedBlame>(key, {
|
||||
//date: new Date(),
|
||||
item: GitService.EmptyPromise,
|
||||
errorMessage: msg
|
||||
} as ICachedBlame;
|
||||
} as ICachedBlame);
|
||||
|
||||
this._onDidBlameFail.fire(entry.key);
|
||||
this._gitCache.set(entry.key, entry);
|
||||
return await GitService.EmptyPromise as IGitBlame;
|
||||
}
|
||||
|
||||
@@ -383,7 +415,7 @@ export class GitService extends Disposable {
|
||||
async getBlameForLine(uri: GitUri, line: number): Promise<IGitBlameLine | undefined> {
|
||||
Logger.log(`getBlameForLine('${uri.repoPath}', '${uri.fsPath}', ${line}, ${uri.sha})`);
|
||||
|
||||
if (this.UseCaching && !uri.sha) {
|
||||
if (this.UseCaching) {
|
||||
const blame = await this.getBlameForFile(uri);
|
||||
if (blame === undefined) return undefined;
|
||||
|
||||
@@ -604,37 +636,72 @@ export class GitService extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
getLogForFile(repoPath: string | undefined, fileName: string, sha?: string, maxCount?: number, range?: Range, reverse: boolean = false): Promise<IGitLog | undefined> {
|
||||
Logger.log(`getLogForFile('${repoPath}', '${fileName}', ${sha}, ${maxCount}, ${range && `[${range.start.line}, ${range.end.line}]`}, ${reverse})`);
|
||||
async getLogForFile(repoPath: string | undefined, fileName: string, sha?: string, maxCount?: number, range?: Range, reverse: boolean = false): Promise<IGitLog | undefined> {
|
||||
let key: string = 'log';
|
||||
if (sha !== undefined) {
|
||||
key += `:${sha}`;
|
||||
}
|
||||
if (maxCount !== undefined) {
|
||||
key += `:n${maxCount}`;
|
||||
}
|
||||
|
||||
let entry: GitCacheEntry | undefined;
|
||||
if (this.UseCaching && !sha && !range && !maxCount && !reverse) {
|
||||
if (this.UseCaching && range === undefined && !reverse) {
|
||||
const cacheKey = this.getCacheEntryKey(fileName);
|
||||
entry = this._gitCache.get(cacheKey);
|
||||
|
||||
if (entry !== undefined && entry.log !== undefined) return entry.log.item;
|
||||
if (entry !== undefined) {
|
||||
const cachedLog = entry.get<ICachedLog>(key);
|
||||
if (cachedLog !== undefined) {
|
||||
Logger.log(`Cached(${key}): getLogForFile('${repoPath}', '${fileName}', ${sha}, ${maxCount}, undefined, false)`);
|
||||
return cachedLog.item;
|
||||
}
|
||||
|
||||
if (key !== 'log') {
|
||||
// Since we are looking for partial log, see if we have the log of the whole file
|
||||
const cachedLog = entry.get<ICachedLog>('log');
|
||||
if (cachedLog !== undefined) {
|
||||
if (sha === undefined) {
|
||||
Logger.log(`Cached(~${key}): getLogForFile('${repoPath}', '${fileName}', ${sha}, ${maxCount}, undefined, false)`);
|
||||
return cachedLog.item;
|
||||
}
|
||||
|
||||
Logger.log(`? Cache(${key}): getLogForFile('${repoPath}', '${fileName}', ${sha}, ${maxCount}, undefined, false)`);
|
||||
const log = await cachedLog.item;
|
||||
if (log !== undefined && log.commits.has(sha)) {
|
||||
Logger.log(`Cached(${key}): getLogForFile('${repoPath}', '${fileName}', ${sha}, ${maxCount}, undefined, false)`);
|
||||
return cachedLog.item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.log(`Not Cached(${key}): getLogForFile('${repoPath}', '${fileName}', ${sha}, ${maxCount}, undefined, false)`);
|
||||
|
||||
if (entry === undefined) {
|
||||
entry = new GitCacheEntry(cacheKey);
|
||||
this._gitCache.set(entry.key, entry);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Logger.log(`getLogForFile('${repoPath}', '${fileName}', ${sha}, ${maxCount}, ${range && `[${range.start.line}, ${range.end.line}]`}, ${reverse})`);
|
||||
}
|
||||
|
||||
const promise = this._getLogForFile(repoPath, fileName, sha, range, maxCount, reverse, entry);
|
||||
const promise = this._getLogForFile(repoPath, fileName, sha, range, maxCount, reverse, entry, key);
|
||||
|
||||
if (entry) {
|
||||
Logger.log(`Add log cache for '${entry.key}'`);
|
||||
Logger.log(`Add log cache for '${entry.key}:${key}'`);
|
||||
|
||||
entry.log = {
|
||||
entry.set<ICachedLog>(key, {
|
||||
//date: new Date(),
|
||||
item: promise
|
||||
} as ICachedLog;
|
||||
|
||||
this._gitCache.set(entry.key, entry);
|
||||
} as ICachedLog);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private async _getLogForFile(repoPath: string | undefined, fileName: string, sha: string | undefined, range: Range | undefined, maxCount: number | undefined, reverse: boolean, entry: GitCacheEntry | undefined): Promise<IGitLog | undefined> {
|
||||
private async _getLogForFile(repoPath: string | undefined, fileName: string, sha: string | undefined, range: Range | undefined, maxCount: number | undefined, reverse: boolean, entry: GitCacheEntry | undefined, key: string): Promise<IGitLog | undefined> {
|
||||
const [file, root] = Git.splitPath(fileName, repoPath, false);
|
||||
|
||||
const ignore = await this._gitignore;
|
||||
@@ -651,15 +718,14 @@ export class GitService extends Disposable {
|
||||
// Trap and cache expected log errors
|
||||
if (entry) {
|
||||
const msg = ex && ex.toString();
|
||||
Logger.log(`Replace log cache with empty promise for '${entry.key}'`);
|
||||
Logger.log(`Replace log cache with empty promise for '${entry.key}:${key}'`);
|
||||
|
||||
entry.log = {
|
||||
entry.set<ICachedLog>(key, {
|
||||
//date: new Date(),
|
||||
item: GitService.EmptyPromise,
|
||||
errorMessage: msg
|
||||
} as ICachedLog;
|
||||
} as ICachedLog);
|
||||
|
||||
this._gitCache.set(entry.key, entry);
|
||||
return await GitService.EmptyPromise as IGitLog;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user