Skip to content

Commit

Permalink
Adds lazy loading for avatars on the Graph
Browse files Browse the repository at this point in the history
  • Loading branch information
eamodio committed Sep 15, 2022
1 parent ceb29f5 commit 22009b9
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 58 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -11680,7 +11680,7 @@
"vscode:prepublish": "yarn run bundle"
},
"dependencies": {
"@gitkraken/gitkraken-components": "1.0.0-rc.13",
"@gitkraken/gitkraken-components": "1.0.0-rc.14",
"@microsoft/fast-element": "^1.10.5",
"@octokit/core": "4.0.5",
"@vscode/codicons": "0.0.32",
Expand Down
77 changes: 50 additions & 27 deletions src/avatars.ts
@@ -1,8 +1,8 @@
import { EventEmitter, Uri } from 'vscode';
import { GravatarDefaultStyle } from './config';
import { configuration } from './configuration';
import { ContextKeys } from './constants';
import { Container } from './container';
import type { GitRevisionReference } from './git/models/reference';
import { getContext } from './context';
import { getGitHubNoReplyAddressParts } from './git/remotes/github';
import type { StoredAvatar } from './storage';
import { debounce } from './system/function';
Expand Down Expand Up @@ -69,19 +69,41 @@ const retryDecay = [

export function getAvatarUri(
email: string | undefined,
repoPathOrCommit: undefined,
repoPathOrCommit?: undefined,
options?: { defaultStyle?: GravatarDefaultStyle; size?: number },
): Uri;
export function getAvatarUri(
email: string | undefined,
repoPathOrCommit: string | GitRevisionReference,
repoPathOrCommit: string | { ref: string; repoPath: string },
options?: { defaultStyle?: GravatarDefaultStyle; size?: number },
): Uri | Promise<Uri>;
export function getAvatarUri(
email: string | undefined,
repoPathOrCommit: string | GitRevisionReference | undefined,
repoPathOrCommit: string | { ref: string; repoPath: string } | undefined,
options?: { defaultStyle?: GravatarDefaultStyle; size?: number },
): Uri | Promise<Uri> {
return getAvatarUriCore(email, repoPathOrCommit, options);
}

export function getCachedAvatarUri(email: string | undefined, options?: { size?: number }): Uri | undefined {
return getAvatarUriCore(email, undefined, { ...options, cached: true });
}

function getAvatarUriCore(
email: string | undefined,
repoPathOrCommit: string | { ref: string; repoPath: string } | undefined,
options?: { cached: true; defaultStyle?: GravatarDefaultStyle; size?: number },
): Uri | undefined;
function getAvatarUriCore(
email: string | undefined,
repoPathOrCommit: string | { ref: string; repoPath: string } | undefined,
options?: { defaultStyle?: GravatarDefaultStyle; size?: number },
): Uri | Promise<Uri>;
function getAvatarUriCore(
email: string | undefined,
repoPathOrCommit: string | { ref: string; repoPath: string } | undefined,
options?: { cached?: boolean; defaultStyle?: GravatarDefaultStyle; size?: number },
): Uri | Promise<Uri> | undefined {
ensureAvatarCache(avatarCache);

// Double the size to avoid blurring on the retina screen
Expand All @@ -104,20 +126,22 @@ export function getAvatarUri(
const avatar = createOrUpdateAvatar(key, email, size, hash, options?.defaultStyle);
if (avatar.uri != null) return avatar.uri;

let query = avatarQueue.get(key);
if (query == null && repoPathOrCommit != null && hasAvatarExpired(avatar)) {
query = getAvatarUriFromRemoteProvider(avatar, key, email, repoPathOrCommit, { size: size }).then(
uri => uri ?? avatar.uri ?? avatar.fallback!,
);
avatarQueue.set(
key,
query.finally(() => avatarQueue.delete(key)),
);
}
if (!options?.cached && repoPathOrCommit != null && getContext(ContextKeys.HasConnectedRemotes)) {
let query = avatarQueue.get(key);
if (query == null && hasAvatarExpired(avatar)) {
query = getAvatarUriFromRemoteProvider(avatar, key, email, repoPathOrCommit, { size: size }).then(
uri => uri ?? avatar.uri ?? avatar.fallback!,
);
avatarQueue.set(
key,
query.finally(() => avatarQueue.delete(key)),
);
}

if (query != null) return query;
return query ?? avatar.fallback!;
}

return avatar.uri ?? avatar.fallback!;
return options?.cached ? avatar.uri : avatar.uri ?? avatar.fallback!;
}

function createOrUpdateAvatar(
Expand Down Expand Up @@ -183,23 +207,22 @@ async function getAvatarUriFromRemoteProvider(
avatar: Avatar,
key: string,
email: string,
repoPathOrCommit: string | GitRevisionReference,
repoPathOrCommit: string | { ref: string; repoPath: string },
{ size = 16 }: { size?: number } = {},
) {
ensureAvatarCache(avatarCache);

try {
let account;
if (configuration.get('integrations.enabled')) {
// if (typeof repoPathOrCommit === 'string') {
// const remote = await Container.instance.git.getRichRemoteProvider(repoPathOrCommit);
// account = await remote?.provider.getAccountForEmail(email, { avatarSize: size });
// } else {
if (typeof repoPathOrCommit !== 'string') {
const remote = await Container.instance.git.getBestRemoteWithRichProvider(repoPathOrCommit.repoPath);
account = await remote?.provider.getAccountForCommit(repoPathOrCommit.ref, { avatarSize: size });
}
// if (typeof repoPathOrCommit === 'string') {
// const remote = await Container.instance.git.getRichRemoteProvider(repoPathOrCommit);
// account = await remote?.provider.getAccountForEmail(email, { avatarSize: size });
// } else {
if (typeof repoPathOrCommit !== 'string') {
const remote = await Container.instance.git.getBestRemoteWithRichProvider(repoPathOrCommit.repoPath);
account = await remote?.provider.getAccountForCommit(repoPathOrCommit.ref, { avatarSize: size });
}

if (account?.avatarUrl == null) {
// If we have no account assume that won't change (without a reset), so set the timestamp to "never expire"
avatar.uri = undefined;
Expand Down
20 changes: 15 additions & 5 deletions src/env/node/git/localGitProvider.ts
Expand Up @@ -12,7 +12,7 @@ import type {
Repository as BuiltInGitRepository,
GitExtension,
} from '../../../@types/vscode.git';
import { getAvatarUri } from '../../../avatars';
import { getCachedAvatarUri } from '../../../avatars';
import { configuration } from '../../../configuration';
import { CoreGitConfiguration, GlyphChars, Schemes } from '../../../constants';
import type { Container } from '../../../container';
Expand Down Expand Up @@ -1635,7 +1635,6 @@ export class LocalGitProvider implements GitProvider, Disposable {
const remotes = getSettledValue(remotesResult);
const remoteMap = remotes != null ? new Map(remotes.map(r => [r.name, r])) : new Map();
const selectSha = first(refParser.parse(getSettledValue(refResult) ?? ''));
const skipStashParents = new Set();

let stdin: string | undefined;
// TODO@eamodio this is insanity -- there *HAS* to be a better way to get git log to return stashes
Expand All @@ -1647,7 +1646,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
);
}

const avatars = new Map<string, string>();
const ids = new Set<string>();
const skipStashParents = new Set();
let total = 0;
let iterations = 0;

Expand Down Expand Up @@ -1698,7 +1699,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
: data.indexOf(`\x00\x00${cursor.sha}\x00`);
if (cursorIndex === -1) {
// If we didn't find any new commits, we must have them all so return that we have everything
if (size === data.length) return { repoPath: repoPath, ids: ids, rows: [] };
if (size === data.length) {
return { repoPath: repoPath, avatars: avatars, ids: ids, rows: [] };
}

size = data.length;
nextPageLimit = (nextPageLimit === 0 ? defaultPageLimit : nextPageLimit) * 2;
Expand All @@ -1723,7 +1726,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
}
}

if (!data) return { repoPath: repoPath, ids: ids, rows: [] };
if (!data) return { repoPath: repoPath, avatars: avatars, ids: ids, rows: [] };

log = data;
if (limit !== 0) {
Expand Down Expand Up @@ -1818,11 +1821,17 @@ export class LocalGitProvider implements GitProvider, Disposable {
parents.splice(1, 2);
}

if (!isStashCommit && !avatars.has(commit.authorEmail)) {
const uri = getCachedAvatarUri(commit.authorEmail);
if (uri != null) {
avatars.set(commit.authorEmail, uri.toString(true));
}
}

rows.push({
sha: commit.sha,
parents: parents,
author: commit.author,
avatarUrl: !isStashCommit ? getAvatarUri(commit.authorEmail, undefined).toString(true) : undefined,
email: commit.authorEmail ?? '',
date: Number(ordering === 'author-date' ? commit.authorDate : commit.committerDate) * 1000,
message: emojify(commit.message),
Expand Down Expand Up @@ -1850,6 +1859,7 @@ export class LocalGitProvider implements GitProvider, Disposable {

return {
repoPath: repoPath,
avatars: avatars,
ids: ids,
rows: rows,
sha: sha,
Expand Down
6 changes: 5 additions & 1 deletion src/git/gitProviderService.ts
Expand Up @@ -201,6 +201,10 @@ export class GitProviderService implements Disposable {
if (configuration.changed(e, 'views.contributors.showAllBranches')) {
this.resetCaches('contributors');
}

if (e != null && configuration.changed(e, 'integrations.enabled')) {
this.updateContext();
}
}

@debug()
Expand Down Expand Up @@ -739,7 +743,7 @@ export class GitProviderService implements Disposable {
let hasRemotes = false;
let hasRichRemotes = false;
let hasConnectedRemotes = false;
if (hasRepositories) {
if (hasRepositories && configuration.get('integrations.enabled')) {
for (const repo of this._repositories.values()) {
if (!hasConnectedRemotes) {
hasConnectedRemotes = await repo.hasRichRemote(true);
Expand Down
12 changes: 9 additions & 3 deletions src/git/models/commit.ts
@@ -1,5 +1,5 @@
import { Uri } from 'vscode';
import { getAvatarUri } from '../../avatars';
import { getAvatarUri, getCachedAvatarUri } from '../../avatars';
import type { GravatarDefaultStyle } from '../../configuration';
import { DateSource, DateStyle } from '../../configuration';
import { GlyphChars } from '../../constants';
Expand Down Expand Up @@ -437,6 +437,10 @@ export class GitCommit implements GitRevisionReference {
return this.author.getAvatarUri(this, options);
}

getCachedAvatarUri(options?: { size?: number }): Uri | undefined {
return this.author.getCachedAvatarUri(options);
}

async getCommitForFile(file: string | GitFile): Promise<GitCommit | undefined> {
const path = typeof file === 'string' ? this.container.git.getRelativePath(file, this.repoPath) : file.path;
const foundFile = await this.findFile(path);
Expand Down Expand Up @@ -616,9 +620,11 @@ export class GitCommitIdentity implements GitCommitIdentityShape {
commit: GitCommit,
options?: { defaultStyle?: GravatarDefaultStyle; size?: number },
): Uri | Promise<Uri> {
if (this.avatarUrl != null) return Uri.parse(this.avatarUrl);
return this.avatarUrl != null ? Uri.parse(this.avatarUrl) : getAvatarUri(this.email, commit, options);
}

return getAvatarUri(this.email, commit, options);
getCachedAvatarUri(options?: { size?: number }): Uri | undefined {
return this.avatarUrl != null ? Uri.parse(this.avatarUrl) : getCachedAvatarUri(this.email, options);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/git/models/graph.ts
Expand Up @@ -21,6 +21,8 @@ export interface GitGraphRow extends GraphRow {

export interface GitGraph {
readonly repoPath: string;
/** A map of all avatar urls */
readonly avatars: Map<string, string>;
/** A set of all "seen" commit ids */
readonly ids: Set<string>;
/** The rows for the set of commits requested */
Expand Down
26 changes: 24 additions & 2 deletions src/plus/github/githubGitProvider.ts
Expand Up @@ -1075,6 +1075,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
this.getTags(repoPath),
]);

const avatars = new Map<string, string>();
const ids = new Set<string>();

return this.getCommitsForGraphCore(
Expand All @@ -1084,6 +1085,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
getSettledValue(branchResult),
getSettledValue(remotesResult)?.[0],
getSettledValue(tagsResult)?.values,
avatars,
ids,
options,
);
Expand All @@ -1096,6 +1098,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
branch: GitBranch | undefined,
remote: GitRemote | undefined,
tags: GitTag[] | undefined,
avatars: Map<string, string>,
ids: Set<string>,
options?: {
branch?: string;
Expand All @@ -1107,6 +1110,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
if (log == null) {
return {
repoPath: repoPath,
avatars: avatars,
ids: ids,
rows: [],
};
Expand All @@ -1116,6 +1120,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
if (commits == null) {
return {
repoPath: repoPath,
avatars: avatars,
ids: ids,
rows: [],
};
Expand Down Expand Up @@ -1169,11 +1174,17 @@ export class GitHubGitProvider implements GitProvider, Disposable {
refTags = [];
}

if (commit.author.email && !avatars.has(commit.author.email)) {
const uri = commit.getCachedAvatarUri();
if (uri != null) {
avatars.set(commit.author.email, uri.toString(true));
}
}

rows.push({
sha: commit.sha,
parents: commit.parents,
author: commit.author.name,
avatarUrl: (await commit.getAvatarUri())?.toString(true),
email: commit.author.email ?? '',
date: commit.committer.date.getTime(),
message: emojify(commit.message && String(commit.message).length ? commit.message : commit.summary),
Expand All @@ -1193,6 +1204,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {

return {
repoPath: repoPath,
avatars: avatars,
ids: ids,
rows: rows,
sha: options?.ref,
Expand All @@ -1205,7 +1217,17 @@ export class GitHubGitProvider implements GitProvider, Disposable {
},
more: async (limit: number | { until: string } | undefined): Promise<GitGraph | undefined> => {
const moreLog = await log.more?.(limit);
return this.getCommitsForGraphCore(repoPath, asWebviewUri, moreLog, branch, remote, tags, ids, options);
return this.getCommitsForGraphCore(
repoPath,
asWebviewUri,
moreLog,
branch,
remote,
tags,
avatars,
ids,
options,
);
},
};
}
Expand Down

0 comments on commit 22009b9

Please sign in to comment.