Skip to content

Commit 71b262f

Browse files
committed
feat: match before checking cache
This changes the flow of processing to make unmatched behaviour more consistent. Before, we had been: - traversing the filesystem - comparing with the cache and only emitting files which had changed - applying the matching rules to determine which formatters should be applied to a given file - applying the formatters Now, we do the following: - traverse the filesystem - apply the matching rules to determine which formatters should be applied to a given file - compare with the cache and only emit files which have changed for formatting - apply the formatters It does mean we are applying the matching rules against files which we may not have to format, but in testing against Nixpkgs the performance impact appears negligible. This makes sense since most of the processing time will be spent in the formatters, not applying some globs to file paths. Signed-off-by: Brian McGee <[email protected]>
1 parent ed8979e commit 71b262f

File tree

6 files changed

+503
-100
lines changed

6 files changed

+503
-100
lines changed

cmd/format/format.go

+61-38
Original file line numberDiff line numberDiff line change
@@ -205,33 +205,34 @@ func Run(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, paths []string)
205205
return fmt.Errorf("failed to create walker: %w", err)
206206
}
207207

208+
// start traversing
208209
files := make([]*walk.File, BatchSize)
210+
209211
for {
212+
// read the next batch
210213
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
211-
212214
n, err := reader.Read(ctx, files)
213215

214-
for idx := 0; idx < n; idx++ {
215-
file := files[idx]
216+
// ensure context is cancelled to release resources
217+
cancel()
216218

217-
// check if this file is new or has changed when compared to the cache entry
218-
if file.Cache == nil || file.Cache.HasChanged(file.Info) {
219-
filesCh <- file
220-
statz.Add(stats.Emitted, 1)
221-
}
219+
// pass each file into the file channel for processing
220+
for idx := 0; idx < n; idx++ {
221+
filesCh <- files[idx]
222222
}
223223

224-
cancel()
225-
226224
if errors.Is(err, io.EOF) {
225+
// we have finished traversing
227226
break
228227
} else if err != nil {
228+
// something went wrong
229229
log.Errorf("failed to read files: %v", err)
230230
cancel()
231231
break
232232
}
233233
}
234234

235+
// indicate no further files for processing
235236
close(filesCh)
236237

237238
// wait for everything to complete
@@ -263,6 +264,8 @@ func applyFormatters(
263264
// formatters which should be applied to their respective files
264265
batches := make(map[string][]*format.Task)
265266

267+
// apply check if the given batch key has enough tasks to trigger processing
268+
// flush is used to force processing regardless of the number of tasks
266269
apply := func(key string, flush bool) {
267270
// lookup the batch and exit early if it's empty
268271
batch := batches[key]
@@ -304,6 +307,7 @@ func applyFormatters(
304307
}
305308
}
306309

310+
// tryApply batches tasks by their batch key and processes the batch if there is enough ready
307311
tryApply := func(task *format.Task) {
308312
// append to batch
309313
key := task.BatchKey
@@ -314,53 +318,68 @@ func applyFormatters(
314318

315319
return func() error {
316320
defer func() {
317-
// close processed channel
321+
// indicate processing has finished
318322
close(formattedCh)
319323
}()
320324

325+
// parse unmatched log level
321326
unmatchedLevel, err := log.ParseLevel(cfg.OnUnmatched)
322327
if err != nil {
323328
return fmt.Errorf("invalid on-unmatched value: %w", err)
324329
}
325330

326-
// iterate the files channel
331+
// iterate the file channel
327332
for file := range filesCh {
328333

334+
// a list of formatters that match this file
335+
var matches []*format.Formatter
336+
329337
// first check if this file has been globally excluded
330338
if format.PathMatches(file.RelPath, globalExcludes) {
331339
log.Debugf("path matched global excludes: %s", file.RelPath)
332-
// mark it as processed and continue to the next
333-
formattedCh <- &format.Task{
334-
File: file,
340+
} else {
341+
// otherwise, check if any formatters are interested in it
342+
for _, formatter := range formatters {
343+
if formatter.Wants(file) {
344+
matches = append(matches, formatter)
345+
}
335346
}
336-
continue
337347
}
338348

339-
// check if any formatters are interested in this file
340-
var matches []*format.Formatter
341-
for _, formatter := range formatters {
342-
if formatter.Wants(file) {
343-
matches = append(matches, formatter)
344-
}
345-
}
349+
// indicates no further processing
350+
var release bool
346351

347-
// see if any formatters matched
352+
// check if there were no matches
348353
if len(matches) == 0 {
349-
354+
// log that there was no match, exiting with an error if the unmatched level was set to fatal
350355
if unmatchedLevel == log.FatalLevel {
351356
return fmt.Errorf("no formatter for path: %s", file.RelPath)
352357
}
358+
353359
log.Logf(unmatchedLevel, "no formatter for path: %s", file.RelPath)
354-
// mark it as processed and continue to the next
355-
formattedCh <- &format.Task{
356-
File: file,
357-
}
360+
361+
// no further processing
362+
release = true
358363
} else {
359-
// record the match
364+
// record there was a match
360365
statz.Add(stats.Matched, 1)
361-
// create a new format task, add it to a batch based on its batch key and try to apply if the batch is full
362-
task := format.NewTask(file, matches)
363-
tryApply(&task)
366+
367+
// check if the file is new or has changed when compared to the cache entry
368+
if file.Cache == nil || file.Cache.HasChanged(file.Info) {
369+
// if so, generate a format task, add it to the relevant batch (by batch key) and try to process
370+
task := format.NewTask(file, matches)
371+
tryApply(&task)
372+
} else {
373+
// indicate no further processing
374+
release = true
375+
}
376+
}
377+
378+
if release {
379+
// release the file as there is no more processing to be done on it
380+
if err := file.Release(); err != nil {
381+
return fmt.Errorf("failed to release file: %w", err)
382+
}
364383
}
365384
}
366385

@@ -398,16 +417,20 @@ func postProcessing(
398417
break LOOP
399418
}
400419

401-
// check if the file has changed
420+
// grab the underlying file reference
402421
file := task.File
422+
423+
// check if the file has changed
403424
changed, newInfo, err := file.Stat()
404425
if err != nil {
405426
return err
406427
}
407428

429+
statz.Add(stats.Formatted, 1)
430+
408431
if changed {
409-
// record the change
410-
statz.Add(stats.Formatted, 1)
432+
// record that a change in the underlying file occurred
433+
statz.Add(stats.Changed, 1)
411434

412435
logMethod := log.Debug
413436
if cfg.FailOnChange {
@@ -434,8 +457,8 @@ func postProcessing(
434457
}
435458
}
436459

437-
// if fail on change has been enabled, check that no files were actually formatted, throwing an error if so
438-
if cfg.FailOnChange && statz.Value(stats.Formatted) != 0 {
460+
// if fail on change has been enabled, check that no files were actually changed, throwing an error if so
461+
if cfg.FailOnChange && statz.Value(stats.Changed) != 0 {
439462
return ErrFailOnChange
440463
}
441464

0 commit comments

Comments
 (0)