Skip to content

Commit

Permalink
Git - simplify git blame caching (#238462)
Browse files Browse the repository at this point in the history
* Git - simplify git blame caching

* 💄 remove code that is not needed
  • Loading branch information
lszomoru authored Jan 22, 2025
1 parent d826882 commit c953035
Showing 1 changed file with 22 additions and 76 deletions.
98 changes: 22 additions & 76 deletions extensions/git/src/blame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { dispose, fromNow, getCommitShortHash, IDisposable } from './util';
import { Repository } from './repository';
import { throttle } from './decorators';
import { BlameInformation, Commit } from './git';
import { fromGitUri, isGitUri } from './uri';
import { fromGitUri, isGitUri, toGitUri } from './uri';
import { emojify, ensureEmojis } from './emoji';
import { getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation } from './staging';
import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider';
Expand Down Expand Up @@ -79,82 +79,34 @@ type BlameInformationTemplateTokens = {
readonly authorDateAgo: string;
};

interface RepositoryBlameInformation {
/**
* Track the current HEAD of the repository so that we can clear cache entries
*/
HEAD: string;

/**
* Outer map - maps resource scheme to resource blame information. Using the uri
* scheme as the key so that we can easily delete the cache entries for the "file"
* scheme as those entries are outdated when the HEAD of the repository changes.
*
* Inner map - maps commit + resource to blame information.
*/
readonly blameInformation: Map<string, Map<string, BlameInformation[]>>;
}

interface LineBlameInformation {
readonly lineNumber: number;
readonly blameInformation: BlameInformation | string;
}

class GitBlameInformationCache {
private readonly _cache = new Map<Repository, RepositoryBlameInformation>();

getRepositoryHEAD(repository: Repository): string | undefined {
return this._cache.get(repository)?.HEAD;
}

setRepositoryHEAD(repository: Repository, commit: string): void {
const repositoryBlameInformation = this._cache.get(repository) ?? {
HEAD: commit,
blameInformation: new Map<string, Map<string, BlameInformation[]>>()
} satisfies RepositoryBlameInformation;

this._cache.set(repository, {
...repositoryBlameInformation,
HEAD: commit
} satisfies RepositoryBlameInformation);
}

deleteBlameInformation(repository: Repository, scheme?: string): boolean {
if (scheme === undefined) {
return this._cache.delete(repository);
}
private readonly _cache = new Map<Repository, LRUCache<string, BlameInformation[]>>();

return this._cache.get(repository)?.blameInformation.delete(scheme) === true;
delete(repository: Repository): boolean {
return this._cache.delete(repository);
}

getBlameInformation(repository: Repository, resource: Uri, commit: string): BlameInformation[] | undefined {
const blameInformationKey = this._getBlameInformationKey(resource, commit);
return this._cache.get(repository)?.blameInformation.get(resource.scheme)?.get(blameInformationKey);
get(repository: Repository, resource: Uri, commit: string): BlameInformation[] | undefined {
const key = this._getCacheKey(resource, commit);
return this._cache.get(repository)?.get(key);
}

setBlameInformation(repository: Repository, resource: Uri, commit: string, blameInformation: BlameInformation[]): void {
if (!repository.HEAD?.commit) {
return;
}

set(repository: Repository, resource: Uri, commit: string, blameInformation: BlameInformation[]): void {
if (!this._cache.has(repository)) {
this._cache.set(repository, {
HEAD: repository.HEAD.commit,
blameInformation: new Map<string, Map<string, BlameInformation[]>>()
} satisfies RepositoryBlameInformation);
}

const repositoryBlameInformation = this._cache.get(repository)!;
if (!repositoryBlameInformation.blameInformation.has(resource.scheme)) {
repositoryBlameInformation.blameInformation.set(resource.scheme, new Map<string, BlameInformation[]>());
this._cache.set(repository, new LRUCache<string, BlameInformation[]>(100));
}

const resourceSchemeBlameInformation = repositoryBlameInformation.blameInformation.get(resource.scheme)!;
resourceSchemeBlameInformation.set(this._getBlameInformationKey(resource, commit), blameInformation);
const key = this._getCacheKey(resource, commit);
this._cache.get(repository)!.set(key, blameInformation);
}

private _getBlameInformationKey(resource: Uri, commit: string): string {
return `${commit}:${resource.toString()}`;
private _getCacheKey(resource: Uri, commit: string): string {
return toGitUri(resource, commit).toString();
}
}

Expand All @@ -173,6 +125,7 @@ export class GitBlameController {
this._onDidChangeBlameInformation.fire();
}

private _HEAD: string | undefined;
private readonly _commitInformationCache = new LRUCache<string, Commit>(100);
private readonly _repositoryBlameCache = new GitBlameInformationCache();

Expand Down Expand Up @@ -389,23 +342,16 @@ export class GitBlameController {
}

this._repositoryDisposables.delete(repository);
this._repositoryBlameCache.deleteBlameInformation(repository);
this._repositoryBlameCache.delete(repository);
}

private _onDidRunGitStatus(repository: Repository): void {
const repositoryHEAD = this._repositoryBlameCache.getRepositoryHEAD(repository);
if (!repositoryHEAD || !repository.HEAD?.commit) {
if (!repository.HEAD?.commit || this._HEAD === repository.HEAD.commit) {
return;
}

// If the HEAD of the repository changed we can remove the cache
// entries for the "file" scheme as those entries are outdated.
if (repositoryHEAD !== repository.HEAD.commit) {
this._repositoryBlameCache.deleteBlameInformation(repository, 'file');
this._repositoryBlameCache.setRepositoryHEAD(repository, repository.HEAD.commit);

this._updateTextEditorBlameInformation(window.activeTextEditor);
}
this._HEAD = repository.HEAD.commit;
this._updateTextEditorBlameInformation(window.activeTextEditor);
}

private async _getBlameInformation(resource: Uri, commit: string): Promise<BlameInformation[] | undefined> {
Expand All @@ -414,18 +360,18 @@ export class GitBlameController {
return undefined;
}

const resourceBlameInformation = this._repositoryBlameCache.getBlameInformation(repository, resource, commit);
const resourceBlameInformation = this._repositoryBlameCache.get(repository, resource, commit);
if (resourceBlameInformation) {
return resourceBlameInformation;
}

// Ensure that the emojis are loaded. We will
// use them when formatting the blame information.
// Ensure that the emojis are loaded as we will need
// access to them when formatting the blame information.
await ensureEmojis();

// Get blame information for the resource and cache it
const blameInformation = await repository.blame2(resource.fsPath, commit) ?? [];
this._repositoryBlameCache.setBlameInformation(repository, resource, commit, blameInformation);
this._repositoryBlameCache.set(repository, resource, commit, blameInformation);

return blameInformation;
}
Expand Down

0 comments on commit c953035

Please sign in to comment.