parseGitDiff function

Map<String, List<StructuredPatchHunk>> parseGitDiff(
  1. String stdout
)

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;
}