Support staged vs. unstaged changs. Increase diff size to 8000.

This commit is contained in:
Steven Wexler 2025-06-05 09:56:07 -04:00
parent 844784445c
commit 9e53dfef33

View file

@ -1,7 +1,7 @@
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'
import { IVoidSCMService } from '../common/voidSCMTypes.js'
import { promisify } from 'util'
import { exec as _exec } from 'child_process'
import { IVoidSCMService } from '../common/voidSCMTypes.js'
interface NumStat {
file: string
@ -11,6 +11,10 @@ interface NumStat {
const exec = promisify(_exec)
//8000 and 10 were chosen after some experimentation on small-to-moderately sized changes
const MAX_DIFF_LENGTH = 8000
const MAX_DIFF_FILES = 10
const git = async (command: string, path: string): Promise<string> => {
const { stdout, stderr } = await exec(`${command}`, { cwd: path })
if (stderr) {
@ -19,101 +23,48 @@ const git = async (command: string, path: string): Promise<string> => {
return stdout.trim()
}
const getNumStat = async (path: string): Promise<NumStat[]> => {
// Get both staged and unstaged changes
const [stagedOutput, unstagedOutput] = await Promise.all([
git('git diff --cached --numstat', path).catch(() => ''), // staged changes
git('git diff --numstat', path).catch(() => '') // unstaged changes
])
const parseOutput = (output: string) => {
if (!output.trim()) return []
return output
.split('\n')
.filter(line => line.trim())
.map((line) => {
const [added, removed, file] = line.split('\t')
return {
file,
added: parseInt(added, 10) || 0,
removed: parseInt(removed, 10) || 0,
}
})
}
const stagedStats = parseOutput(stagedOutput)
const unstagedStats = parseOutput(unstagedOutput)
// Combine and deduplicate by file, summing the changes
const fileMap = new Map<string, NumStat>()
for (const stat of [...stagedStats, ...unstagedStats]) {
const existing = fileMap.get(stat.file)
if (existing) {
existing.added += stat.added
existing.removed += stat.removed
} else {
fileMap.set(stat.file, { ...stat })
}
}
return Array.from(fileMap.values())
const getNumStat = async (path: string, useStagedChanges: boolean): Promise<NumStat[]> => {
const staged = useStagedChanges ? '--staged' : ''
const output = await git(`git diff --numstat ${staged}`, path)
return output
.split('\n')
.map((line) => {
const [added, removed, file] = line.split('\t')
return {
file,
added: parseInt(added, 10) || 0,
removed: parseInt(removed, 10) || 0,
}
})
}
const getSampledDiff = async (file: string, path: string): Promise<string> => {
// Get both staged and unstaged diffs
const [stagedDiff, unstagedDiff] = await Promise.all([
git(`git diff --cached --unified=0 --no-color -- "${file}"`, path).catch(() => ''), // staged changes
git(`git diff --unified=0 --no-color -- "${file}"`, path).catch(() => '') // unstaged changes
])
let combinedDiff = ''
if (stagedDiff.trim()) {
combinedDiff += `=== STAGED CHANGES ===\n${stagedDiff}\n\n`
}
if (unstagedDiff.trim()) {
combinedDiff += `=== UNSTAGED CHANGES ===\n${unstagedDiff}\n\n`
}
return combinedDiff.slice(0, 2000)
const getSampledDiff = async (file: string, path: string, useStagedChanges: boolean): Promise<string> => {
const staged = useStagedChanges ? '--staged' : ''
const diff = await git(`git diff --unified=0 --no-color ${staged} -- "${file}"`, path)
return diff.slice(0, MAX_DIFF_LENGTH)
}
const hasStagedChanges = async (path: string): Promise<boolean> => {
const output = await git('git diff --staged --name-only', path)
return output.length > 0
}
export class VoidSCMService implements IVoidSCMService {
readonly _serviceBrand: undefined
async gitStat(path: string): Promise<string> {
// Get both staged and unstaged stats
const [stagedStat, unstagedStat] = await Promise.all([
git('git diff --cached --stat', path).catch(() => ''), // staged changes
git('git diff --stat', path).catch(() => '') // unstaged changes
])
let combinedStat = ''
if (stagedStat.trim()) {
combinedStat += `Staged changes:\n${stagedStat}\n\n`
}
if (unstagedStat.trim()) {
combinedStat += `Unstaged changes:\n${unstagedStat}\n\n`
}
// If neither staged nor unstaged changes, check if there are any changes at all
if (!combinedStat.trim()) {
// This will show changes between HEAD and working directory (includes staged changes)
const allChanges = await git('git diff HEAD --stat', path).catch(() => '')
if (allChanges.trim()) {
combinedStat = `All changes:\n${allChanges}`
}
}
return combinedStat.trim()
const useStagedChanges = await hasStagedChanges(path)
const staged = useStagedChanges ? '--staged' : ''
return git(`git diff --stat ${staged}`, path)
}
async gitSampledDiffs(path: string): Promise<string> {
const numStatList = await getNumStat(path)
const useStagedChanges = await hasStagedChanges(path)
const numStatList = await getNumStat(path, useStagedChanges)
const topFiles = numStatList
.sort((a, b) => (b.added + b.removed) - (a.added + a.removed))
.slice(0, 10)
const diffs = await Promise.all(topFiles.map(async ({ file }) => ({ file, diff: await getSampledDiff(file, path) })))
.slice(0, MAX_DIFF_FILES)
const diffs = await Promise.all(topFiles.map(async ({ file }) => ({ file, diff: await getSampledDiff(file, path, useStagedChanges) })))
return diffs.map(({ file, diff }) => `==== ${file} ====\n${diff}`).join('\n\n')
}