Skip to content

Commit b67c022

Browse files
committed
Improves rename detection when getting stats
1 parent 207c77c commit b67c022

File tree

5 files changed

+164
-72
lines changed

5 files changed

+164
-72
lines changed

src/git/parsers/blameParser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Container } from '../../container';
22
import { maybeStopWatch } from '../../system/stopwatch';
3-
import { getLines } from '../../system/string';
3+
import { iterateByDelimiter } from '../../system/string';
44
import type { GitBlame, GitBlameAuthor } from '../models/blame';
55
import type { GitCommitLine } from '../models/commit';
66
import { GitCommit, GitCommitIdentity } from '../models/commit';
@@ -55,7 +55,7 @@ export function parseGitBlame(
5555
let line: string;
5656
let lineParts: string[];
5757

58-
for (line of getLines(data)) {
58+
for (line of iterateByDelimiter(data)) {
5959
lineParts = line.split(' ');
6060
if (lineParts.length < 2) continue;
6161

src/git/parsers/logParser.ts

Lines changed: 76 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import type { Range } from 'vscode';
33
import type { Container } from '../../container';
44
import { relative } from '../../system/-webview/path';
55
import { filterMap } from '../../system/array';
6-
import { normalizePath } from '../../system/path';
6+
import { joinPaths, normalizePath } from '../../system/path';
77
import { maybeStopWatch } from '../../system/stopwatch';
8-
import { getLines } from '../../system/string';
8+
import { iterateByDelimiter, iterateByDelimiters } from '../../system/string';
99
import type { GitCommitLine, GitStashCommit } from '../models/commit';
1010
import { GitCommit, GitCommitIdentity } from '../models/commit';
1111
import type { GitFile } from '../models/file';
@@ -231,7 +231,7 @@ export function createLogParser<
231231
let fieldCount = 0;
232232
let field;
233233

234-
const fields = getLines(data, options?.separator ?? '\0');
234+
const fields = iterateByDelimiters(data, options?.separator ?? '\0');
235235
if (options?.skip) {
236236
for (let i = 0; i < options.skip; i++) {
237237
field = fields.next();
@@ -266,7 +266,7 @@ export function createLogParserSingle(field: string): Parser<string> {
266266
function* parse(data: string | string[]): Generator<string> {
267267
let field;
268268

269-
const fields = getLines(data, '\0');
269+
const fields = iterateByDelimiters(data, '\0');
270270
while (true) {
271271
field = fields.next();
272272
if (field.done) break;
@@ -295,7 +295,7 @@ export function createLogParserWithFiles<T extends Record<string, unknown>>(
295295
}
296296

297297
function* parse(data: string | string[]): Generator<ParsedEntryWithFiles<T>> {
298-
const records = getLines(data, '\0\0\0');
298+
const records = iterateByDelimiters(data, '\0\0\0');
299299

300300
let entry: ParsedEntryWithFiles<T>;
301301
let files: ParsedEntryFile[];
@@ -304,7 +304,7 @@ export function createLogParserWithFiles<T extends Record<string, unknown>>(
304304
for (const record of records) {
305305
entry = {} as any;
306306
files = [];
307-
fields = getLines(record, '\0');
307+
fields = iterateByDelimiter(record, '\0');
308308

309309
if (fieldMapping != null) {
310310
// Skip the 2 starting NULs
@@ -353,13 +353,13 @@ export function createLogParserWithFilesAndStats<T extends Record<string, unknow
353353
keys.push(key);
354354
format += `%x00${fieldMapping[key]}`;
355355
}
356-
args = ['-z', `--format=${format}`, '--numstat'];
356+
args = ['-z', `--format=${format}`, '--numstat', '--summary'];
357357
} else {
358-
args = ['-z', '--numstat'];
358+
args = ['-z', '--numstat', '--summary'];
359359
}
360360

361361
function* parse(data: string | string[]): Generator<ParsedEntryWithFilesAndStats<T>> {
362-
const records = getLines(data, '\0\0\0');
362+
const records = iterateByDelimiters(data, '\0\0\0', '\n\0\0');
363363

364364
let entry: ParsedEntryWithFilesAndStats<T>;
365365
let files: ParsedEntryFileWithStats[];
@@ -368,7 +368,7 @@ export function createLogParserWithFilesAndStats<T extends Record<string, unknow
368368
for (const record of records) {
369369
entry = {} as unknown as ParsedEntryWithFilesAndStats<T>;
370370
files = [];
371-
fields = getLines(record, '\0');
371+
fields = iterateByDelimiter(record, '\0');
372372

373373
if (fieldMapping != null) {
374374
// Skip the 2 starting NULs
@@ -385,6 +385,70 @@ export function createLogParserWithFilesAndStats<T extends Record<string, unknow
385385
if (fieldCount < keys.length) {
386386
entry[keys[fieldCount++]] = field.value as ParsedEntryWithFilesAndStats<T>[keyof T];
387387
} else {
388+
if (!field.value) continue;
389+
if (!field.value.includes('\t')) {
390+
let match;
391+
let rename;
392+
let renamePrefix;
393+
let renameBefore;
394+
let renameAfter;
395+
let renameSuffix;
396+
let createOrDelete;
397+
let createOrDeletePath;
398+
399+
for (let line of field.value.split('\n')) {
400+
line = line.trim();
401+
if (!line) continue;
402+
403+
match =
404+
/(rename) (.*?)\{?([^{]+)\s+=>\s+([^}]+)\}?(.*?)?(?: \(\d+%\))|(create|delete) mode \d+ (.+)/.exec(
405+
line,
406+
);
407+
if (match == null) continue;
408+
409+
[
410+
,
411+
rename,
412+
renamePrefix,
413+
renameBefore,
414+
renameAfter,
415+
renameSuffix,
416+
createOrDelete,
417+
createOrDeletePath,
418+
] = match;
419+
420+
let summaryPath;
421+
let summaryOriginalPath;
422+
let summaryStatus;
423+
424+
if (rename != null) {
425+
summaryPath = normalizePath(joinPaths(renamePrefix, renameAfter, renameSuffix ?? ''));
426+
summaryOriginalPath = normalizePath(
427+
joinPaths(renamePrefix, renameBefore, renameSuffix ?? ''),
428+
);
429+
summaryStatus = 'R';
430+
} else {
431+
summaryPath = normalizePath(createOrDeletePath);
432+
summaryStatus = createOrDelete === 'create' ? 'A' : 'D';
433+
}
434+
435+
const file = files.find(f => f.path === summaryPath);
436+
if (file == null) {
437+
debugger;
438+
continue;
439+
}
440+
441+
if (file.status !== summaryStatus) {
442+
file.status = summaryStatus;
443+
if (summaryOriginalPath != null) {
444+
file.originalPath = summaryOriginalPath;
445+
}
446+
}
447+
}
448+
449+
break;
450+
}
451+
388452
let [additions, deletions, path] = field.value.split('\t');
389453
additions = additions.trim();
390454
deletions = deletions.trim();
@@ -409,19 +473,8 @@ export function createLogParserWithFilesAndStats<T extends Record<string, unknow
409473
}
410474
}
411475

412-
// Skip binary files which show as - for both additions and deletions
413-
if (additions === '-' && deletions === '-') continue;
414-
415476
const file: ParsedEntryFileWithStats = {
416-
status:
417-
status ??
418-
(additions === '0' && deletions === '0'
419-
? 'M'
420-
: additions === '0'
421-
? 'D'
422-
: deletions === '0'
423-
? 'A'
424-
: 'M'),
477+
status: status ?? 'M',
425478
path: path,
426479
originalPath: originalPath,
427480
additions: additions === '-' ? 0 : parseInt(additions, 10),
@@ -530,7 +583,7 @@ export function parseGitLog(
530583
let i = 0;
531584
let first = true;
532585

533-
const lines = getLines(`${data}</f>`);
586+
const lines = iterateByDelimiter(`${data}</f>`);
534587
// Skip the first line since it will always be </f>
535588
let next = lines.next();
536589
if (next.done) return undefined;

src/git/parsers/mergeTreeParser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { map } from '../../system/iterable';
2-
import { getLines } from '../../system/string';
2+
import { iterateByDelimiter } from '../../system/string';
33
import type { MergeConflictFile } from '../models/mergeConflict';
44

55
export interface GitMergeConflict {
@@ -8,7 +8,7 @@ export interface GitMergeConflict {
88
}
99

1010
export function parseMergeTreeConflict(data: string): GitMergeConflict {
11-
const lines = getLines(data, '\0');
11+
const lines = iterateByDelimiter(data, '\0');
1212
const treeOid = lines.next();
1313
if (treeOid.done) return { treeOid: treeOid.value, conflicts: [] };
1414

src/git/parsers/worktreeParser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Uri } from 'vscode';
22
import type { Container } from '../../container';
33
import { normalizePath } from '../../system/path';
44
import { maybeStopWatch } from '../../system/stopwatch';
5-
import { getLines } from '../../system/string';
5+
import { iterateByDelimiter } from '../../system/string';
66
import type { GitBranch } from '../models/branch';
77
import { GitWorktree } from '../models/worktree';
88

@@ -40,7 +40,7 @@ export function parseGitWorktrees(
4040
let prunable: string;
4141
let main = true; // the first worktree is the main worktree
4242

43-
for (line of getLines(data)) {
43+
for (line of iterateByDelimiter(data)) {
4444
index = line.indexOf(' ');
4545
if (index === -1) {
4646
key = line;

src/system/string.ts

Lines changed: 82 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -140,49 +140,6 @@ export function getDurationMilliseconds(start: [number, number]): number {
140140
return secs * 1000 + Math.floor(nanosecs / 1000000);
141141
}
142142

143-
export function* getLines(data: string | string[], char: string = '\n'): IterableIterator<string> {
144-
if (typeof data === 'string') {
145-
let i = 0;
146-
while (i < data.length) {
147-
let j = data.indexOf(char, i);
148-
if (j === -1) {
149-
j = data.length;
150-
}
151-
152-
yield data.substring(i, j);
153-
i = j + 1;
154-
}
155-
156-
return;
157-
}
158-
159-
let count = 0;
160-
let leftover: string | undefined;
161-
for (let s of data) {
162-
count++;
163-
if (leftover) {
164-
s = leftover + s;
165-
leftover = undefined;
166-
}
167-
168-
let i = 0;
169-
while (i < s.length) {
170-
let j = s.indexOf(char, i);
171-
if (j === -1) {
172-
if (count === data.length) {
173-
j = s.length;
174-
} else {
175-
leftover = s.substring(i);
176-
break;
177-
}
178-
}
179-
180-
yield s.substring(i, j);
181-
i = j + 1;
182-
}
183-
}
184-
}
185-
186143
/**
187144
* Distributes a value into one of 100 groups based on a hash of the value
188145
* @param value The value to distribute (e.g., machine ID)
@@ -429,6 +386,88 @@ export function getTokensFromTemplateRegex(template: string): TokenMatch[] {
429386
return tokens;
430387
}
431388

389+
export function* iterateByDelimiter(data: string, delimiter: string = '\n'): IterableIterator<string> {
390+
let i = 0;
391+
while (i < data.length) {
392+
let j = data.indexOf(delimiter, i);
393+
if (j === -1) {
394+
j = data.length;
395+
}
396+
397+
yield data.substring(i, j);
398+
i = j + 1;
399+
}
400+
}
401+
402+
export function* iterateByDelimiters(
403+
data: string | string[],
404+
primaryDelimiter: string,
405+
secondaryDelimiter?: string,
406+
): IterableIterator<string> {
407+
if (typeof data === 'string') {
408+
let i = 0;
409+
let primaryIndex;
410+
let secondaryIndex;
411+
while (i < data.length) {
412+
let j = data.length;
413+
414+
primaryIndex = data.indexOf(primaryDelimiter, i);
415+
if (primaryIndex !== -1) {
416+
j = primaryIndex;
417+
}
418+
419+
if (secondaryDelimiter != null) {
420+
secondaryIndex = data.indexOf(secondaryDelimiter, i);
421+
if (secondaryIndex !== -1 && secondaryIndex < j) {
422+
j = secondaryIndex;
423+
}
424+
}
425+
426+
yield data.substring(i, j);
427+
i = j + 1;
428+
}
429+
430+
return;
431+
}
432+
433+
let count = 0;
434+
let leftover: string | undefined;
435+
for (let s of data) {
436+
count++;
437+
if (leftover) {
438+
s = leftover + s;
439+
leftover = undefined;
440+
}
441+
442+
let i = 0;
443+
let primaryIndex;
444+
let secondaryIndex;
445+
while (i < s.length) {
446+
let j = s.length;
447+
448+
primaryIndex = s.indexOf(primaryDelimiter, i);
449+
if (primaryIndex !== -1) {
450+
j = primaryIndex;
451+
}
452+
453+
if (secondaryDelimiter != null) {
454+
secondaryIndex = s.indexOf(secondaryDelimiter, i);
455+
if (secondaryIndex !== -1 && secondaryIndex < j) {
456+
j = secondaryIndex;
457+
}
458+
}
459+
460+
if (j === s.length && count !== data.length) {
461+
leftover = s.substring(i);
462+
break;
463+
}
464+
465+
yield s.substring(i, j);
466+
i = j + 1;
467+
}
468+
}
469+
}
470+
432471
export function interpolate(template: string, context: object | undefined): string {
433472
if (template == null || template.length === 0) return template;
434473
if (context == null) return template.replace(tokenSanitizeRegex, '');

0 commit comments

Comments
 (0)