Browse Source

Adds ability to include stats on graph git call

main
Eric Amodio 2 years ago
parent
commit
d2c0d43daa
7 changed files with 135 additions and 86 deletions
  1. +6
    -2
      src/env/node/git/git.ts
  2. +17
    -61
      src/env/node/git/localGitProvider.ts
  3. +1
    -1
      src/git/gitProvider.ts
  4. +1
    -1
      src/git/gitProviderService.ts
  5. +7
    -0
      src/git/models/graph.ts
  6. +102
    -20
      src/git/parsers/logParser.ts
  7. +1
    -1
      src/plus/github/githubGitProvider.ts

+ 6
- 2
src/env/node/git/git.ts View File

@ -907,7 +907,7 @@ export class Git {
'--',
);
const shaRegex = new RegExp(`(?:^|\x00\x00)${sha}\x00`);
const shaRegex = getShaInLogRegex(sha);
let found = false;
let count = 0;
@ -935,7 +935,7 @@ export class Git {
function onData(s: string) {
data.push(s);
// eslint-disable-next-line no-control-regex
count += s.match(/(?:^|\x00\x00)[0-9a-f]{40}\x00/g)?.length ?? 0;
count += s.match(/(?:^\x00*|\x00\x00)[0-9a-f]{40}\x00/g)?.length ?? 0;
if (!found && shaRegex.test(s)) {
found = true;
@ -1829,3 +1829,7 @@ export class Git {
}
}
}
export function getShaInLogRegex(sha: string) {
return new RegExp(`(?:^\x00*|\x00\x00)${sha}\x00`);
}

+ 17
- 61
src/env/node/git/localGitProvider.ts View File

@ -92,9 +92,9 @@ import { GitBlameParser } from '../../../git/parsers/blameParser';
import { GitBranchParser } from '../../../git/parsers/branchParser';
import { GitDiffParser } from '../../../git/parsers/diffParser';
import {
createLogParser,
createLogParserSingle,
createLogParserWithFiles,
getContributorsParser,
getGraphParser,
getRefAndDateParser,
getRefParser,
@ -155,7 +155,13 @@ import { serializeWebviewItemContext } from '../../../system/webview';
import type { CachedBlame, CachedDiff, CachedLog, TrackedDocument } from '../../../trackers/gitDocumentTracker';
import { GitDocumentState } from '../../../trackers/gitDocumentTracker';
import type { Git } from './git';
import { GitErrors, gitLogDefaultConfigs, gitLogDefaultConfigsWithFiles, maxGitCliLength } from './git';
import {
getShaInLogRegex,
GitErrors,
gitLogDefaultConfigs,
gitLogDefaultConfigsWithFiles,
maxGitCliLength,
} from './git';
import type { GitLocation } from './locator';
import { findGitPath, InvalidGitConfigError, UnableToFindGitError } from './locator';
import { CancelledRunError, fsExists, RunError } from './shell';
@ -1629,12 +1635,12 @@ export class LocalGitProvider implements GitProvider, Disposable {
asWebviewUri: (uri: Uri) => Uri,
options?: {
branch?: string;
include?: { stats?: boolean };
limit?: number;
mode?: 'single' | 'local' | 'all';
ref?: string;
},
): Promise<GitGraph> {
const parser = getGraphParser();
const parser = getGraphParser(options?.include?.stats);
const refParser = getRefParser();
const defaultLimit = options?.limit ?? configuration.get('graph.defaultItemLimit') ?? 5000;
@ -1710,10 +1716,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
data = await this.git.log2(repoPath, stdin ? { stdin: stdin } : undefined, ...args);
if (cursor) {
const cursorIndex = data.startsWith(`${cursor.sha}\x00`)
? 0
: data.indexOf(`\x00\x00${cursor.sha}\x00`);
if (cursorIndex === -1) {
if (!getShaInLogRegex(cursor.sha).test(data)) {
// If we didn't find any new commits, we must have them all so return that we have everything
if (size === data.length) {
return {
@ -1732,32 +1735,19 @@ export class LocalGitProvider implements GitProvider, Disposable {
continue;
}
// if (cursorIndex > 0 && cursor != null) {
// const duplicates = data.substring(0, cursorIndex);
// if (data.length - duplicates.length < (size ?? data.length) / 4) {
// size = data.length;
// nextPageLimit = (nextPageLimit === 0 ? defaultPageLimit : nextPageLimit) * 2;
// continue;
// }
// // Substract out any duplicate commits (regex is faster than parsing and counting)
// nextPageLimit -= (duplicates.match(/\0\0[0-9a-f]{40}\0/g)?.length ?? 0) + 1;
// data = data.substring(cursorIndex + 2);
// }
}
}
if (!data)
{return {
if (!data) {
return {
repoPath: repoPath,
avatars: avatars,
ids: ids,
branches: branchMap,
remotes: remoteMap,
rows: [],
};}
};
}
log = data;
if (limit !== 0) {
@ -2053,6 +2043,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
remotes: refRemoteHeads,
tags: refTags,
contexts: contexts,
stats: commit.stats,
});
}
@ -2121,42 +2112,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
try {
repoPath = normalizePath(repoPath);
const currentUser = await this.getCurrentUser(repoPath);
const parser = createLogParser<{
sha: string;
author: string;
email: string;
date: string;
stats?: { files: number; additions: number; deletions: number };
}>(
{
sha: '%H',
author: '%aN',
email: '%aE',
date: '%at',
},
options?.stats
? {
additionalArgs: ['--shortstat', '--use-mailmap'],
parseEntry: (fields, entry) => {
const line = fields.next().value;
const match = GitLogParser.shortstatRegex.exec(line);
if (match?.groups != null) {
const { files, additions, deletions } = match.groups;
entry.stats = {
files: Number(files || 0),
additions: Number(additions || 0),
deletions: Number(deletions || 0),
};
}
return entry;
},
prefix: '%x00',
fieldSuffix: '%x00',
skip: 1,
}
: undefined,
);
const parser = getContributorsParser(options?.stats);
const data = await this.git.log(repoPath, options?.ref, {
all: options?.all,

+ 1
- 1
src/git/gitProvider.ts View File

@ -224,8 +224,8 @@ export interface GitProvider extends Disposable {
asWebviewUri: (uri: Uri) => Uri,
options?: {
branch?: string;
include?: { stats?: boolean };
limit?: number;
mode?: 'single' | 'local' | 'all';
ref?: string;
},
): Promise<GitGraph>;

+ 1
- 1
src/git/gitProviderService.ts View File

@ -1410,8 +1410,8 @@ export class GitProviderService implements Disposable {
asWebviewUri: (uri: Uri) => Uri,
options?: {
branch?: string;
include?: { stats?: boolean };
limit?: number;
mode?: 'single' | 'local' | 'all';
ref?: string;
},
): Promise<GitGraph> {

+ 7
- 0
src/git/models/graph.ts View File

@ -15,12 +15,19 @@ export const enum GitGraphRowType {
Rebase = 'unsupported-rebase-warning-node',
}
export interface GitGraphRowStats {
files: number;
additions: number;
deletions: number;
}
export interface GitGraphRow extends GraphRow {
type: GitGraphRowType;
heads?: GitGraphRowHead[];
remotes?: GitGraphRowRemoteHead[];
tags?: GitGraphRowTag[];
contexts?: GitGraphRowContexts;
stats?: GitGraphRowStats;
}
export interface GitGraph {

+ 102
- 20
src/git/parsers/logParser.ts View File

@ -25,6 +25,9 @@ const logFileSimpleRegex = /^ (.*)\s*(?:(?:diff --git a\/(.*) b\/(.*))|(?:(\S
const logFileSimpleRenamedRegex = /^<r> (\S+)\s*(.*)$/s;
const logFileSimpleRenamedFilesRegex = /^(\S)\S*\t([^\t\n]+)(?:\t(.+)?)?$/gm;
const shortstatRegex =
/(?<files>\d+) files? changed(?:, (?<additions>\d+) insertions?\(\+\))?(?:, (?<deletions>\d+) deletions?\(-\))?/;
// Using %x00 codes because some shells seem to try to expand things if not
const lb = '%x3c'; // `%x${'<'.charCodeAt(0).toString(16)}`;
const rb = '%x3e'; // `%x${'>'.charCodeAt(0).toString(16)}`;
@ -71,14 +74,48 @@ export type Parser = {
parse: (data: string | string[]) => Generator<T>;
};
type ParsedEntryFile = { status: string; path: string; originalPath?: string };
type ParsedEntryWithFiles<T> = { [K in keyof T]: string } & { files: ParsedEntryFile[] };
type ParserWithFiles<T> = {
arguments: string[];
parse: (data: string) => Generator<ParsedEntryWithFiles<T>>;
};
export type ParsedEntryFile = { status: string; path: string; originalPath?: string };
export type ParsedEntryWithFiles<T> = { [K in keyof T]: string } & { files: ParsedEntryFile[] };
export type ParserWithFiles<T> = Parser<ParsedEntryWithFiles<T>>;
export type ParsedStats = { files: number; additions: number; deletions: number };
export type ParsedEntryWithStats<T> = T & { stats?: ParsedStats };
export type ParserWithStats<T> = Parser<ParsedEntryWithStats<T>>;
type GraphParser = Parser<{
type ContributorsParserMaybeWithStats = ParserWithStats<{
sha: string;
author: string;
email: string;
date: string;
}>;
let _contributorsParser: ContributorsParserMaybeWithStats | undefined;
let _contributorsParserWithStats: ContributorsParserMaybeWithStats | undefined;
export function getContributorsParser(stats?: boolean): ContributorsParserMaybeWithStats {
if (stats) {
if (_contributorsParserWithStats == null) {
_contributorsParserWithStats = createLogParserWithStats({
sha: '%H',
author: '%aN',
email: '%aE',
date: '%at',
});
}
return _contributorsParserWithStats;
}
if (_contributorsParser == null) {
_contributorsParser = createLogParser({
sha: '%H',
author: '%aN',
email: '%aE',
date: '%at',
});
}
return _contributorsParser;
}
type GraphParserMaybeWithStats = ParserWithStats<{
sha: string;
author: string;
authorEmail: string;
@ -89,8 +126,26 @@ type GraphParser = Parser<{
message: string;
}>;
let _graphParser: GraphParser | undefined;
export function getGraphParser(): GraphParser {
let _graphParser: GraphParserMaybeWithStats | undefined;
let _graphParserWithStats: GraphParserMaybeWithStats | undefined;
export function getGraphParser(stats?: boolean): GraphParserMaybeWithStats {
if (stats) {
if (_graphParserWithStats == null) {
_graphParserWithStats = createLogParserWithStats({
sha: '%H',
author: '%aN',
authorEmail: '%aE',
authorDate: '%at',
committerDate: '%ct',
parents: '%P',
tips: '%D',
message: '%B',
});
}
return _graphParserWithStats;
}
if (_graphParser == null) {
_graphParser = createLogParser({
sha: '%H',
@ -130,18 +185,21 @@ export function getRefAndDateParser(): RefAndDateParser {
return _refAndDateParser;
}
export function createLogParser<T extends Record<string, unknown>>(
export function createLogParser<
T extends Record<string, unknown>,
TAdditional extends Record<string, unknown> = Record<string, unknown>,
>(
fieldMapping: ExtractAll<T, string>,
options?: {
additionalArgs?: string[];
parseEntry?: (fields: IterableIterator<string>, entry: T) => void;
parseEntry?: (fields: IterableIterator<string>, entry: T & TAdditional) => void;
prefix?: string;
fieldPrefix?: string;
fieldSuffix?: string;
separator?: string;
skip?: number;
},
): Parser<T> {
): Parser<T & TAdditional> {
let format = options?.prefix ?? '';
const keys: (keyof ExtractAll<T, string>)[] = [];
for (const key in fieldMapping) {
@ -156,15 +214,15 @@ export function createLogParser>(
args.push(...options.additionalArgs);
}
function* parse(data: string | string[]): Generator<T> {
let entry: T = {} as any;
function* parse(data: string | string[]): Generator<T & TAdditional> {
let entry: T & TAdditional = {} as any;
let fieldCount = 0;
let field;
const fields = getLines(data, options?.separator ?? '\0');
if (options?.skip) {
for (let i = 0; i < options.skip; i++) {
fields.next();
field = fields.next();
}
}
@ -172,7 +230,7 @@ export function createLogParser>(
field = fields.next();
if (field.done) break;
entry[keys[fieldCount++]] = field.value as T[keyof T];
entry[keys[fieldCount++]] = field.value as (T & TAdditional)[keyof T];
if (fieldCount === keys.length) {
fieldCount = 0;
@ -220,7 +278,7 @@ export function createLogParserWithFiles>(
const args = ['-z', `--format=${format}`, '--name-status'];
function* parse(data: string): Generator<ParsedEntryWithFiles<T>> {
function* parse(data: string | string[]): Generator<ParsedEntryWithFiles<T>> {
const records = getLines(data, '\0\0\0');
let entry: ParsedEntryWithFiles<T>;
@ -266,11 +324,35 @@ export function createLogParserWithFiles>(
return { arguments: args, parse: parse };
}
export function createLogParserWithStats<T extends Record<string, unknown>>(
fieldMapping: ExtractAll<T, string>,
): ParserWithStats<T> {
function parseStats(fields: IterableIterator<string>, entry: ParsedEntryWithStats<T>) {
const stats = fields.next().value;
const match = shortstatRegex.exec(stats);
if (match?.groups != null) {
entry.stats = {
files: Number(match.groups.files || 0),
additions: Number(match.groups.additions || 0),
deletions: Number(match.groups.deletions || 0),
};
}
fields.next();
return entry;
}
return createLogParser<T, ParsedEntryWithStats<T>>(fieldMapping, {
additionalArgs: ['--shortstat'],
parseEntry: parseStats,
prefix: '%x00%x00',
separator: '\0',
fieldSuffix: '%x00',
skip: 2,
});
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class GitLogParser {
static readonly shortstatRegex =
/(?<files>\d+) files? changed(?:, (?<additions>\d+) insertions?\(\+\))?(?:, (?<deletions>\d+) deletions?\(-\))?/;
// private static _defaultParser: ParserWithFiles<{
// sha: string;
// author: string;

+ 1
- 1
src/plus/github/githubGitProvider.ts View File

@ -1072,8 +1072,8 @@ export class GitHubGitProvider implements GitProvider, Disposable {
asWebviewUri: (uri: Uri) => Uri,
options?: {
branch?: string;
include?: { stats?: boolean };
limit?: number;
mode?: 'single' | 'local' | 'all';
ref?: string;
},
): Promise<GitGraph> {

Loading…
Cancel
Save