Skip to content

Commit a30cf17

Browse files
authored
enhance: support type-changed diffs (sourcegit-scm#2474)
1 parent 337c049 commit a30cf17

1 file changed

Lines changed: 135 additions & 79 deletions

File tree

src/Commands/Diff.cs

Lines changed: 135 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,18 @@ public partial class Diff : Command
1616
[GeneratedRegex(@"^index\s([0-9a-f]{6,64})\.\.([0-9a-f]{6,64})(\s[1-9]{6})?")]
1717
private static partial Regex REG_HASH_CHANGE();
1818

19-
private const string PREFIX_LFS_NEW = "+version https://git-lfs.github.com/spec/";
20-
private const string PREFIX_LFS_DEL = "-version https://git-lfs.github.com/spec/";
21-
private const string PREFIX_LFS_MODIFY = " version https://git-lfs.github.com/spec/";
19+
private const string LFS_SPECIFIER = "version https://git-lfs.github.com/spec/";
20+
private const string LFS_OID_PREFIX = "oid sha256:";
21+
private const string LFS_SIZE_PREFIX = "size ";
22+
23+
private enum Indicator
24+
{
25+
ChunkHeader = '@',
26+
Context = ' ',
27+
Old = '-',
28+
New = '+',
29+
Special = '\\',
30+
}
2231

2332
public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignoreWhitespace, bool ignoreCRAtEOL)
2433
{
@@ -81,7 +90,12 @@ public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignore
8190
}
8291
else
8392
{
84-
ProcessInlineHighlights();
93+
if (_isInChunk)
94+
{
95+
ProcessInlineHighlights();
96+
_isInChunk = false;
97+
}
98+
8599
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
86100
_result.TextDiff.OldMode = _result.OldMode;
87101
_result.TextDiff.NewMode = _result.NewMode;
@@ -95,92 +109,124 @@ public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignore
95109
private void ParseLine(byte[] lineBytes)
96110
{
97111
var line = Encoding.UTF8.GetString(lineBytes);
98-
if (_result.TextDiff.Lines.Count == 0)
99-
{
100-
if (ParseFileModeChange(line))
101-
return;
112+
if (line.Length == 0)
113+
return;
102114

103-
if (line.StartsWith("Binary", StringComparison.Ordinal))
104-
{
105-
_result.IsBinary = true;
106-
return;
107-
}
115+
if (ParseChunkStartLine(line, lineBytes))
116+
return;
108117

109-
if (string.IsNullOrEmpty(_result.OldHash))
110-
{
111-
var match = REG_HASH_CHANGE().Match(line);
112-
if (!match.Success)
113-
return;
118+
if (ParseChunkBodyLine(line[0], line.Substring(1), lineBytes[1..]))
119+
return;
120+
121+
ParseDiffHeaderLine(line);
122+
}
114123

115-
_result.OldHash = match.Groups[1].Value;
124+
private void ParseDiffHeaderLine(string line)
125+
{
126+
if (line.StartsWith("diff"))
127+
return;
128+
129+
if (ParseFileModeChange(line))
130+
return;
131+
132+
if (line.StartsWith("index"))
133+
{
134+
var match = REG_HASH_CHANGE().Match(line);
135+
if (match.Success)
136+
{
137+
// NOTE: For a TypeChanged file we receive two full sets of diff-lines within
138+
// the same diff output, indicating a 'deleted file' followed by a 'new file' .
139+
// We then keep the oldest Old hash and the newest New hash.
140+
if (string.IsNullOrEmpty(_result.OldHash))
141+
_result.OldHash = match.Groups[1].Value;
116142
_result.NewHash = match.Groups[2].Value;
117143
}
118-
else
144+
return;
145+
}
146+
147+
if (line.StartsWith("Binary", StringComparison.Ordinal))
148+
_result.IsBinary = true;
149+
}
150+
151+
private bool ParseChunkStartLine(System.String line, byte[] lineBytes)
152+
{
153+
if (line[0] == (char)Indicator.ChunkHeader)
154+
{
155+
if (_isInChunk)
119156
{
120-
var match = REG_INDICATOR().Match(line);
121-
if (!match.Success)
122-
return;
157+
ProcessInlineHighlights();
158+
_isInChunk = false;
159+
}
123160

161+
var match = REG_INDICATOR().Match(line);
162+
if (match.Success)
163+
{
124164
_oldLine = int.Parse(match.Groups[1].Value);
125165
_newLine = int.Parse(match.Groups[2].Value);
126166
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, lineBytes, 0, 0);
127167
_result.TextDiff.Lines.Add(_last);
168+
169+
_isInChunk = true;
170+
return true;
128171
}
129172
}
130-
else
131-
{
132-
if (ParseLFSChange(line))
133-
return;
173+
return false;
174+
}
134175

135-
if (line.Length == 0)
136-
{
137-
ProcessInlineHighlights();
138-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", [], _oldLine, _newLine);
139-
_result.TextDiff.Lines.Add(_last);
140-
_oldLine++;
141-
_newLine++;
142-
return;
143-
}
176+
private bool ParseChunkBodyLine(char ch, string line, byte[] rawContent)
177+
{
178+
if (_isInChunk)
179+
{
180+
if (ParseLFSChange(ch, line))
181+
return true;
144182

145-
var ch = line[0];
146-
if (ch == '-')
183+
if (ch == (char)Indicator.Old)
147184
{
148185
_result.TextDiff.DeletedLines++;
149-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), lineBytes[1..], _oldLine, 0);
186+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line, rawContent, _oldLine, 0);
150187
_deleted.Add(_last);
151188
_oldLine++;
189+
return true;
152190
}
153-
else if (ch == '+')
191+
192+
if (ch == (char)Indicator.New)
154193
{
155194
_result.TextDiff.AddedLines++;
156-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), lineBytes[1..], 0, _newLine);
195+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line, rawContent, 0, _newLine);
157196
_added.Add(_last);
158197
_newLine++;
198+
return true;
159199
}
160-
else if (ch != '\\')
200+
201+
if (ch == (char)Indicator.Context)
161202
{
162203
ProcessInlineHighlights();
163-
var match = REG_INDICATOR().Match(line);
164-
if (match.Success)
165-
{
166-
_oldLine = int.Parse(match.Groups[1].Value);
167-
_newLine = int.Parse(match.Groups[2].Value);
168-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, lineBytes, 0, 0);
169-
_result.TextDiff.Lines.Add(_last);
170-
}
171-
else
172-
{
173-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), lineBytes[1..], _oldLine, _newLine);
174-
_result.TextDiff.Lines.Add(_last);
175-
_oldLine++;
176-
_newLine++;
177-
}
204+
205+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line, rawContent, _oldLine, _newLine);
206+
_result.TextDiff.Lines.Add(_last);
207+
_oldLine++;
208+
_newLine++;
209+
return true;
178210
}
179-
else if (line.Equals("\\ No newline at end of file", StringComparison.Ordinal))
211+
212+
if (ch == (char)Indicator.Special)
180213
{
181-
_last.NoNewLineEndOfFile = true;
214+
if (line.Equals(" No newline at end of file", StringComparison.Ordinal))
215+
_last.NoNewLineEndOfFile = true;
216+
return true;
182217
}
183218
}
219+
220+
ProcessInlineHighlights();
221+
_isInChunk = false;
222+
return false;
223+
}
224+
225+
private int ParseFileModeNumber(string fileModeStr)
226+
{
227+
int fileMode = 0;
228+
Int32.TryParse(fileModeStr, out fileMode);
229+
return fileMode;
184230
}
185231

