UNPKG

git-tweezers

Version:

Advanced git staging tool with hunk and line-level control

136 lines (135 loc) 5.28 kB
import parse from 'parse-git-diff'; import { DiffAnalyzer } from './diff-analyzer.js'; import { generateHunkId, getHunkSummary, getHunkStats } from './hunk-id.js'; export class DiffParser { parse(diffText) { return parse(diffText); } parseFiles(diffText) { const files = this.parseFilesWithInfo(diffText); // Convert back to ParsedFile for backward compatibility return files.map(file => ({ oldPath: file.oldPath, newPath: file.newPath, hunks: file.hunks.map(hunk => ({ index: hunk.index, header: hunk.header, oldStart: hunk.oldStart, oldLines: hunk.oldLines, newStart: hunk.newStart, newLines: hunk.newLines, changes: hunk.changes, })) })); } parseFilesWithInfo(diffText) { const gitDiff = this.parse(diffText); const eolMap = DiffAnalyzer.analyzeEOL(diffText); let globalChangeIndex = 0; return gitDiff.files.map(file => { const oldPath = this.getOldPath(file); const newPath = this.getNewPath(file); return { oldPath, newPath, hunks: file.chunks .filter((chunk) => chunk.type === 'Chunk') .map((chunk, index) => { // Build header from chunk data const header = `@@ -${chunk.fromFileRange.start},${chunk.fromFileRange.lines} +${chunk.toFileRange.start},${chunk.toFileRange.lines} @@`; // Enhance changes with EOL information const enhancedChanges = chunk.changes .filter(change => change.content !== 'No newline at end of file') .map(change => { const eol = eolMap.get(globalChangeIndex) ?? true; // Default to true if not found globalChangeIndex++; return { ...change, eol }; }); const hunkData = { index: index + 1, // 1-based index for user-facing header, oldStart: chunk.fromFileRange.start, oldLines: chunk.fromFileRange.lines, newStart: chunk.toFileRange.start, newLines: chunk.toFileRange.lines, changes: enhancedChanges, }; const id = generateHunkId(hunkData, newPath); const summary = getHunkSummary(hunkData); const stats = getHunkStats(hunkData); return { ...hunkData, id, summary, stats, }; }), }; }); } getOldPath(file) { switch (file.type) { case 'ChangedFile': case 'AddedFile': case 'DeletedFile': return file.path; case 'RenamedFile': return file.pathBefore; } } getNewPath(file) { switch (file.type) { case 'ChangedFile': case 'AddedFile': case 'DeletedFile': return file.path; case 'RenamedFile': return file.pathAfter; } } getHunkCount(diffText) { const gitDiff = this.parse(diffText); return gitDiff.files.reduce((count, file) => { const chunks = file.chunks.filter(chunk => chunk.type === 'Chunk'); return count + chunks.length; }, 0); } getFileHunkCount(diffText, filePath) { const gitDiff = this.parse(diffText); const file = gitDiff.files.find(f => this.getNewPath(f) === filePath || this.getOldPath(f) === filePath); if (!file) return 0; return file.chunks.filter(chunk => chunk.type === 'Chunk').length; } extractHunk(diffText, filePath, hunkIndex) { const files = this.parseFiles(diffText); const file = files.find(f => f.newPath === filePath || f.oldPath === filePath); if (!file || hunkIndex < 1 || hunkIndex > file.hunks.length) { return null; } return file.hunks[hunkIndex - 1]; } extractLines(diffText, filePath, startLine, endLine) { const files = this.parseFiles(diffText); const file = files.find(f => f.newPath === filePath || f.oldPath === filePath); if (!file) return []; const changes = []; let currentLine = 0; for (const hunk of file.hunks) { currentLine = hunk.newStart; for (const change of hunk.changes) { if (change.type === 'AddedLine' || change.type === 'UnchangedLine') { if (currentLine >= startLine && currentLine <= endLine) { changes.push(change); } currentLine++; } } } return changes; } }