@@ -148,97 +148,22 @@ func (f *Format) Run() (err error) {
148
148
// we use a multiple of batch size here as a rudimentary concurrency optimization based on the host machine
149
149
f .filesCh = make (chan * walk.File , BatchSize * runtime .NumCPU ())
150
150
151
+ // create a channel for files that have been formatted
152
+ f .formattedCh = make (chan * walk.File , cap (f .filesCh ))
153
+
151
154
// create a channel for files that have been processed
152
155
f .processedCh = make (chan * walk.File , cap (f .filesCh ))
153
156
154
157
// start concurrent processing tasks in reverse order
155
- if ! f .NoCache {
156
- eg .Go (f .updateCache (ctx ))
157
- }
158
+ eg .Go (f .updateCache (ctx ))
159
+ eg .Go (f .detectFormatted (ctx ))
158
160
eg .Go (f .applyFormatters (ctx ))
159
161
eg .Go (f .walkFilesystem (ctx ))
160
162
161
163
// wait for everything to complete
162
164
return eg .Wait ()
163
165
}
164
166
165
- func (f * Format ) updateCache (ctx context.Context ) func () error {
166
- return func () error {
167
- // used to batch updates for more efficient txs
168
- batch := make ([]* walk.File , 0 , BatchSize )
169
-
170
- // apply a batch
171
- processBatch := func () error {
172
- if f .Stdin {
173
- // do nothing
174
- return nil
175
- }
176
- if err := cache .Update (batch ); err != nil {
177
- return err
178
- }
179
- batch = batch [:0 ]
180
- return nil
181
- }
182
-
183
- LOOP:
184
- for {
185
- select {
186
- // detect ctx cancellation
187
- case <- ctx .Done ():
188
- return ctx .Err ()
189
- // respond to processed files
190
- case file , ok := <- f .processedCh :
191
- if ! ok {
192
- // channel has been closed, no further files to process
193
- break LOOP
194
- }
195
-
196
- if f .Stdin {
197
- // dump file into stdout
198
- f , err := os .Open (file .Path )
199
- if err != nil {
200
- return fmt .Errorf ("failed to open %s: %w" , file .Path , err )
201
- }
202
- if _ , err = io .Copy (os .Stdout , f ); err != nil {
203
- return fmt .Errorf ("failed to copy %s to stdout: %w" , file .Path , err )
204
- }
205
- if err = os .Remove (f .Name ()); err != nil {
206
- return fmt .Errorf ("failed to remove temp file %s: %w" , file .Path , err )
207
- }
208
-
209
- stats .Add (stats .Formatted , 1 )
210
- continue
211
- }
212
-
213
- // append to batch and process if we have enough
214
- batch = append (batch , file )
215
- if len (batch ) == BatchSize {
216
- if err := processBatch (); err != nil {
217
- return err
218
- }
219
- }
220
- }
221
- }
222
-
223
- // final flush
224
- if err := processBatch (); err != nil {
225
- return err
226
- }
227
-
228
- // if fail on change has been enabled, check that no files were actually formatted, throwing an error if so
229
- if f .FailOnChange && stats .Value (stats .Formatted ) != 0 {
230
- return ErrFailOnChange
231
- }
232
-
233
- // print stats to stdout unless we are processing stdin and printing the results to stdout
234
- if ! f .Stdin {
235
- stats .Print ()
236
- }
237
-
238
- return nil
239
- }
240
- }
241
-
242
167
func (f * Format ) walkFilesystem (ctx context.Context ) func () error {
243
168
return func () error {
244
169
eg , ctx := errgroup .WithContext (ctx )
@@ -368,9 +293,9 @@ func (f *Format) applyFormatters(ctx context.Context) func() error {
368
293
}
369
294
}
370
295
371
- // pass each file to the processed channel
296
+ // pass each file to the formatted channel
372
297
for _ , task := range tasks {
373
- f .processedCh <- task .File
298
+ f .formattedCh <- task .File
374
299
}
375
300
376
301
return nil
@@ -392,7 +317,7 @@ func (f *Format) applyFormatters(ctx context.Context) func() error {
392
317
return func () error {
393
318
defer func () {
394
319
// close processed channel
395
- close (f .processedCh )
320
+ close (f .formattedCh )
396
321
}()
397
322
398
323
// iterate the files channel
@@ -402,7 +327,7 @@ func (f *Format) applyFormatters(ctx context.Context) func() error {
402
327
if format .PathMatches (file .RelPath , f .globalExcludes ) {
403
328
log .Debugf ("path matched global excludes: %s" , file .RelPath )
404
329
// mark it as processed and continue to the next
405
- f .processedCh <- file
330
+ f .formattedCh <- file
406
331
continue
407
332
}
408
333
@@ -421,7 +346,7 @@ func (f *Format) applyFormatters(ctx context.Context) func() error {
421
346
}
422
347
log .Logf (f .OnUnmatched , "no formatter for path: %s" , file .RelPath )
423
348
// mark it as processed and continue to the next
424
- f .processedCh <- file
349
+ f .formattedCh <- file
425
350
} else {
426
351
// record the match
427
352
stats .Add (stats .Matched , 1 )
@@ -444,6 +369,129 @@ func (f *Format) applyFormatters(ctx context.Context) func() error {
444
369
}
445
370
}
446
371
372
+ func (f * Format ) detectFormatted (ctx context.Context ) func () error {
373
+ return func () error {
374
+ defer func () {
375
+ // close formatted channel
376
+ close (f .processedCh )
377
+ }()
378
+
379
+ for {
380
+ select {
381
+
382
+ // detect ctx cancellation
383
+ case <- ctx .Done ():
384
+ return ctx .Err ()
385
+ // take the next file that has been processed
386
+ case file , ok := <- f .formattedCh :
387
+ if ! ok {
388
+ // channel has been closed, no further files to process
389
+ return nil
390
+ }
391
+
392
+ // look up current file info
393
+ currentInfo , err := os .Stat (file .Path )
394
+ if err != nil {
395
+ return fmt .Errorf ("failed to stat processed file: %w" , err )
396
+ }
397
+
398
+ // check if the file has changed
399
+ if ! (file .Info .ModTime () == currentInfo .ModTime () && file .Info .Size () == currentInfo .Size ()) {
400
+ // record the change
401
+ stats .Add (stats .Formatted , 1 )
402
+ // log the change for diagnostics
403
+ log .Debugf ("file has been changed: %s" , file .Path )
404
+ // update the file info
405
+ file .Info = currentInfo
406
+ }
407
+
408
+ // mark as processed
409
+ f .processedCh <- file
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ func (f * Format ) updateCache (ctx context.Context ) func () error {
416
+ return func () error {
417
+ // used to batch updates for more efficient txs
418
+ batch := make ([]* walk.File , 0 , BatchSize )
419
+
420
+ // apply a batch
421
+ processBatch := func () error {
422
+ // pass the batch to the cache for updating
423
+ if err := cache .Update (batch ); err != nil {
424
+ return err
425
+ }
426
+ batch = batch [:0 ]
427
+ return nil
428
+ }
429
+
430
+ // if we are processing from stdin that means we are outputting to stdout, no caching involved
431
+ // if f.NoCache is set that means either the user explicitly disabled the cache or we failed to open on
432
+ if f .Stdin || f .NoCache {
433
+ // do nothing
434
+ processBatch = func () error { return nil }
435
+ }
436
+
437
+ LOOP:
438
+ for {
439
+ select {
440
+ // detect ctx cancellation
441
+ case <- ctx .Done ():
442
+ return ctx .Err ()
443
+ // respond to formatted files
444
+ case file , ok := <- f .processedCh :
445
+ if ! ok {
446
+ // channel has been closed, no further files to process
447
+ break LOOP
448
+ }
449
+
450
+ if f .Stdin {
451
+ // dump file into stdout
452
+ f , err := os .Open (file .Path )
453
+ if err != nil {
454
+ return fmt .Errorf ("failed to open %s: %w" , file .Path , err )
455
+ }
456
+ if _ , err = io .Copy (os .Stdout , f ); err != nil {
457
+ return fmt .Errorf ("failed to copy %s to stdout: %w" , file .Path , err )
458
+ }
459
+ if err = os .Remove (f .Name ()); err != nil {
460
+ return fmt .Errorf ("failed to remove temp file %s: %w" , file .Path , err )
461
+ }
462
+
463
+ continue
464
+ }
465
+
466
+ // append to batch and process if we have enough
467
+ batch = append (batch , file )
468
+ if len (batch ) == BatchSize {
469
+ if err := processBatch (); err != nil {
470
+ return err
471
+ }
472
+ }
473
+ }
474
+ }
475
+
476
+ // final flush
477
+ if err := processBatch (); err != nil {
478
+ return err
479
+ }
480
+
481
+ // if fail on change has been enabled, check that no files were actually formatted, throwing an error if so
482
+ if f .FailOnChange && stats .Value (stats .Formatted ) != 0 {
483
+ return ErrFailOnChange
484
+ }
485
+
486
+ // print stats to stdout unless we are processing stdin and printing the results to stdout
487
+ if ! f .Stdin {
488
+ stats .Print ()
489
+ }
490
+
491
+ return nil
492
+ }
493
+ }
494
+
447
495
func findUp (searchDir string , fileName string ) (path string , dir string , err error ) {
448
496
for _ , dir := range eachDir (searchDir ) {
449
497
path := filepath .Join (dir , fileName )
0 commit comments