186232
private bool ParseFileModeChange(string line)
@@ -219,36 +265,45 @@ private int ParseFileMode(string content)
219265
return mode;
220266
}
221267

222-
private bool ParseLFSChange(string line)
268+
private bool ParseLFSChange(char ch, string line)
223269
{
224270
if (_result.IsLFS)
225271
{
226-
if (line.StartsWith("-oid sha256:", StringComparison.Ordinal))
227-
_result.LFSDiff.Old.Oid = line.Substring(12);
228-
else if (line.StartsWith("-size ", StringComparison.Ordinal))
229-
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
230-
else if (line.StartsWith("+oid sha256:", StringComparison.Ordinal))
231-
_result.LFSDiff.New.Oid = line.Substring(12);
232-
else if (line.StartsWith("+size ", StringComparison.Ordinal))
233-
_result.LFSDiff.New.Size = long.Parse(line.AsSpan(6));
234-
else if (line.StartsWith(" size ", StringComparison.Ordinal))
235-
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
236-
272+
if (ch == (char)Indicator.Old)
273+
{
274+
if (line.StartsWith(LFS_OID_PREFIX, StringComparison.Ordinal))
275+
_result.LFSDiff.Old.Oid = line.Substring(11);
276+
else if (line.StartsWith(LFS_SIZE_PREFIX, StringComparison.Ordinal))
277+
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan(5));
278+
}
279+
else if (ch == (char)Indicator.New)
280+
{
281+
if (line.StartsWith(LFS_OID_PREFIX, StringComparison.Ordinal))
282+
_result.LFSDiff.New.Oid = line.Substring(11);
283+
else if (line.StartsWith(LFS_SIZE_PREFIX, StringComparison.Ordinal))
284+
_result.LFSDiff.New.Size = long.Parse(line.AsSpan(5));
285+
}
286+
else if (ch == (char)Indicator.Context)
287+
{
288+
if (line.StartsWith(LFS_SIZE_PREFIX, StringComparison.Ordinal))
289+
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(5));
290+
}
237291
return true;
238292
}
239293

240294
if (_result.TextDiff.Lines.Count != 1)
241295
return false;
242296

243-
var isLFS = (_oldLine == 1 && _newLine == 1 && line.StartsWith(PREFIX_LFS_MODIFY, StringComparison.Ordinal)) ||
244-
(_oldLine == 1 && _newLine == 0 && line.StartsWith(PREFIX_LFS_DEL, StringComparison.Ordinal)) ||
245-
(_oldLine == 0 && _newLine == 1 && line.StartsWith(PREFIX_LFS_NEW, StringComparison.Ordinal));
246-
247-
if (isLFS)
297+
if ((_oldLine == 1 && _newLine == 1 && ch == (char)Indicator.Context) ||
298+
(_oldLine == 1 && _newLine == 0 && ch == (char)Indicator.Old) ||
299+
(_oldLine == 0 && _newLine == 1 && ch == (char)Indicator.New))
248300
{
249-
_result.IsLFS = true;
250-
_result.LFSDiff = new Models.LFSDiff();
251-
return true;
301+
if (line.StartsWith(LFS_SPECIFIER, StringComparison.Ordinal))
302+
{
303+
_result.IsLFS = true;
304+
_result.LFSDiff = new Models.LFSDiff();
305+
return true;
306+
}
252307
}
253308

254309
return false;
@@ -300,5 +355,6 @@ private void ProcessInlineHighlights()
300355
private Models.TextDiffLine _last = null;
301356
private int _oldLine = 0;
302357
private int _newLine = 0;
358+
private bool _isInChunk = false;
303359
}
304360
}

0 commit comments

Comments
 (0)