Git - get the diff and num stats for a commit (#284403)

This commit is contained in:
Ladislau Szomoru
2025-12-19 10:18:29 +00:00
committed by GitHub
parent c4faa84e4e
commit c95739960f
7 changed files with 115 additions and 16 deletions

View File

@@ -13,7 +13,7 @@ import { EventEmitter } from 'events';
import * as filetype from 'file-type';
import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh, isDescendant, relativePathWithNoFallback, Mutable } from './util';
import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode';
import { Commit as ApiCommit, Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery as ApiRefQuery, InitOptions, Worktree } from './api/git';
import { Commit as ApiCommit, Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery as ApiRefQuery, InitOptions, Worktree, DiffChange } from './api/git';
import * as byline from 'byline';
import { StringDecoder } from 'string_decoder';
@@ -1084,6 +1084,79 @@ function parseGitChanges(repositoryRoot: string, raw: string): Change[] {
return result;
}
function parseGitChangesRaw(repositoryRoot: string, raw: string): DiffChange[] {
const changes: Change[] = [];
const numStats = new Map<string, { insertions: number; deletions: number }>();
let index = 0;
const segments = raw.trim().split('\x00').filter(s => s);
segmentsLoop:
while (index < segments.length) {
const segment = segments[index++];
if (!segment) {
break;
}
if (segment.startsWith(':')) {
// Parse --raw output
const [, , , , change] = segment.split(' ');
const filePath = segments[index++];
const originalUri = Uri.file(path.isAbsolute(filePath) ? filePath : path.join(repositoryRoot, filePath));
let uri = originalUri;
let renameUri = originalUri;
let status = Status.UNTRACKED;
switch (change[0]) {
case 'A':
status = Status.INDEX_ADDED;
break;
case 'M':
status = Status.MODIFIED;
break;
case 'D':
status = Status.DELETED;
break;
case 'R': {
if (index >= segments.length) {
break;
}
const newPath = segments[index++];
if (!newPath) {
break;
}
status = Status.INDEX_RENAMED;
uri = renameUri = Uri.file(path.isAbsolute(newPath) ? newPath : path.join(repositoryRoot, newPath));
break;
}
default:
// Unknown status
break segmentsLoop;
}
changes.push({ status, uri, originalUri, renameUri });
} else {
// Parse --numstat output
const [insertions, deletions, filePath] = segment.split('\t');
numStats.set(
path.isAbsolute(filePath)
? filePath
: path.join(repositoryRoot, filePath), {
insertions: insertions === '-' ? 0 : parseInt(insertions),
deletions: deletions === '-' ? 0 : parseInt(deletions),
});
}
}
return changes.map(change => ({
...change,
insertions: numStats.get(change.uri.fsPath)?.insertions ?? 0,
deletions: numStats.get(change.uri.fsPath)?.deletions ?? 0,
}));
}
export interface BlameInformation {
readonly hash: string;
readonly subject?: string;
@@ -1694,8 +1767,24 @@ export class Repository {
return result.stdout.trim();
}
async diffBetween2(ref1: string, ref2: string, options: { similarityThreshold?: number }): Promise<Change[]> {
return await this.diffFiles(`${ref1}...${ref2}`, { cached: false, similarityThreshold: options.similarityThreshold });
async diffBetweenWithStats(ref: string, options: { path?: string; similarityThreshold?: number }): Promise<DiffChange[]> {
const args = ['diff', '--raw', '--numstat', '--diff-filter=ADMR', '-z',];
if (options.similarityThreshold) {
args.push(`--find-renames=${options.similarityThreshold}%`);
}
args.push(...[ref, '--']);
if (options.path) {
args.push(this.sanitizeRelativePath(options.path));
}
const gitResult = await this.exec(args);
if (gitResult.exitCode) {
return [];
}
return parseGitChangesRaw(this.repositoryRoot, gitResult.stdout);
}
private async diffFiles(ref: string | undefined, options: { cached: boolean; similarityThreshold?: number }): Promise<Change[]> {
@@ -1749,8 +1838,8 @@ export class Repository {
}
async diffTrees(treeish1: string, treeish2?: string, options?: { similarityThreshold?: number }): Promise<Change[]> {
const args = ['diff-tree', '-r', '--name-status', '-z', '--diff-filter=ADMR'];
async diffTrees(treeish1: string, treeish2?: string, options?: { similarityThreshold?: number }): Promise<DiffChange[]> {
const args = ['diff-tree', '-r', '--raw', '--numstat', '--diff-filter=ADMR', '-z'];
if (options?.similarityThreshold) {
args.push(`--find-renames=${options.similarityThreshold}%`);
@@ -1769,7 +1858,7 @@ export class Repository {
return [];
}
return parseGitChanges(this.repositoryRoot, gitResult.stdout);
return parseGitChangesRaw(this.repositoryRoot, gitResult.stdout);
}
async getMergeBase(ref1: string, ref2: string, ...refs: string[]): Promise<string | undefined> {