parseGitDiff function
Parse unified diff output into per-file hunks.
Implementation
Map<String, List<StructuredPatchHunk>> parseGitDiff(String stdout) {
final result = <String, List<StructuredPatchHunk>>{};
if (stdout.trim().isEmpty) return result;
final fileDiffs = stdout
.split(RegExp(r'^diff --git ', multiLine: true))
.where((s) => s.isNotEmpty)
.toList();
for (final fileDiff in fileDiffs) {
if (result.length >= _maxFiles) break;
if (fileDiff.length > _maxDiffSizeBytes) continue;
final lines = fileDiff.split('\n');
final headerMatch = RegExp(r'^a/(.+?) b/(.+)$').firstMatch(lines.first);
if (headerMatch == null) continue;
final filePath = headerMatch.group(2) ?? headerMatch.group(1) ?? '';
final fileHunks = <StructuredPatchHunk>[];
StructuredPatchHunk? currentHunk;
var lineCount = 0;
for (var i = 1; i < lines.length; i++) {
final line = lines[i];
final hunkMatch = RegExp(
r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@',
).firstMatch(line);
if (hunkMatch != null) {
if (currentHunk != null) fileHunks.add(currentHunk);
currentHunk = StructuredPatchHunk(
oldStart: int.parse(hunkMatch.group(1) ?? '0'),
oldLines: int.parse(hunkMatch.group(2) ?? '1'),
newStart: int.parse(hunkMatch.group(3) ?? '0'),
newLines: int.parse(hunkMatch.group(4) ?? '1'),
);
continue;
}
// Skip metadata lines
if (line.startsWith('index ') ||
line.startsWith('---') ||
line.startsWith('+++') ||
line.startsWith('new file') ||
line.startsWith('deleted file') ||
line.startsWith('old mode') ||
line.startsWith('new mode') ||
line.startsWith('Binary files')) {
continue;
}
if (currentHunk != null &&
(line.startsWith('+') ||
line.startsWith('-') ||
line.startsWith(' ') ||
line.isEmpty)) {
if (lineCount >= _maxLinesPerFile) continue;
currentHunk.lines.add(line);
lineCount++;
}
}
if (currentHunk != null) fileHunks.add(currentHunk);
if (fileHunks.isNotEmpty) result[filePath] = fileHunks;
}
return result;
}