@@ -2,7 +2,6 @@ package git_commands
22
33import (
44 "fmt"
5- "os"
65 "path/filepath"
76 "regexp"
87 "strings"
@@ -116,64 +115,124 @@ func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File)
116115 return beforeFile , afterFile , nil
117116}
118117
119- // DiscardAllFileChanges directly
120- func (self * WorkingTreeCommands ) DiscardAllFileChanges (file * models.File ) error {
121- if file .IsRename () {
122- beforeFile , afterFile , err := self .BeforeAndAfterFileForRename (file )
123- if err != nil {
124- return err
118+ // DiscardAllFilesChanges discards changes for multiple files in batch
119+ func (self * WorkingTreeCommands ) DiscardAllFilesChanges (files []* models.File ) error {
120+ // Group files by their discard strategy
121+ var (
122+ aaStatusFiles []* models.File
123+ duStatusFiles []* models.File
124+ filesToReset []* models.File
125+ addedFilesToRemove []* models.File
126+ filesToCheckout []* models.File
127+ )
128+
129+ // Helper function to categorize a file into the appropriate group
130+ categorizeFile := func (file * models.File ) {
131+ if file .ShortStatus == "AA" {
132+ aaStatusFiles = append (aaStatusFiles , file )
133+ return
125134 }
126135
127- if err := self .DiscardAllFileChanges (beforeFile ); err != nil {
128- return err
136+ if file .ShortStatus == "DU" {
137+ duStatusFiles = append (duStatusFiles , file )
138+ return
129139 }
130140
131- if err := self .DiscardAllFileChanges (afterFile ); err != nil {
132- return err
141+ // Track which files need to be reset first
142+ needsReset := file .HasStagedChanges || file .HasMergeConflicts
143+ if needsReset {
144+ filesToReset = append (filesToReset , file )
145+ }
146+
147+ if file .ShortStatus == "DD" || file .ShortStatus == "AU" {
148+ } else if file .Added {
149+ addedFilesToRemove = append (addedFilesToRemove , file )
150+ } else {
151+ filesToCheckout = append (filesToCheckout , file )
152+ }
153+ }
154+
155+ for _ , file := range files {
156+ if file .IsRename () {
157+ // Get the before and after files for the rename and add them to the appropriate groups
158+ beforeFile , afterFile , err := self .BeforeAndAfterFileForRename (file )
159+ if err != nil {
160+ return err
161+ }
162+ categorizeFile (beforeFile )
163+ categorizeFile (afterFile )
164+ continue
133165 }
134166
135- return nil
167+ categorizeFile ( file )
136168 }
137169
138- if file .ShortStatus == "AA" {
170+ // Batch reset files that need resetting
171+ if len (filesToReset ) > 0 {
172+ paths := make ([]string , len (filesToReset ))
173+ for i , file := range filesToReset {
174+ paths [i ] = file .Path
175+ }
139176 if err := self .cmd .New (
140- NewGitCmd ("checkout " ).Arg ("--ours" , "--" , file . Path ).ToArgv (),
177+ NewGitCmd ("reset " ).Arg ("--" ). Arg ( paths ... ).ToArgv (),
141178 ).Run (); err != nil {
142179 return err
143180 }
181+ }
144182
183+ // Batch remove DU status files
184+ if len (duStatusFiles ) > 0 {
185+ paths := make ([]string , len (duStatusFiles ))
186+ for i , file := range duStatusFiles {
187+ paths [i ] = file .Path
188+ }
145189 if err := self .cmd .New (
146- NewGitCmd ("add " ).Arg ("--" , file . Path ).ToArgv (),
190+ NewGitCmd ("rm " ).Arg ("--" ). Arg ( paths ... ).ToArgv (),
147191 ).Run (); err != nil {
148192 return err
149193 }
150- return nil
151- }
152-
153- if file .ShortStatus == "DU" {
154- return self .cmd .New (
155- NewGitCmd ("rm" ).Arg ("--" , file .Path ).ToArgv (),
156- ).Run ()
157194 }
158195
159- // if the file isn't tracked, we assume you want to delete it
160- if file .HasStagedChanges || file .HasMergeConflicts {
196+ // Batch checkout --ours for AA status files
197+ if len (aaStatusFiles ) > 0 {
198+ paths := make ([]string , len (aaStatusFiles ))
199+ for i , file := range aaStatusFiles {
200+ paths [i ] = file .Path
201+ }
202+ if err := self .cmd .New (
203+ NewGitCmd ("checkout" ).Arg ("--ours" , "--" ).Arg (paths ... ).ToArgv (),
204+ ).Run (); err != nil {
205+ return err
206+ }
207+ // Stage them after checkout
161208 if err := self .cmd .New (
162- NewGitCmd ("reset " ).Arg ("--" , file . Path ).ToArgv (),
209+ NewGitCmd ("add " ).Arg ("--" ). Arg ( paths ... ).ToArgv (),
163210 ).Run (); err != nil {
164211 return err
165212 }
166213 }
167214
168- if file .ShortStatus == "DD" || file .ShortStatus == "AU" {
169- return nil
215+ // Remove added files from filesystem
216+ for _ , file := range addedFilesToRemove {
217+ if err := self .os .RemoveFile (file .Path ); err != nil {
218+ return err
219+ }
170220 }
171221
172- if file .Added {
173- return self .os .RemoveFile (file .Path )
222+ // Batch checkout other files
223+ if len (filesToCheckout ) > 0 {
224+ paths := make ([]string , len (filesToCheckout ))
225+ for i , file := range filesToCheckout {
226+ paths [i ] = file .Path
227+ }
228+ if err := self .cmd .New (
229+ NewGitCmd ("checkout" ).Arg ("--" ).Arg (paths ... ).ToArgv (),
230+ ).Run (); err != nil {
231+ return err
232+ }
174233 }
175234
176- return self . DiscardUnstagedFileChanges ( file )
235+ return nil
177236}
178237
179238type IFileNode interface {
@@ -184,55 +243,45 @@ type IFileNode interface {
184243 GetFile () * models.File
185244}
186245
187- func (self * WorkingTreeCommands ) DiscardAllDirChanges (node IFileNode ) error {
188- // this could be more efficient but we would need to handle all the edge cases
189- return node .ForEachFile (self .DiscardAllFileChanges )
190- }
191-
192- func (self * WorkingTreeCommands ) DiscardUnstagedDirChanges (node IFileNode ) error {
193- file := node .GetFile ()
194- if file == nil {
195- if err := self .RemoveUntrackedDirFiles (node ); err != nil {
196- return err
197- }
246+ // DiscardUnstagedFilesChanges discards unstaged changes for multiple files in batch
247+ func (self * WorkingTreeCommands ) DiscardUnstagedFilesChanges (files []* models.File ) error {
248+ var (
249+ addedFilesToRemove []* models.File
250+ trackedFilesToCheckout []* models.File
251+ )
198252
199- cmdArgs := NewGitCmd ("checkout" ).Arg ("--" , node .GetPath ()).ToArgv ()
200- if err := self .cmd .New (cmdArgs ).Run (); err != nil {
201- return err
202- }
203- } else {
253+ for _ , file := range files {
254+ // Only remove files that are added but not staged
204255 if file .Added && ! file .HasStagedChanges {
205- return self .os .RemoveFile (file .Path )
256+ addedFilesToRemove = append (addedFilesToRemove , file )
257+ } else {
258+ // Checkout tracked files to discard unstaged changes
259+ trackedFilesToCheckout = append (trackedFilesToCheckout , file )
206260 }
261+ }
207262
208- if err := self .DiscardUnstagedFileChanges (file ); err != nil {
263+ // Remove added files from filesystem
264+ for _ , file := range addedFilesToRemove {
265+ if err := self .os .RemoveFile (file .Path ); err != nil {
209266 return err
210267 }
211268 }
212269
213- return nil
214- }
215-
216- func (self * WorkingTreeCommands ) RemoveUntrackedDirFiles (node IFileNode ) error {
217- untrackedFilePaths := node .GetFilePathsMatching (
218- func (file * models.File ) bool { return ! file .GetIsTracked () },
219- )
220-
221- for _ , path := range untrackedFilePaths {
222- err := os .Remove (path )
223- if err != nil {
270+ // Batch checkout tracked files
271+ if len (trackedFilesToCheckout ) > 0 {
272+ paths := make ([]string , len (trackedFilesToCheckout ))
273+ for i , file := range trackedFilesToCheckout {
274+ paths [i ] = file .Path
275+ }
276+ cmdArgs := NewGitCmd ("checkout" ).Arg ("--" ).Arg (paths ... ).ToArgv ()
277+ if err := self .cmd .New (cmdArgs ).Run (); err != nil {
224278 return err
225279 }
226280 }
227281
228282 return nil
229283}
230284
231- func (self * WorkingTreeCommands ) DiscardUnstagedFileChanges (file * models.File ) error {
232- cmdArgs := NewGitCmd ("checkout" ).Arg ("--" , file .Path ).ToArgv ()
233- return self .cmd .New (cmdArgs ).Run ()
234- }
235-
236285// Escapes special characters in a filename for gitignore and exclude files
237286func escapeFilename (filename string ) string {
238287 re := regexp .MustCompile (`^[!#]|[\[\]*]` )
0 commit comments