Stops parsing at maxCount (with reverse)

This commit is contained in:
Eric Amodio
2017-03-22 00:56:35 -04:00
parent 33debb6bb2
commit 823e93080e

View File

@@ -1,276 +1,279 @@
'use strict'; 'use strict';
import { Range } from 'vscode'; import { Range } from 'vscode';
import { Git, GitStatusFileStatus, GitLogCommit, GitLogType, IGitAuthor, IGitLog } from './../git'; import { Git, GitStatusFileStatus, GitLogCommit, GitLogType, IGitAuthor, IGitLog } from './../git';
// import { Logger } from '../../logger'; // import { Logger } from '../../logger';
import * as moment from 'moment'; import * as moment from 'moment';
import * as path from 'path'; import * as path from 'path';
interface ILogEntry { interface ILogEntry {
sha: string; sha: string;
author?: string; author?: string;
authorDate?: string; authorDate?: string;
// committer?: string; // committer?: string;
// committerDate?: string; // committerDate?: string;
// parentSha?: string; // parentSha?: string;
fileName?: string; fileName?: string;
originalFileName?: string; originalFileName?: string;
fileStatuses?: { status: GitStatusFileStatus, fileName: string, originalFileName: string }[]; fileStatuses?: { status: GitStatusFileStatus, fileName: string, originalFileName: string }[];
status?: GitStatusFileStatus; status?: GitStatusFileStatus;
summary?: string; summary?: string;
} }
export class GitLogParser { export class GitLogParser {
private static _parseEntries(data: string, isRepoPath: boolean): ILogEntry[] { private static _parseEntries(data: string, isRepoPath: boolean, maxCount: number | undefined, reverse: boolean): ILogEntry[] {
if (!data) return undefined; if (!data) return undefined;
const lines = data.split('\n'); const lines = data.split('\n');
if (!lines.length) return undefined; if (!lines.length) return undefined;
const entries: ILogEntry[] = []; const entries: ILogEntry[] = [];
let entry: ILogEntry; let entry: ILogEntry;
let position = -1; let position = -1;
while (++position < lines.length) { while (++position < lines.length) {
let lineParts = lines[position].split(' '); // Since log --reverse doesn't properly honor a max count -- enforce it here
if (lineParts.length < 2) { if (reverse && maxCount && (entries.length >= maxCount)) break;
continue;
} let lineParts = lines[position].split(' ');
if (lineParts.length < 2) {
if (!entry) { continue;
if (!Git.shaRegex.test(lineParts[0])) continue; }
entry = { if (!entry) {
sha: lineParts[0] if (!Git.shaRegex.test(lineParts[0])) continue;
};
entry = {
continue; sha: lineParts[0]
} };
switch (lineParts[0]) { continue;
case 'author': }
entry.author = Git.isUncommitted(entry.sha)
? 'Uncommitted' switch (lineParts[0]) {
: lineParts.slice(1).join(' ').trim(); case 'author':
break; entry.author = Git.isUncommitted(entry.sha)
? 'Uncommitted'
case 'author-date': : lineParts.slice(1).join(' ').trim();
entry.authorDate = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`; break;
break;
case 'author-date':
// case 'committer': entry.authorDate = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
// entry.committer = lineParts.slice(1).join(' ').trim(); break;
// break;
// case 'committer':
// case 'committer-date': // entry.committer = lineParts.slice(1).join(' ').trim();
// entry.committerDate = lineParts.slice(1).join(' ').trim(); // break;
// break;
// case 'committer-date':
// case 'parent': // entry.committerDate = lineParts.slice(1).join(' ').trim();
// entry.parentSha = lineParts.slice(1).join(' ').trim(); // break;
// break;
// case 'parent':
case 'summary': // entry.parentSha = lineParts.slice(1).join(' ').trim();
entry.summary = lineParts.slice(1).join(' ').trim(); // break;
while (++position < lines.length) {
if (!lines[position]) break; case 'summary':
entry.summary += `\n${lines[position]}`; entry.summary = lineParts.slice(1).join(' ').trim();
} while (++position < lines.length) {
break; if (!lines[position]) break;
entry.summary += `\n${lines[position]}`;
case 'filename': }
if (isRepoPath) { break;
case 'filename':
if (isRepoPath) {
const nextLine = lines[position + 1]; const nextLine = lines[position + 1];
// If the next line isn't blank, make sure it isn't starting a new commit // If the next line isn't blank, make sure it isn't starting a new commit
if (nextLine && Git.shaRegex.test(nextLine)) continue; if (nextLine && Git.shaRegex.test(nextLine)) continue;
position++; position++;
let diff = false; let diff = false;
while (++position < lines.length) { while (++position < lines.length) {
lineParts = lines[position].split(' '); lineParts = lines[position].split(' ');
if (Git.shaRegex.test(lineParts[0])) { if (Git.shaRegex.test(lineParts[0])) {
position--; position--;
break; break;
} }
if (diff) continue; if (diff) continue;
if (lineParts[0] === 'diff') { if (lineParts[0] === 'diff') {
diff = true; diff = true;
entry.fileName = lineParts[2].substring(2); entry.fileName = lineParts[2].substring(2);
const originalFileName = lineParts[3].substring(2); const originalFileName = lineParts[3].substring(2);
if (entry.fileName !== originalFileName) { if (entry.fileName !== originalFileName) {
entry.originalFileName = originalFileName; entry.originalFileName = originalFileName;
} }
continue; continue;
} }
if (entry.fileStatuses == null) { if (entry.fileStatuses == null) {
entry.fileStatuses = []; entry.fileStatuses = [];
} }
const status = { const status = {
status: lineParts[0][0] as GitStatusFileStatus, status: lineParts[0][0] as GitStatusFileStatus,
fileName: lineParts[0].substring(1), fileName: lineParts[0].substring(1),
originalFileName: undefined as string originalFileName: undefined as string
}; };
const index = status.fileName.indexOf('\t') + 1; const index = status.fileName.indexOf('\t') + 1;
if (index) { if (index) {
const next = status.fileName.indexOf('\t', index) + 1; const next = status.fileName.indexOf('\t', index) + 1;
if (next) { if (next) {
status.originalFileName = status.fileName.substring(index, next - 1); status.originalFileName = status.fileName.substring(index, next - 1);
status.fileName = status.fileName.substring(next); status.fileName = status.fileName.substring(next);
} }
else { else {
status.fileName = status.fileName.substring(index); status.fileName = status.fileName.substring(index);
} }
} }
entry.fileStatuses.push(status); entry.fileStatuses.push(status);
} }
if (entry.fileStatuses) { if (entry.fileStatuses) {
entry.fileName = entry.fileStatuses.filter(_ => !!_.fileName).map(_ => _.fileName).join(', '); entry.fileName = entry.fileStatuses.filter(_ => !!_.fileName).map(_ => _.fileName).join(', ');
} }
} }
else { else {
position += 2; position += 2;
lineParts = lines[position].split(' '); lineParts = lines[position].split(' ');
if (lineParts.length === 1) { if (lineParts.length === 1) {
entry.status = lineParts[0][0] as GitStatusFileStatus; entry.status = lineParts[0][0] as GitStatusFileStatus;
entry.fileName = lineParts[0].substring(1); entry.fileName = lineParts[0].substring(1);
} }
else { else {
entry.status = lineParts[3][0] as GitStatusFileStatus; entry.status = lineParts[3][0] as GitStatusFileStatus;
entry.fileName = lineParts[0].substring(1); entry.fileName = lineParts[0].substring(1);
position += 4; position += 4;
} }
const index = entry.fileName.indexOf('\t') + 1; const index = entry.fileName.indexOf('\t') + 1;
if (index) { if (index) {
const next = entry.fileName.indexOf('\t', index) + 1; const next = entry.fileName.indexOf('\t', index) + 1;
if (next) { if (next) {
entry.originalFileName = entry.fileName.substring(index, next - 1); entry.originalFileName = entry.fileName.substring(index, next - 1);
entry.fileName = entry.fileName.substring(next); entry.fileName = entry.fileName.substring(next);
} }
else { else {
entry.fileName = entry.fileName.substring(index); entry.fileName = entry.fileName.substring(index);
} }
} }
} }
entries.push(entry); entries.push(entry);
entry = undefined; entry = undefined;
break; break;
default: default:
break; break;
} }
} }
return entries; return entries;
} }
static parse(data: string, type: GitLogType, fileNameOrRepoPath: string, maxCount: number | undefined, isRepoPath: boolean, reverse: boolean, range: Range): IGitLog { static parse(data: string, type: GitLogType, fileNameOrRepoPath: string, maxCount: number | undefined, isRepoPath: boolean, reverse: boolean, range: Range): IGitLog {
const entries = this._parseEntries(data, isRepoPath); const entries = this._parseEntries(data, isRepoPath, maxCount, reverse);
if (!entries) return undefined; if (!entries) return undefined;
const authors: Map<string, IGitAuthor> = new Map(); const authors: Map<string, IGitAuthor> = new Map();
const commits: Map<string, GitLogCommit> = new Map(); const commits: Map<string, GitLogCommit> = new Map();
let repoPath: string; let repoPath: string;
let relativeFileName: string; let relativeFileName: string;
let recentCommit: GitLogCommit; let recentCommit: GitLogCommit;
if (isRepoPath) { if (isRepoPath) {
repoPath = Git.normalizePath(fileNameOrRepoPath); repoPath = Git.normalizePath(fileNameOrRepoPath);
} }
for (let i = 0, len = entries.length; i < len; i++) { for (let i = 0, len = entries.length; i < len; i++) {
// Since log --reverse doesn't properly honor a max count -- enforce it here // Since log --reverse doesn't properly honor a max count -- enforce it here
if (reverse && i >= maxCount) break; if (reverse && maxCount && (i >= maxCount)) break;
const entry = entries[i]; const entry = entries[i];
if (i === 0 || isRepoPath) { if (i === 0 || isRepoPath) {
if (isRepoPath) { if (isRepoPath) {
relativeFileName = entry.fileName; relativeFileName = entry.fileName;
} }
else { else {
// Try to get the repoPath from the most recent commit // Try to get the repoPath from the most recent commit
repoPath = Git.normalizePath(fileNameOrRepoPath.replace(fileNameOrRepoPath.startsWith('/') ? `/${entry.fileName}` : entry.fileName, '')); repoPath = Git.normalizePath(fileNameOrRepoPath.replace(fileNameOrRepoPath.startsWith('/') ? `/${entry.fileName}` : entry.fileName, ''));
relativeFileName = path.relative(repoPath, fileNameOrRepoPath).replace(/\\/g, '/'); relativeFileName = path.relative(repoPath, fileNameOrRepoPath).replace(/\\/g, '/');
} }
} }
let commit = commits.get(entry.sha); let commit = commits.get(entry.sha);
if (!commit) { if (!commit) {
let author = authors.get(entry.author); let author = authors.get(entry.author);
if (!author) { if (!author) {
author = { author = {
name: entry.author, name: entry.author,
lineCount: 0 lineCount: 0
}; };
authors.set(entry.author, author); authors.set(entry.author, author);
} }
commit = new GitLogCommit(type, repoPath, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary, entry.status, entry.fileStatuses, undefined, entry.originalFileName); commit = new GitLogCommit(type, repoPath, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary, entry.status, entry.fileStatuses, undefined, entry.originalFileName);
if (relativeFileName !== entry.fileName) { if (relativeFileName !== entry.fileName) {
commit.originalFileName = entry.fileName; commit.originalFileName = entry.fileName;
} }
commits.set(entry.sha, commit); commits.set(entry.sha, commit);
} }
// else { // else {
// Logger.log(`merge commit? ${entry.sha}`); // Logger.log(`merge commit? ${entry.sha}`);
// } // }
if (recentCommit) { if (recentCommit) {
recentCommit.previousSha = commit.sha; recentCommit.previousSha = commit.sha;
// If the commit sha's match (merge commit), just forward it along // If the commit sha's match (merge commit), just forward it along
commit.nextSha = commit.sha !== recentCommit.sha ? recentCommit.sha : recentCommit.nextSha; commit.nextSha = commit.sha !== recentCommit.sha ? recentCommit.sha : recentCommit.nextSha;
// Only add a filename if this is a file log // Only add a filename if this is a file log
if (type === 'file') { if (type === 'file') {
recentCommit.previousFileName = commit.originalFileName || commit.fileName; recentCommit.previousFileName = commit.originalFileName || commit.fileName;
commit.nextFileName = recentCommit.originalFileName || recentCommit.fileName; commit.nextFileName = recentCommit.originalFileName || recentCommit.fileName;
} }
} }
recentCommit = commit; recentCommit = commit;
} }
commits.forEach(c => authors.get(c.author).lineCount += c.lines.length); commits.forEach(c => authors.get(c.author).lineCount += c.lines.length);
const sortedAuthors: Map<string, IGitAuthor> = new Map(); const sortedAuthors: Map<string, IGitAuthor> = new Map();
// const values = // const values =
Array.from(authors.values()) Array.from(authors.values())
.sort((a, b) => b.lineCount - a.lineCount) .sort((a, b) => b.lineCount - a.lineCount)
.forEach(a => sortedAuthors.set(a.name, a)); .forEach(a => sortedAuthors.set(a.name, a));
// const sortedCommits: Map<string, IGitCommit> = new Map(); // const sortedCommits: Map<string, IGitCommit> = new Map();
// Array.from(commits.values()) // Array.from(commits.values())
// .sort((a, b) => b.date.getTime() - a.date.getTime()) // .sort((a, b) => b.date.getTime() - a.date.getTime())
// .forEach(c => sortedCommits.set(c.sha, c)); // .forEach(c => sortedCommits.set(c.sha, c));
return { return {
repoPath: repoPath, repoPath: repoPath,
authors: sortedAuthors, authors: sortedAuthors,
// commits: sortedCommits, // commits: sortedCommits,
commits: commits, commits: commits,
maxCount: maxCount, maxCount: maxCount,
range: range, range: range,
truncated: !!(maxCount && entries.length >= maxCount) truncated: !!(maxCount && entries.length >= maxCount)
} as IGitLog; } as IGitLog;
} }
} }