Skip to content

Commit 40a4381

Browse files
committed
[fix+enhancement] Improve handling of filemode changes
* Add handling of ChangeState.TypeChanged 'T' in CompareRevisions and QueryFileHistory (it's already handled in QueryLocalChanges and QueryStagedChangesWithAmend). * DiffResult class: * Change type of OldMode and NewMode from string to int, for more convenient handling. * Make the FileModeChange property return a (human-readable, localized) name instead of a 'magic number'. * NOTE: The 6 new localized strings (Text.FileMode.*) are only provided for en_US, so far. * Diff class: * Refactor the ParseLine() method by delegating to 3 new methods (for parsing of diff-header, chunk-start and chunk-body). * Add new private bool-variable _isInChunk, to keep track of when we're parsing the 'inside' of a chunk-body. * Improve the parsing within ParseLine() and related methods to correctly handle TypeChanged diffs, which actually contain two full diffs in one (1st diff-header, 1st chunk, 2nd diff-header, 2nd chunk). * Make ParseFileModeChange() parse file-mode strings into integers (they are actually 'octal' numbers, but we can treat them as 'decimal' numbers since we only ever check for equality against known values). * Refactor ParseLFSChange() to make use of the chunk body indicator (first char).
1 parent 93fb4fc commit 40a4381

6 files changed

Lines changed: 186 additions & 95 deletions

File tree

‎src/Commands/CompareRevisions.cs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace SourceGit.Commands
77
{
88
public partial class CompareRevisions : Command
99
{
10-
[GeneratedRegex(@"^([MAD])\s+(.+)$")]
10+
[GeneratedRegex(@"^([MADT])\s+(.+)$")]
1111
private static partial Regex REG_FORMAT();
1212
[GeneratedRegex(@"^([CR])[0-9]{0,4}\s+(.+)$")]
1313
private static partial Regex REG_RENAME_FORMAT();
@@ -73,6 +73,10 @@ public CompareRevisions(string repo, string start, string end, string path)
7373
change.Set(Models.ChangeState.Deleted);
7474
changes.Add(change);
7575
break;
76+
case 'T':
77+
change.Set(Models.ChangeState.TypeChanged);
78+
changes.Add(change);
79+
break;
7680
}
7781
}
7882

‎src/Commands/Diff.cs‎

Lines changed: 137 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@ 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+
21+
private enum Indicator
22+
{
23+
ChunkHeader = '@',
24+
Context = ' ',
25+
Old = '-',
26+
New = '+',
27+
Special = '\\',
28+
}
2229

2330
public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignoreWhitespace, bool ignoreCRAtEOL)
2431
{
@@ -81,7 +88,12 @@ public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignore
8188
}
8289
else
8390
{
84-
ProcessInlineHighlights();
91+
if (_isInChunk)
92+
{
93+
ProcessInlineHighlights();
94+
_isInChunk = false;
95+
}
96+
8597
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
8698
_result.TextDiff.OldMode = _result.OldMode;
8799
_result.TextDiff.NewMode = _result.NewMode;
@@ -95,153 +107,194 @@ public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignore
95107
private void ParseLine(byte[] lineBytes)
96108
{
97109
var line = Encoding.UTF8.GetString(lineBytes);
98-
if (_result.TextDiff.Lines.Count == 0)
99-
{
100-
if (ParseFileModeChange(line))
101-
return;
110+
if (line.Length == 0)
111+
return;
102112

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

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

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

159+
var match = REG_INDICATOR().Match(line);
160+
if (match.Success)
161+
{
124162
_oldLine = int.Parse(match.Groups[1].Value);
125163
_newLine = int.Parse(match.Groups[2].Value);
126164
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, lineBytes, 0, 0);
127165
_result.TextDiff.Lines.Add(_last);
166+
167+
_isInChunk = true;
168+
return true;
128169
}
129170
}
130-
else
131-
{
132-
if (ParseLFSChange(line))
133-
return;
171+
return false;
172+
}
134173

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-
}
174+
private bool ParseChunkBodyLine(char ch, string line, byte[] rawContent)
175+
{
176+
if (_isInChunk)
177+
{
178+
if (ParseLFSChange(ch, line))
179+
return true;
144180

145-
var ch = line[0];
146-
if (ch == '-')
181+
if (ch == (char)Indicator.Old)
147182
{
148183
_result.TextDiff.DeletedLines++;
149-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), lineBytes[1..], _oldLine, 0);
184+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line, rawContent, _oldLine, 0);
150185
_deleted.Add(_last);
151186
_oldLine++;
187+
return true;
152188
}
153-
else if (ch == '+')
189+
190+
if (ch == (char)Indicator.New)
154191
{
155192
_result.TextDiff.AddedLines++;
156-
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), lineBytes[1..], 0, _newLine);
193+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line, rawContent, 0, _newLine);
157194
_added.Add(_last);
158195
_newLine++;
196+
return true;
159197
}
160-
else if (ch != '\\')
198+
199+
if (ch == (char)Indicator.Context)
161200
{
162201
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-
}
202+
203+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line, rawContent, _oldLine, _newLine);
204+
_result.TextDiff.Lines.Add(_last);
205+
_oldLine++;
206+
_newLine++;
207+
return true;
178208
}
179-
else if (line.Equals("\\ No newline at end of file", StringComparison.Ordinal))
209+
210+
if (ch == (char)Indicator.Special)
180211
{
181-
_last.NoNewLineEndOfFile = true;
212+
if (line.Equals(" No newline at end of file", StringComparison.Ordinal))
213+
_last.NoNewLineEndOfFile = true;
214+
return true;
182215
}
183216
}
217+
218+
ProcessInlineHighlights();
219+
_isInChunk = false;
220+
return false;
221+
}
222+
223+
private int ParseFileModeNumber(string fileModeStr)
224+
{
225+
int fileMode = 0;
226+
Int32.TryParse(fileModeStr, out fileMode);
227+
return fileMode;
184228
}
185229

