@@ -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