Browse Source

Supports querying for commit details for VFH

Adds a limit on the number of queries
main
Eric Amodio 3 years ago
parent
commit
c80e4ae01b
7 changed files with 113 additions and 25 deletions
  1. +13
    -0
      package.json
  2. +3
    -0
      src/config.ts
  3. +4
    -2
      src/git/gitProviderService.ts
  4. +2
    -2
      src/premium/webviews/timeline/protocol.ts
  5. +32
    -5
      src/premium/webviews/timeline/timelineWebview.ts
  6. +53
    -8
      src/premium/webviews/timeline/timelineWebviewView.ts
  7. +6
    -8
      src/webviews/apps/premium/timeline/chart.ts

+ 13
- 0
package.json View File

@ -2399,6 +2399,19 @@
}
},
{
"id": "visual-history",
"title": "Visual File History",
"order": 112,
"properties": {
"gitlens.visualHistory.queryLimit": {
"type": "number",
"default": 20,
"markdownDescription": "Specifies the limit on the how many commits can be queried for statistics in the Visual File History, because of rate limits. Only applies to virtual workspaces.",
"scope": "window"
}
}
},
{
"id": "date-times",
"title": "Date & Times",
"order": 120,

+ 3
- 0
src/config.ts View File

@ -148,6 +148,9 @@ export interface Config {
enabled: boolean;
};
views: ViewsConfig;
visualHistory: {
queryLimit: number;
};
worktrees: {
defaultLocation: string | null;
promptForLocation: boolean;

+ 4
- 2
src/git/gitProviderService.ts View File

@ -105,11 +105,13 @@ const weightedDefaultBranches = new Map([
export type GitProvidersChangeEvent = {
readonly added: readonly GitProvider[];
readonly removed: readonly GitProvider[];
readonly etag: number;
};
export type RepositoriesChangeEvent = {
readonly added: readonly Repository[];
readonly removed: readonly Repository[];
readonly etag: number;
};
export interface GitProviderResult {
@ -133,7 +135,7 @@ export class GitProviderService implements Disposable {
private fireProvidersChanged(added?: GitProvider[], removed?: GitProvider[]) {
this._etag = Date.now();
this._onDidChangeProviders.fire({ added: added ?? [], removed: removed ?? [] });
this._onDidChangeProviders.fire({ added: added ?? [], removed: removed ?? [], etag: this._etag });
}
private _onDidChangeRepositories = new EventEmitter<RepositoriesChangeEvent>();
@ -148,7 +150,7 @@ export class GitProviderService implements Disposable {
if (removed?.length) {
this._visibilityCache.clear();
}
this._onDidChangeRepositories.fire({ added: added ?? [], removed: removed ?? [] });
this._onDidChangeRepositories.fire({ added: added ?? [], removed: removed ?? [], etag: this._etag });
}
private readonly _onDidChangeRepository = new EventEmitter<RepositoryChangeEvent>();

+ 2
- 2
src/premium/webviews/timeline/protocol.ts View File

@ -17,8 +17,8 @@ export interface Commit {
date: string;
message: string;
additions: number;
deletions: number;
additions: number | undefined;
deletions: number | undefined;
sort: number;
}

+ 32
- 5
src/premium/webviews/timeline/timelineWebview.ts View File

@ -1,6 +1,7 @@
'use strict';
import { commands, Disposable, TextEditor, Uri, window } from 'vscode';
import { ShowQuickCommitCommandArgs } from '../../../commands';
import { configuration } from '../../../configuration';
import { Commands, ContextKeys } from '../../../constants';
import type { Container } from '../../../container';
import { setContext } from '../../../context';
@ -10,6 +11,7 @@ import { RepositoryChange, RepositoryChangeComparisonMode, RepositoryChangeEvent
import { createFromDateDelta } from '../../../system/date';
import { debug } from '../../../system/decorators/log';
import { debounce, Deferrable } from '../../../system/function';
import { filter } from '../../../system/iterable';
import { hasVisibleTextEditor, isTextEditor } from '../../../system/utils';
import { IpcMessage, onIpc } from '../../../webviews/protocol';
import { WebviewBase } from '../../../webviews/webviewBase';
@ -117,7 +119,11 @@ export class TimelineWebview extends WebviewBase {
// Since this gets called even the first time the webview is shown, avoid sending an update, because the bootstrap has the data
if (this._bootstraping) {
this._bootstraping = false;
return;
// If the uri changed since bootstrap still send the update
if (this._pendingContext == null || !('uri' in this._pendingContext)) {
return;
}
}
// Should be immediate, but it causes the bubbles to go missing on the chart, since the update happens while it still rendering
@ -229,16 +235,37 @@ export class TimelineWebview extends WebviewBase {
};
}
let queryRequiredCommits = [
...filter(log.commits.values(), c => c.file?.stats == null && c.stats?.changedFiles !== 1),
];
if (queryRequiredCommits.length !== 0) {
const limit = configuration.get('visualHistory.queryLimit') ?? 20;
const repository = this.container.git.getRepository(current.uri);
const name = repository?.provider.name;
if (queryRequiredCommits.length > limit) {
void window.showWarningMessage(
`Unable able to show more than the first ${limit} commits for the specified time period because of ${
name ? `${name} ` : ''
}rate limits.`,
);
queryRequiredCommits = queryRequiredCommits.slice(0, 20);
}
void (await Promise.allSettled(queryRequiredCommits.map(c => c.ensureFullDetails())));
}
const name = currentUser?.name ? `${currentUser.name} (you)` : 'You';
const dataset: Commit[] = [];
for (const commit of log.commits.values()) {
const stats = commit.file?.stats;
const stats = commit.file?.stats ?? (commit.stats?.changedFiles === 1 ? commit.stats : undefined);
dataset.push({
author: commit.author.name === 'You' ? name : commit.author.name,
additions: stats?.additions ?? 0,
// changed: stats?.changes ?? 0,
deletions: stats?.deletions ?? 0,
additions: stats?.additions,
deletions: stats?.deletions,
commit: commit.sha,
date: commit.date.toISOString(),
message: commit.message ?? commit.summary,

+ 53
- 8
src/premium/webviews/timeline/timelineWebviewView.ts View File

@ -1,14 +1,17 @@
'use strict';
import { commands, Disposable, TextEditor, Uri, window } from 'vscode';
import { ShowQuickCommitCommandArgs } from '../../../commands';
import { configuration } from '../../../configuration';
import { Commands } from '../../../constants';
import { Container } from '../../../container';
import { PremiumFeatures } from '../../../features';
import { RepositoriesChangeEvent } from '../../../git/gitProviderService';
import { GitUri } from '../../../git/gitUri';
import { RepositoryChange, RepositoryChangeComparisonMode, RepositoryChangeEvent } from '../../../git/models';
import { createFromDateDelta } from '../../../system/date';
import { debug } from '../../../system/decorators/log';
import { debounce, Deferrable } from '../../../system/function';
import { filter } from '../../../system/iterable';
import { hasVisibleTextEditor, isTextEditor } from '../../../system/utils';
import { IpcMessage, onIpc } from '../../../webviews/protocol';
import { WebviewViewBase } from '../../../webviews/webviewViewBase';
@ -25,6 +28,7 @@ import {
interface Context {
uri: Uri | undefined;
period: Period | undefined;
etagRepositories: number | undefined;
etagRepository: number | undefined;
etagSubscription: number | undefined;
}
@ -44,6 +48,7 @@ export class TimelineWebviewView extends WebviewViewBase {
this._context = {
uri: undefined,
period: defaultPeriod,
etagRepositories: 0,
etagRepository: 0,
etagSubscription: 0,
};
@ -53,6 +58,7 @@ export class TimelineWebviewView extends WebviewViewBase {
this._context = {
uri: undefined,
period: defaultPeriod,
etagRepositories: this.container.git.etag,
etagRepository: 0,
etagSubscription: this.container.subscription.etag,
};
@ -65,6 +71,7 @@ export class TimelineWebviewView extends WebviewViewBase {
this.container.subscription.onDidChange(this.onSubscriptionChanged, this),
window.onDidChangeActiveTextEditor(this.onActiveEditorChanged, this),
this.container.git.onDidChangeRepository(this.onRepositoryChanged, this),
this.container.git.onDidChangeRepositories(this.onRepositoriesChanged, this),
];
}
@ -90,7 +97,11 @@ export class TimelineWebviewView extends WebviewViewBase {
// Since this gets called even the first time the webview is shown, avoid sending an update, because the bootstrap has the data
if (this._bootstraping) {
this._bootstraping = false;
return;
// If the uri changed since bootstrap still send the update
if (this._pendingContext == null || !('uri' in this._pendingContext)) {
return;
}
}
// Should be immediate, but it causes the bubbles to go missing on the chart, since the update happens while it still rendering
@ -150,6 +161,15 @@ export class TimelineWebviewView extends WebviewViewBase {
}
@debug({ args: false })
private onRepositoriesChanged(e: RepositoriesChangeEvent) {
const changed = this.updatePendingUri(this._context.uri);
if (this.updatePendingContext({ etagRepositories: e.etag }) || changed) {
this.updateState();
}
}
@debug({ args: false })
private onRepositoryChanged(e: RepositoryChangeEvent) {
if (!e.changed(RepositoryChange.Heads, RepositoryChange.Index, RepositoryChangeComparisonMode.Any)) {
return;
@ -209,16 +229,37 @@ export class TimelineWebviewView extends WebviewViewBase {
};
}
let queryRequiredCommits = [
...filter(log.commits.values(), c => c.file?.stats == null && c.stats?.changedFiles !== 1),
];
if (queryRequiredCommits.length !== 0) {
const limit = configuration.get('visualHistory.queryLimit') ?? 20;
const repository = this.container.git.getRepository(current.uri);
const name = repository?.provider.name;
if (queryRequiredCommits.length > limit) {
void window.showWarningMessage(
`Unable able to show more than the first ${limit} commits for the specified time period because of ${
name ? `${name} ` : ''
}rate limits.`,
);
queryRequiredCommits = queryRequiredCommits.slice(0, 20);
}
void (await Promise.allSettled(queryRequiredCommits.map(c => c.ensureFullDetails())));
}
const name = currentUser?.name ? `${currentUser.name} (you)` : 'You';
const dataset: Commit[] = [];
for (const commit of log.commits.values()) {
const stats = commit.file?.stats;
const stats = commit.file?.stats ?? (commit.stats?.changedFiles === 1 ? commit.stats : undefined);
dataset.push({
author: commit.author.name === 'You' ? name : commit.author.name,
additions: stats?.additions ?? 0,
// changed: stats?.changes ?? 0,
deletions: stats?.deletions ?? 0,
additions: stats?.additions,
deletions: stats?.deletions,
commit: commit.sha,
date: commit.date.toISOString(),
message: commit.message ?? commit.summary,
@ -286,15 +327,19 @@ export class TimelineWebviewView extends WebviewViewBase {
if (editor == null && hasVisibleTextEditor()) return false;
if (editor != null && !isTextEditor(editor)) return false;
return this.updatePendingUri(editor?.document.uri);
}
private updatePendingUri(uri: Uri | undefined): boolean {
let etag;
if (editor != null) {
const repository = this.container.git.getRepository(editor.document.uri);
if (uri != null) {
const repository = this.container.git.getRepository(uri);
etag = repository?.etag ?? 0;
} else {
etag = 0;
}
return this.updatePendingContext({ uri: editor?.document.uri, etagRepository: etag });
return this.updatePendingContext({ uri: uri, etagRepository: etag });
}
private _notifyDidChangeStateDebounced: Deferrable<() => void> | undefined = undefined;

+ 6
- 8
src/webviews/apps/premium/timeline/chart.ts View File

@ -111,8 +111,8 @@ export class TimelineChart {
let commit: Commit;
let author: string;
let date: string;
let additions: number;
let deletions: number;
let additions: number | undefined;
let deletions: number | undefined;
// // Get the min and max additions and deletions from the dataset
// let minChanges = Infinity;
@ -178,16 +178,16 @@ export class TimelineChart {
types[author] = bubble();
group.push(author, authorX);
group.push(authorX);
}
series[x].push(date);
series['additions'].push(additions);
series['deletions'].push(deletions);
series['additions'].push(additions ?? 0);
series['deletions'].push(deletions ?? 0);
series[authorX].push(date);
const z = additions + deletions; //bubbleScale(additions + deletions);
const z = additions == null && deletions == null ? 6 : (additions ?? 0) + (deletions ?? 0); //bubbleScale(additions + deletions);
series[author].push({
y: this._indexByAuthors.get(author),
z: z,
@ -249,8 +249,6 @@ export class TimelineChart {
const config: ChartOptions = {
bindto: this._selector,
data: {
columns: [],
types: { time: bubble(), additions: bar(), deletions: bar() },
xFormat: '%Y-%m-%dT%H:%M:%S.%LZ',
xLocaltime: false,
// selection: {

Loading…
Cancel
Save