-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDiffParser.cs
More file actions
146 lines (125 loc) · 4.98 KB
/
DiffParser.cs
File metadata and controls
146 lines (125 loc) · 4.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using System.Text.RegularExpressions;
namespace CodeAtlas;
/// <summary>
/// Parses unified diff output (git diff) into structured data.
/// </summary>
public static partial class DiffParser
{
public static List<DiffFile> Parse(string diffText)
{
var files = new List<DiffFile>();
DiffFile? current = null;
DiffHunk? currentHunk = null;
int oldLine = 0, newLine = 0;
var hasGitHeaders = diffText.Contains("diff --git");
foreach (var rawLine in diffText.Split('\n'))
{
// New file: diff --git a/path b/path
if (rawLine.StartsWith("diff --git"))
{
current = new DiffFile();
files.Add(current);
currentHunk = null;
continue;
}
// Old file path
if (rawLine.StartsWith("--- "))
{
if (!hasGitHeaders && (current is null || current.Hunks.Count > 0))
{
// glab format: no "diff --git" lines, so "---" marks a new file
current = new DiffFile();
files.Add(current);
currentHunk = null;
}
if (current is null) continue;
if (rawLine.StartsWith("--- a/"))
current.OldPath = rawLine[6..];
else if (rawLine.StartsWith("--- /dev/null"))
current.IsNew = true;
else if (rawLine.Length > 4)
current.OldPath = rawLine[4..];
continue;
}
if (current is null) continue;
// New file path
if (rawLine.StartsWith("+++ "))
{
if (rawLine.StartsWith("+++ b/"))
current.NewPath = rawLine[6..];
else if (rawLine.StartsWith("+++ /dev/null"))
current.IsDeleted = true;
else if (rawLine.Length > 4)
current.NewPath = rawLine[4..];
continue;
}
// Hunk header: @@ -old,count +new,count @@
var hunkMatch = HunkHeaderRegex().Match(rawLine);
if (hunkMatch.Success)
{
oldLine = int.Parse(hunkMatch.Groups[1].Value);
newLine = int.Parse(hunkMatch.Groups[3].Value);
currentHunk = new DiffHunk
{
OldStart = oldLine,
NewStart = newLine,
Header = rawLine
};
current.Hunks.Add(currentHunk);
continue;
}
if (currentHunk is null) continue;
// Skip binary/mode lines
if (rawLine.StartsWith("Binary") || rawLine.StartsWith("index ") ||
rawLine.StartsWith("new file") || rawLine.StartsWith("deleted file") ||
rawLine.StartsWith("old mode") || rawLine.StartsWith("new mode") ||
rawLine.StartsWith("similarity") || rawLine.StartsWith("rename"))
continue;
// Diff lines
if (rawLine.StartsWith('+'))
{
currentHunk.Lines.Add(new DiffLine(DiffLineType.Add, rawLine[1..], null, newLine));
newLine++;
}
else if (rawLine.StartsWith('-'))
{
currentHunk.Lines.Add(new DiffLine(DiffLineType.Remove, rawLine[1..], oldLine, null));
oldLine++;
}
else if (rawLine.TrimEnd('\r').StartsWith(' '))
{
var trimmed = rawLine.TrimEnd('\r');
var text = trimmed.Length > 0 ? trimmed[1..] : "";
currentHunk.Lines.Add(new DiffLine(DiffLineType.Context, text, oldLine, newLine));
oldLine++;
newLine++;
}
}
return files;
}
[GeneratedRegex(@"^@@ -(\d+)(,\d+)? \+(\d+)(,\d+)? @@")]
private static partial Regex HunkHeaderRegex();
}
public class DiffFile
{
public string? OldPath { get; set; }
public string? NewPath { get; set; }
public bool IsNew { get; set; }
public bool IsDeleted { get; set; }
public List<DiffHunk> Hunks { get; } = [];
public string Path => NewPath ?? OldPath ?? "unknown";
public string FileName => System.IO.Path.GetFileName(Path);
public bool IsCSharp => Path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase);
public string FileType => GraphHelpers.DetectFileType(Path);
public int Additions => Hunks.Sum(h => h.Lines.Count(l => l.Type == DiffLineType.Add));
public int Deletions => Hunks.Sum(h => h.Lines.Count(l => l.Type == DiffLineType.Remove));
}
public class DiffHunk
{
public int OldStart { get; set; }
public int NewStart { get; set; }
public string Header { get; set; } = "";
public List<DiffLine> Lines { get; } = [];
}
public record DiffLine(DiffLineType Type, string Text, int? OldLineNum, int? NewLineNum);
public enum DiffLineType { Context, Add, Remove }