186230
private bool ParseFileModeChange(string line)
187231
{
188232
if (line.StartsWith("old mode ", StringComparison.Ordinal))
189233
{
190-
_result.OldMode = line.Substring(9);
234+
_result.OldMode = ParseFileModeNumber(line.Substring(9));
191235
return true;
192236
}
193237

194238
if (line.StartsWith("new mode ", StringComparison.Ordinal))
195239
{
196-
_result.NewMode = line.Substring(9);
240+
_result.NewMode = ParseFileModeNumber(line.Substring(9));
197241
return true;
198242
}
199243

200244
if (line.StartsWith("deleted file mode ", StringComparison.Ordinal))
201245
{
202-
_result.OldMode = line.Substring(18);
246+
_result.OldMode = ParseFileModeNumber(line.Substring(18));
203247
return true;
204248
}
205249

206250
if (line.StartsWith("new file mode ", StringComparison.Ordinal))
207251
{
208-
_result.NewMode = line.Substring(14);
252+
_result.NewMode = ParseFileModeNumber(line.Substring(14));
209253
return true;
210254
}
211255

212256
return false;
213257
}
214258

215-
private bool ParseLFSChange(string line)
259+
private bool ParseLFSChange(char ch, string line)
216260
{
217261
if (_result.IsLFS)
218262
{
219-
if (line.StartsWith("-oid sha256:", StringComparison.Ordinal))
220-
_result.LFSDiff.Old.Oid = line.Substring(12);
221-
else if (line.StartsWith("-size ", StringComparison.Ordinal))
222-
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
223-
else if (line.StartsWith("+oid sha256:", StringComparison.Ordinal))
224-
_result.LFSDiff.New.Oid = line.Substring(12);
225-
else if (line.StartsWith("+size ", StringComparison.Ordinal))
226-
_result.LFSDiff.New.Size = long.Parse(line.AsSpan(6));
227-
else if (line.StartsWith(" size ", StringComparison.Ordinal))
228-
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
229-
263+
if (ch == (char)Indicator.Old)
264+
{
265+
if (line.StartsWith("oid sha256:", StringComparison.Ordinal))
266+
_result.LFSDiff.Old.Oid = line.Substring(11);
267+
else if (line.StartsWith("size ", StringComparison.Ordinal))
268+
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan(5));
269+
}
270+
else if (ch == (char)Indicator.New)
271+
{
272+
if (line.StartsWith("oid sha256:", StringComparison.Ordinal))
273+
_result.LFSDiff.New.Oid = line.Substring(11);
274+
else if (line.StartsWith("size ", StringComparison.Ordinal))
275+
_result.LFSDiff.New.Size = long.Parse(line.AsSpan(5));
276+
}
277+
else if (ch == (char)Indicator.Context)
278+
{
279+
if (line.StartsWith("size ", StringComparison.Ordinal))
280+
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(5));
281+
}
230282
return true;
231283
}
232284

233285
if (_result.TextDiff.Lines.Count != 1)
234286
return false;
235287

236-
var isLFS = (_oldLine == 1 && _newLine == 1 && line.StartsWith(PREFIX_LFS_MODIFY, StringComparison.Ordinal)) ||
237-
(_oldLine == 1 && _newLine == 0 && line.StartsWith(PREFIX_LFS_DEL, StringComparison.Ordinal)) ||
238-
(_oldLine == 0 && _newLine == 1 && line.StartsWith(PREFIX_LFS_NEW, StringComparison.Ordinal));
239-
240-
if (isLFS)
288+
if ((_oldLine == 1 && _newLine == 1 && ch == (char)Indicator.Context) ||
289+
(_oldLine == 1 && _newLine == 0 && ch == (char)Indicator.Old) ||
290+
(_oldLine == 0 && _newLine == 1 && ch == (char)Indicator.New))
241291
{
242-
_result.IsLFS = true;
243-
_result.LFSDiff = new Models.LFSDiff();
244-
return true;
292+
if (line.StartsWith(LFS_SPECIFIER, StringComparison.Ordinal))
293+
{
294+
_result.IsLFS = true;
295+
_result.LFSDiff = new Models.LFSDiff();
296+
return true;
297+
}
245298
}
246299

247300
return false;
@@ -293,5 +346,6 @@ private void ProcessInlineHighlights()
293346
private Models.TextDiffLine _last = null;
294347
private int _oldLine = 0;
295348
private int _newLine = 0;
349+
private bool _isInChunk = false;
296350
}
297351
}

‎src/Commands/QueryFileHistory.cs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace SourceGit.Commands
88
{
99
public partial class QueryFileHistory : Command
1010
{
11-
[GeneratedRegex(@"^([MAD])\s+(.+)$")]
11+
[GeneratedRegex(@"^([MADT])\s+(.+)$")]
1212
private static partial Regex REG_FORMAT();
1313
[GeneratedRegex(@"^([CR])[0-9]{0,4}\s+(.+)$")]
1414
private static partial Regex REG_RENAME_FORMAT();
@@ -86,6 +86,9 @@ public QueryFileHistory(string repo, string path, string head)
8686
case 'D':
8787
last.Change.Set(Models.ChangeState.Deleted);
8888
break;
89+
case 'T':
90+
last.Change.Set(Models.ChangeState.TypeChanged);
91+
break;
8992
}
9093
}
9194
}

0 commit comments

Comments
 (0)