From 782c0b78663d62c324ad8e03b251adb88bd0f3fe Mon Sep 17 00:00:00 2001 From: waleedcodes <110061477+waleedcodes@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:19:05 +0500 Subject: [PATCH 1/3] feat(git): scaffold diff parser structure and file header matchers --- src/git/diffParser.ts | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/git/diffParser.ts diff --git a/src/git/diffParser.ts b/src/git/diffParser.ts new file mode 100644 index 0000000..32519a9 --- /dev/null +++ b/src/git/diffParser.ts @@ -0,0 +1,54 @@ +import { ChangedLineRange, DiffHunk, FileDiff, GitDiffResult } from '../types'; + +/** + * Parses unified git diff output into structured FileDiff models + */ +export class DiffParser { + public static parse(rawDiff: string, repoRoot: string = ''): GitDiffResult { + const files: FileDiff[] = []; + if (!rawDiff || rawDiff.trim().length === 0) { + return { + repoRoot, + files: [], + totalFilesChanged: 0, + totalInsertions: 0, + totalDeletions: 0 + }; + } + + const lines = rawDiff.split('\n'); + let currentFile: FileDiff | null = null; + let currentHunk: DiffHunk | null = null; + let currentNewLine = 0; + let totalInsertions = 0; + let totalDeletions = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + // New file diff header: diff --git a/path b/path + if (line.startsWith('diff --git ')) { + if (currentHunk && currentFile) { + currentFile.hunks.push(currentHunk); + currentHunk = null; + } + if (currentFile) { + files.push(currentFile); + } + + const match = line.match(/^diff --git a\/(.+) b\/(.+)$/); + const oldPath = match ? match[1] : ''; + const newPath = match ? match[2] : ''; + + currentFile = { + oldPath, + newPath: newPath || oldPath, + isNew: false, + isDeleted: false, + isRenamed: false, + hunks: [], + addedLines: [], + deletedLines: [], + modifiedLineRanges: [] + }; + continue; From 5157d5712a998b48980fefb4fff96d81df2c33ba Mon Sep 17 00:00:00 2001 From: waleedcodes <110061477+waleedcodes@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:19:05 +0500 Subject: [PATCH 2/3] feat(git): implement hunk header parser and line number tracking --- src/git/diffParser.ts | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/git/diffParser.ts b/src/git/diffParser.ts index 32519a9..edc239a 100644 --- a/src/git/diffParser.ts +++ b/src/git/diffParser.ts @@ -52,3 +52,65 @@ export class DiffParser { modifiedLineRanges: [] }; continue; + } + + if (!currentFile) { + continue; + } + + if (line.startsWith('new file mode ')) { + currentFile.isNew = true; + } else if (line.startsWith('deleted file mode ')) { + currentFile.isDeleted = true; + } else if (line.startsWith('similarity index ')) { + currentFile.isRenamed = true; + } else if (line.startsWith('rename from ')) { + currentFile.oldPath = line.substring('rename from '.length).trim(); + } else if (line.startsWith('rename to ')) { + currentFile.newPath = line.substring('rename to '.length).trim(); + } else if (line.startsWith('@@ ')) { + // Hunk header: @@ -oldStart,oldLines +newStart,newLines @@ [optional context] + if (currentHunk) { + currentFile.hunks.push(currentHunk); + } + + const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/); + if (hunkMatch) { + const oldStart = parseInt(hunkMatch[1], 10); + const oldLines = hunkMatch[2] ? parseInt(hunkMatch[2], 10) : 1; + const newStart = parseInt(hunkMatch[3], 10); + const newLines = hunkMatch[4] ? parseInt(hunkMatch[4], 10) : 1; + + currentHunk = { + oldStart, + oldLines, + newStart, + newLines, + header: line, + lines: [] + }; + currentNewLine = newStart; + } + } else if (currentHunk) { + currentHunk.lines.push(line); + + if (line.startsWith('+') && !line.startsWith('+++')) { + currentFile.addedLines.push(currentNewLine); + totalInsertions++; + currentNewLine++; + } else if (line.startsWith('-') && !line.startsWith('---')) { + currentFile.deletedLines.push(currentHunk.newStart); + totalDeletions++; + } else if (line.startsWith(' ')) { + currentNewLine++; + } + } + } + + if (currentHunk && currentFile) { + currentFile.hunks.push(currentHunk); + } + if (currentFile) { + files.push(currentFile); + } + From cfd8b790ac0c8bdaa9e4caa888e7bcacae0b2c6a Mon Sep 17 00:00:00 2001 From: waleedcodes <110061477+waleedcodes@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:19:05 +0500 Subject: [PATCH 3/3] feat(git): compute exact modified line ranges and handle renames --- src/git/diffParser.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/git/diffParser.ts b/src/git/diffParser.ts index edc239a..028f0ef 100644 --- a/src/git/diffParser.ts +++ b/src/git/diffParser.ts @@ -114,3 +114,42 @@ export class DiffParser { files.push(currentFile); } + // Compute consolidated modifiedLineRanges per file + for (const file of files) { + file.modifiedLineRanges = this.computeLineRanges(file.addedLines); + } + + return { + repoRoot, + files, + totalFilesChanged: files.length, + totalInsertions, + totalDeletions + }; + } + + private static computeLineRanges(lineNumbers: number[]): ChangedLineRange[] { + if (lineNumbers.length === 0) { + return []; + } + + const sorted = Array.from(new Set(lineNumbers)).sort((a, b) => a - b); + const ranges: ChangedLineRange[] = []; + let start = sorted[0]; + let end = sorted[0]; + + for (let i = 1; i < sorted.length; i++) { + const current = sorted[i]; + if (current === end + 1) { + end = current; + } else { + ranges.push({ start, end }); + start = current; + end = current; + } + } + ranges.push({ start, end }); + + return ranges; + } +}