@@ -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}
0 commit comments