1- using GitExtensions . Extensibility ;
1+ using System . Diagnostics ;
2+ using GitExtensions . Extensibility ;
23using GitExtensions . Extensibility . Git ;
34
45namespace GitCommands . Git
@@ -28,14 +29,13 @@ public IReadOnlyList<GitItemStatus> Parse(string getAllChangedFilesCommandOutput
2829 }
2930
3031 /// <summary>
31- /// Parse git-status --porcelain=1 and git- diff --name-status
32- /// Outputs are similar, except that git-status has status for both worktree and index.
32+ /// Parse git-diff --raw
33+ /// format is similar to " git-status --porcelain=1 -z" and "git diff --name-status", no longer supported
3334 /// </summary>
3435 /// <param name="getAllChangedFilesCommandOutput">An output of <see cref="Commands.GetAllChangedFiles"/> command.</param>
35- /// <param name="fromDiff">Parse git-diff.</param>
3636 /// <param name="staged">The staged status <see cref="GitItemStatus"/>, only relevant for git-diff (parsed for git-status).</param>
3737 /// <returns>list with the git items.</returns>
38- internal List < GitItemStatus > GetAllChangedFilesFromString_v1 ( string getAllChangedFilesCommandOutput , bool fromDiff , StagedStatus staged )
38+ internal List < GitItemStatus > GetDiffChangedFilesFromString ( string getAllChangedFilesCommandOutput , StagedStatus staged )
3939 {
4040 List < GitItemStatus > diffFiles = [ ] ;
4141
@@ -44,113 +44,69 @@ internal List<GitItemStatus> GetAllChangedFilesFromString_v1(string getAllChange
4444 return diffFiles ;
4545 }
4646
47- string trimmedStatus = RemoveWarnings ( getAllChangedFilesCommandOutput ) ;
48-
49- // Doesn't work with removed submodules
50- IReadOnlyList < string > submodules = GetModule ( ) . GetSubmodulesLocalPaths ( ) ;
51-
5247 // Split all files on '\0' (WE NEED ALL COMMANDS TO BE RUN WITH -z! THIS IS ALSO IMPORTANT FOR ENCODING ISSUES!)
53- string [ ] files = trimmedStatus . Split ( Delimiters . Null , StringSplitOptions . RemoveEmptyEntries ) ;
54- for ( int n = 0 ; n < files . Length ; n ++ )
48+ string [ ] files = getAllChangedFilesCommandOutput . Split ( Delimiters . Null , StringSplitOptions . RemoveEmptyEntries ) ;
49+ for ( int n = 0 ; n < files . Length ; ++ n )
5550 {
5651 if ( string . IsNullOrEmpty ( files [ n ] ) )
5752 {
5853 continue ;
5954 }
6055
61- int splitIndex ;
62- if ( fromDiff )
63- {
64- splitIndex = - 1 ;
65- }
66- else
56+ if ( n >= files . Length - 1 )
6757 {
68- // Note that this fails for files with spaces (git-status --porcelain=1 is deprecated)
69- splitIndex = files [ n ] . IndexOfAny ( Delimiters . TabAndSpace , 1 ) ;
58+ // no file name for last entry, ignore
59+ Trace . WriteLine ( $ "No filename for last entry, ignoring: { files [ n ] } ") ;
60+ continue ;
7061 }
7162
72- string status ;
73- string fileName ;
63+ ReadOnlySpan < char > status = files [ n ] . AsSpan ( ) ;
64+ ++ n ;
65+ ReadOnlySpan < char > fileName = files [ n ] . AsSpan ( ) ;
7466
75- if ( splitIndex < 0 )
67+ if ( status [ 0 ] != ':' || status . Length < 15 )
7668 {
77- if ( n >= files . Length - 1 )
78- {
79- // Illegal, ignore
80- continue ;
81- }
82-
83- status = files [ n ] ;
84- fileName = files [ n + 1 ] ;
85- n ++ ;
86- }
87- else
88- {
89- status = files [ n ] [ ..splitIndex ] ;
90- fileName = files [ n ] [ splitIndex ..] ;
69+ Trace . WriteLine ( $ "Illegal status line: { fileName } ") ;
70+ continue ;
9171 }
9272
93- char x = status [ 0 ] ;
94- char y = status . Length > 1 ? status [ 1 ] : GitItemStatusConverter . UnmodifiedStatus_v1 ;
73+ int statusIndex = status . LastIndexOfAnyExceptInRange ( '0' , '9' ) ;
74+ char x = status [ statusIndex ] ;
9575
96- if ( fromDiff && staged == StagedStatus . WorkTree && x == GitItemStatusConverter . UnmergedStatus )
76+ if ( staged == StagedStatus . WorkTree && x == GitItemStatusConverter . UnmergedStatus )
9777 {
9878 // git-diff has two lines to inform that a file is modified and has a merge conflict
9979 continue ;
10080 }
10181
102- // Skip unmerged where both are modified: Only worktree interesting.
103- if ( ( x != GitItemStatusConverter . UntrackedStatus && x != GitItemStatusConverter . IgnoredStatus && x != GitItemStatusConverter . UnmodifiedStatus_v1 )
104- || x != GitItemStatusConverter . UnmergedStatus
105- || y != GitItemStatusConverter . UnmergedStatus )
82+ GitItemStatus gitItemStatusX = GitItemStatusConverter . FromStatusCharacter ( staged , fileName . ToString ( ) , x ) ;
83+ if ( x is GitItemStatusConverter . RenamedStatus or GitItemStatusConverter . CopiedStatus )
10684 {
107- GitItemStatus gitItemStatusX ;
108- StagedStatus stagedX = fromDiff ? staged : StagedStatus . Index ;
109- if ( x == GitItemStatusConverter . RenamedStatus || x == GitItemStatusConverter . CopiedStatus )
85+ gitItemStatusX . RenameCopyPercentage = status [ ( statusIndex + 1 ) ..] . ToString ( ) ;
86+ gitItemStatusX . OldName = gitItemStatusX . Name ;
87+
88+ if ( n + 1 >= files . Length )
11089 {
111- // Find renamed files...
112- string nextFile = n + 1 < files . Length ? files [ n + 1 ] : "" ;
113- gitItemStatusX = GitItemStatusFromCopyRename ( stagedX , fromDiff , nextFile , fileName , x , status ) ;
114- n ++ ;
90+ Trace . WriteLine ( $ "No next file for renamed entry: { status } ::{ gitItemStatusX . Name } ") ;
11591 }
11692 else
11793 {
118- gitItemStatusX = GitItemStatusConverter . FromStatusCharacter ( stagedX , fileName , x ) ;
94+ // Renamed file in an extra entry
95+ // No check if the entry seem to be a file (if it starts with '.' it is likely next status)
96+ ++ n ;
97+ gitItemStatusX . Name = files [ n ] . ToString ( ) ;
11998 }
120-
121- if ( submodules . Contains ( gitItemStatusX . Name ) )
122- {
123- gitItemStatusX . IsSubmodule = true ;
124- }
125-
126- diffFiles . Add ( gitItemStatusX ) ;
127- }
128-
129- if ( fromDiff || y == GitItemStatusConverter . UnmodifiedStatus_v1 )
130- {
131- continue ;
13299 }
133100
134- GitItemStatus gitItemStatusY ;
135- StagedStatus stagedY = StagedStatus . WorkTree ;
136- if ( y == GitItemStatusConverter . RenamedStatus || y == GitItemStatusConverter . CopiedStatus )
137- {
138- // Find renamed files...
139- string nextFile = n + 1 < files . Length ? files [ n + 1 ] : "" ;
140- gitItemStatusY = GitItemStatusFromCopyRename ( stagedY , false , nextFile , fileName , y , status ) ;
141- n ++ ;
142- }
143- else
101+ const string subMode = "160000" ;
102+ if ( status . Slice ( 1 , 6 ) . SequenceEqual ( subMode ) || status . Slice ( 8 , 6 ) . SequenceEqual ( subMode ) )
144103 {
145- gitItemStatusY = GitItemStatusConverter . FromStatusCharacter ( stagedY , fileName , y ) ;
104+ gitItemStatusX . IsSubmodule = true ;
146105 }
147106
148- if ( submodules . Contains ( gitItemStatusY . Name ) )
149- {
150- gitItemStatusY . IsSubmodule = true ;
151- }
107+ // ignore the sha in status (could be used to set TreeGuid)
152108
153- diffFiles . Add ( gitItemStatusY ) ;
109+ diffFiles . Add ( gitItemStatusX ) ;
154110 }
155111
156112 return diffFiles ;
@@ -170,16 +126,15 @@ private static IReadOnlyList<GitItemStatus> GetAllChangedFilesFromString_v2(stri
170126 return diffFiles ;
171127 }
172128
173- string trimmedStatus = RemoveWarnings ( getAllChangedFilesCommandOutput ) ;
174-
175129 // Split all files on '\0'
176- string [ ] files = trimmedStatus . Split ( Delimiters . Null , StringSplitOptions . RemoveEmptyEntries ) ;
177- for ( int n = 0 ; n < files . Length ; n ++ )
130+ string [ ] files = getAllChangedFilesCommandOutput . Split ( Delimiters . Null , StringSplitOptions . RemoveEmptyEntries ) ;
131+ for ( int n = 0 ; n < files . Length ; ++ n )
178132 {
179- string line = files [ n ] ;
180- if ( line . Length <= 2 || line [ 1 ] != ' ' )
133+ ReadOnlySpan < char > line = files [ n ] . AsSpan ( ) ;
134+ if ( line . Length <= 2 || line [ 1 ] != ' ' || line [ 0 ] == '#' )
181135 {
182- // Illegal info, like error output
136+ // Illegal info, like error output,
137+ // except '#' that is an ignored header
183138 continue ;
184139 }
185140
@@ -188,7 +143,7 @@ private static IReadOnlyList<GitItemStatus> GetAllChangedFilesFromString_v2(stri
188143 if ( entryType == GitItemStatusConverter . UntrackedStatus || entryType == GitItemStatusConverter . IgnoredStatus )
189144 {
190145 // Untracked and ignored with just the path following, supply dummy data for most info.
191- string otherFileName = line [ 2 ..] ;
146+ string otherFileName = line [ 2 ..] . ToString ( ) ;
192147 const string NotSumoduleEntry = "N..." ;
193148 UpdateItemStatus ( entryType , false , NotSumoduleEntry , otherFileName , null , null ) ;
194149 continue ;
@@ -217,27 +172,28 @@ private static IReadOnlyList<GitItemStatus> GetAllChangedFilesFromString_v2(stri
217172 string fileName ;
218173 string ? oldFileName = null ;
219174 string ? renamePercent = null ;
220- string subm = line . Substring ( 5 , 4 ) ;
175+ string subm = line [ 5 .. 9 ] . ToString ( ) ;
221176
222177 if ( entryType == OrdinaryEntry )
223178 {
224- DebugHelpers . Assert ( line . Length > 113 , "Cannot parse line:" + line ) ;
225- fileName = line [ 113 ..] ;
179+ DebugHelpers . Assert ( line . Length > 113 , $ "Cannot parse line: { line } " ) ;
180+ fileName = line [ 113 ..] . ToString ( ) ;
226181 }
227182 else if ( entryType == RenamedEntry )
228183 {
229- DebugHelpers . Assert ( n + 1 < files . Length , "Cannot parse renamed:" + line ) ;
184+ DebugHelpers . Assert ( n + 1 < files . Length , "Cannot parse renamed: { line}" ) ;
230185
231186 // Find renamed files...
232- string [ ] renames = line [ 114 ..] . Split ( Delimiters . Space , 2 ) ;
233- renamePercent = renames [ 0 ] ;
234- fileName = renames [ 1 ] ;
187+ int pos = line [ 114 ..] . IndexOfAnyExceptInRange ( '0' , '9' ) ;
188+ DebugHelpers . Assert ( pos >= 0 , "Cannot find space separating rename: {line}" ) ;
189+ renamePercent = line [ 114 ..( 114 + pos ) ] . ToString ( ) ;
190+ fileName = line [ ( 114 + pos + 1 ) ..] . ToString ( ) ;
235191 oldFileName = files [ ++ n ] ;
236192 }
237193 else if ( entryType == UnmergedEntry )
238194 {
239- DebugHelpers . Assert ( line . Length > 161 , "Cannot parse unmerged:" + line ) ;
240- fileName = line [ 161 ..] ;
195+ DebugHelpers . Assert ( line . Length > 161 , $ "Cannot parse line: { line } " ) ;
196+ fileName = line [ 161 ..] . ToString ( ) ;
241197 }
242198 else
243199 {
@@ -292,79 +248,5 @@ void UpdateItemStatus(char x, bool isIndex, string subm, string fileName, string
292248 diffFiles . Add ( gitItemStatus ) ;
293249 }
294250 }
295-
296- private static GitItemStatus GitItemStatusFromCopyRename ( StagedStatus staged , bool fromDiff , string nextFile , string fileName , char x , string status )
297- {
298- GitItemStatus gitItemStatus ;
299-
300- // Find renamed files...
301- if ( fromDiff )
302- {
303- gitItemStatus = new GitItemStatus ( name : nextFile ) ;
304- gitItemStatus . OldName = fileName ;
305- }
306- else
307- {
308- gitItemStatus = new GitItemStatus ( name : fileName ) ;
309- gitItemStatus . OldName = nextFile ;
310- }
311-
312- gitItemStatus . IsNew = false ;
313- gitItemStatus . IsChanged = false ;
314- gitItemStatus . IsDeleted = false ;
315- if ( x == GitItemStatusConverter . RenamedStatus )
316- {
317- gitItemStatus . IsRenamed = true ;
318- }
319- else
320- {
321- gitItemStatus . IsCopied = true ;
322- }
323-
324- gitItemStatus . IsTracked = true ;
325- if ( status . Length > 2 )
326- {
327- gitItemStatus . RenameCopyPercentage = status [ 1 ..] ;
328- }
329-
330- gitItemStatus . Staged = staged ;
331-
332- return gitItemStatus ;
333- }
334-
335- private IGitModule GetModule ( )
336- {
337- IGitModule module = _getModule ( ) ;
338-
339- if ( module is null )
340- {
341- throw new ArgumentException ( $ "Require a valid instance of { nameof ( IGitModule ) } ") ;
342- }
343-
344- return module ;
345- }
346-
347- private static string RemoveWarnings ( string statusString )
348- {
349- // The status string from git-diff can show warnings. See tests
350- string trimmedStatus = statusString ;
351- int lastNewLinePos = trimmedStatus . LastIndexOfAny ( Delimiters . LineFeedAndCarriageReturn ) ;
352- while ( lastNewLinePos >= 0 )
353- {
354- if ( lastNewLinePos == 0 )
355- {
356- trimmedStatus = trimmedStatus . Remove ( 0 , 1 ) ;
357- break ;
358- }
359-
360- // Error always end with \n and start at previous index
361- int ind = trimmedStatus . LastIndexOfAny ( Delimiters . LineFeedAndCarriageReturnAndNull , lastNewLinePos - 1 ) ;
362-
363- trimmedStatus = trimmedStatus . Remove ( ind + 1 , lastNewLinePos - ind ) ;
364- lastNewLinePos = trimmedStatus . LastIndexOfAny ( Delimiters . LineFeedAndCarriageReturn ) ;
365- }
366-
367- return trimmedStatus ;
368- }
369251 }
370252}
0 commit comments