Skip to content

Commit 65152cb

Browse files
authored
Merge pull request #345 from numtide/fix/fail-on-change
fix: use second precision when comparing file mod times
2 parents 23e563b + 85ce0a2 commit 65152cb

File tree

8 files changed

+110
-52
lines changed

8 files changed

+110
-52
lines changed

cli/format.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"runtime"
1212
"runtime/pprof"
1313
"syscall"
14+
"time"
1415

1516
"git.numtide.com/numtide/treefmt/format"
1617
"git.numtide.com/numtide/treefmt/stats"
@@ -389,20 +390,26 @@ func (f *Format) detectFormatted(ctx context.Context) func() error {
389390
return nil
390391
}
391392

392-
// look up current file info
393-
currentInfo, err := os.Stat(file.Path)
393+
// check if the file has changed
394+
changed, newInfo, err := file.HasChanged()
394395
if err != nil {
395-
return fmt.Errorf("failed to stat processed file: %w", err)
396+
return err
396397
}
397398

398-
// check if the file has changed
399-
if !(file.Info.ModTime() == currentInfo.ModTime() && file.Info.Size() == currentInfo.Size()) {
399+
if changed {
400400
// record the change
401401
stats.Add(stats.Formatted, 1)
402402
// log the change for diagnostics
403-
log.Debugf("file has been changed: %s", file.Path)
403+
log.Debug(
404+
"file has changed",
405+
"path", file.Path,
406+
"prev_size", file.Info.Size(),
407+
"current_size", newInfo.Size(),
408+
"prev_mod_time", file.Info.ModTime().Truncate(time.Second),
409+
"current_mod_time", newInfo.ModTime().Truncate(time.Second),
410+
)
404411
// update the file info
405-
file.Info = currentInfo
412+
file.Info = newInfo
406413
}
407414

408415
// mark as processed

cli/format_test.go

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"path/filepath"
1010
"regexp"
1111
"testing"
12+
"time"
1213

1314
"git.numtide.com/numtide/treefmt/config"
1415
"git.numtide.com/numtide/treefmt/format"
@@ -159,22 +160,22 @@ func TestSpecifyingFormatters(t *testing.T) {
159160
setup()
160161
_, err := cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
161162
as.NoError(err)
162-
assertStats(t, as, 31, 31, 3, 3)
163+
assertStats(t, as, 32, 32, 3, 3)
163164

164165
setup()
165166
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "elm,nix")
166167
as.NoError(err)
167-
assertStats(t, as, 31, 31, 2, 2)
168+
assertStats(t, as, 32, 32, 2, 2)
168169

169170
setup()
170171
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "-f", "ruby,nix")
171172
as.NoError(err)
172-
assertStats(t, as, 31, 31, 2, 2)
173+
assertStats(t, as, 32, 32, 2, 2)
173174

174175
setup()
175176
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "nix")
176177
as.NoError(err)
177-
assertStats(t, as, 31, 31, 1, 1)
178+
assertStats(t, as, 32, 32, 1, 1)
178179

179180
// test bad names
180181
setup()
@@ -204,23 +205,23 @@ func TestIncludesAndExcludes(t *testing.T) {
204205
test.WriteConfig(t, configPath, cfg)
205206
_, err := cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
206207
as.NoError(err)
207-
assertStats(t, as, 31, 31, 31, 0)
208+
assertStats(t, as, 32, 32, 32, 0)
208209

209210
// globally exclude nix files
210211
cfg.Global.Excludes = []string{"*.nix"}
211212

212213
test.WriteConfig(t, configPath, cfg)
213214
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
214215
as.NoError(err)
215-
assertStats(t, as, 31, 31, 30, 0)
216+
assertStats(t, as, 32, 32, 31, 0)
216217

217218
// add haskell files to the global exclude
218219
cfg.Global.Excludes = []string{"*.nix", "*.hs"}
219220

220221
test.WriteConfig(t, configPath, cfg)
221222
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
222223
as.NoError(err)
223-
assertStats(t, as, 31, 31, 24, 0)
224+
assertStats(t, as, 32, 32, 25, 0)
224225

225226
echo := cfg.Formatters["echo"]
226227

@@ -230,31 +231,31 @@ func TestIncludesAndExcludes(t *testing.T) {
230231
test.WriteConfig(t, configPath, cfg)
231232
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
232233
as.NoError(err)
233-
assertStats(t, as, 31, 31, 22, 0)
234+
assertStats(t, as, 32, 32, 23, 0)
234235

235236
// remove go files from the echo formatter
236237
echo.Excludes = []string{"*.py", "*.go"}
237238

238239
test.WriteConfig(t, configPath, cfg)
239240
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
240241
as.NoError(err)
241-
assertStats(t, as, 31, 31, 21, 0)
242+
assertStats(t, as, 32, 32, 22, 0)
242243

243244
// adjust the includes for echo to only include elm files
244245
echo.Includes = []string{"*.elm"}
245246

246247
test.WriteConfig(t, configPath, cfg)
247248
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
248249
as.NoError(err)
249-
assertStats(t, as, 31, 31, 1, 0)
250+
assertStats(t, as, 32, 32, 1, 0)
250251

251252
// add js files to echo formatter
252253
echo.Includes = []string{"*.elm", "*.js"}
253254

254255
test.WriteConfig(t, configPath, cfg)
255256
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
256257
as.NoError(err)
257-
assertStats(t, as, 31, 31, 2, 0)
258+
assertStats(t, as, 32, 32, 2, 0)
258259
}
259260

260261
func TestCache(t *testing.T) {
@@ -281,7 +282,7 @@ func TestCache(t *testing.T) {
281282
test.WriteConfig(t, configPath, cfg)
282283
_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
283284
as.NoError(err)
284-
assertStats(t, as, 31, 31, 31, 0)
285+
assertStats(t, as, 32, 32, 32, 0)
285286

286287
out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
287288
as.NoError(err)
@@ -290,7 +291,7 @@ func TestCache(t *testing.T) {
290291
// clear cache
291292
_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "-c")
292293
as.NoError(err)
293-
assertStats(t, as, 31, 31, 31, 0)
294+
assertStats(t, as, 32, 32, 32, 0)
294295

295296
out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
296297
as.NoError(err)
@@ -299,7 +300,7 @@ func TestCache(t *testing.T) {
299300
// clear cache
300301
_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "-c")
301302
as.NoError(err)
302-
assertStats(t, as, 31, 31, 31, 0)
303+
assertStats(t, as, 32, 32, 32, 0)
303304

304305
out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
305306
as.NoError(err)
@@ -308,7 +309,7 @@ func TestCache(t *testing.T) {
308309
// no cache
309310
_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "--no-cache")
310311
as.NoError(err)
311-
assertStats(t, as, 31, 31, 31, 0)
312+
assertStats(t, as, 32, 32, 32, 0)
312313
}
313314

314315
func TestChangeWorkingDirectory(t *testing.T) {
@@ -342,7 +343,7 @@ func TestChangeWorkingDirectory(t *testing.T) {
342343
// this should fail if the working directory hasn't been changed first
343344
_, err = cmd(t, "-C", tempDir)
344345
as.NoError(err)
345-
assertStats(t, as, 31, 31, 31, 0)
346+
assertStats(t, as, 32, 32, 32, 0)
346347
}
347348

348349
func TestFailOnChange(t *testing.T) {
@@ -365,6 +366,9 @@ func TestFailOnChange(t *testing.T) {
365366
_, err := cmd(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir)
366367
as.ErrorIs(err, ErrFailOnChange)
367368

369+
// we have second precision mod time tracking
370+
time.Sleep(time.Second)
371+
368372
// test with no cache
369373
test.WriteConfig(t, configPath, cfg)
370374
_, err = cmd(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir, "--no-cache")
@@ -411,31 +415,31 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
411415
args := []string{"--config-file", configPath, "--tree-root", tempDir}
412416
_, err := cmd(t, args...)
413417
as.NoError(err)
414-
assertStats(t, as, 31, 31, 3, 0)
418+
assertStats(t, as, 32, 32, 3, 0)
415419

416420
// tweak mod time of elm formatter
417421
as.NoError(test.RecreateSymlink(t, binPath+"/"+"elm-format"))
418422

419423
_, err = cmd(t, args...)
420424
as.NoError(err)
421-
assertStats(t, as, 31, 31, 3, 0)
425+
assertStats(t, as, 32, 32, 3, 0)
422426

423427
// check cache is working
424428
_, err = cmd(t, args...)
425429
as.NoError(err)
426-
assertStats(t, as, 31, 0, 0, 0)
430+
assertStats(t, as, 32, 0, 0, 0)
427431

428432
// tweak mod time of python formatter
429433
as.NoError(test.RecreateSymlink(t, binPath+"/"+"black"))
430434

431435
_, err = cmd(t, args...)
432436
as.NoError(err)
433-
assertStats(t, as, 31, 31, 3, 0)
437+
assertStats(t, as, 32, 32, 3, 0)
434438

435439
// check cache is working
436440
_, err = cmd(t, args...)
437441
as.NoError(err)
438-
assertStats(t, as, 31, 0, 0, 0)
442+
assertStats(t, as, 32, 0, 0, 0)
439443

440444
// add go formatter
441445
cfg.Formatters["go"] = &config.Formatter{
@@ -447,38 +451,38 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
447451

448452
_, err = cmd(t, args...)
449453
as.NoError(err)
450-
assertStats(t, as, 31, 31, 4, 0)
454+
assertStats(t, as, 32, 32, 4, 0)
451455

452456
// check cache is working
453457
_, err = cmd(t, args...)
454458
as.NoError(err)
455-
assertStats(t, as, 31, 0, 0, 0)
459+
assertStats(t, as, 32, 0, 0, 0)
456460

457461
// remove python formatter
458462
delete(cfg.Formatters, "python")
459463
test.WriteConfig(t, configPath, cfg)
460464

461465
_, err = cmd(t, args...)
462466
as.NoError(err)
463-
assertStats(t, as, 31, 31, 2, 0)
467+
assertStats(t, as, 32, 32, 2, 0)
464468

465469
// check cache is working
466470
_, err = cmd(t, args...)
467471
as.NoError(err)
468-
assertStats(t, as, 31, 0, 0, 0)
472+
assertStats(t, as, 32, 0, 0, 0)
469473

470474
// remove elm formatter
471475
delete(cfg.Formatters, "elm")
472476
test.WriteConfig(t, configPath, cfg)
473477

474478
_, err = cmd(t, args...)
475479
as.NoError(err)
476-
assertStats(t, as, 31, 31, 1, 0)
480+
assertStats(t, as, 32, 32, 1, 0)
477481

478482
// check cache is working
479483
_, err = cmd(t, args...)
480484
as.NoError(err)
481-
assertStats(t, as, 31, 0, 0, 0)
485+
assertStats(t, as, 32, 0, 0, 0)
482486
}
483487

484488
func TestGitWorktree(t *testing.T) {
@@ -524,7 +528,7 @@ func TestGitWorktree(t *testing.T) {
524528
// add everything to the worktree
525529
as.NoError(wt.AddGlob("."))
526530
as.NoError(err)
527-
run(31, 31, 31, 0)
531+
run(32, 32, 32, 0)
528532

529533
// remove python directory
530534
as.NoError(wt.RemoveGlob("python/*"))
@@ -533,7 +537,7 @@ func TestGitWorktree(t *testing.T) {
533537
// walk with filesystem instead of git
534538
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--walk", "filesystem")
535539
as.NoError(err)
536-
assertStats(t, as, 59, 59, 59, 0)
540+
assertStats(t, as, 61, 61, 61, 0)
537541
}
538542

539543
func TestPathsArg(t *testing.T) {
@@ -568,7 +572,7 @@ func TestPathsArg(t *testing.T) {
568572
// without any path args
569573
_, err = cmd(t, "-C", tempDir)
570574
as.NoError(err)
571-
assertStats(t, as, 31, 31, 31, 0)
575+
assertStats(t, as, 32, 32, 32, 0)
572576

573577
// specify some explicit paths
574578
_, err = cmd(t, "-C", tempDir, "-c", "elm/elm.json", "haskell/Nested/Foo.hs")

flake.lock

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nix/packages/treefmt/formatters.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ with pkgs; [
1616
statix
1717
deadnix
1818
terraform
19+
dos2unix
20+
yamlfmt
1921
# util for unit testing
2022
(pkgs.writeShellApplication {
2123
name = "test-fmt";

0 commit comments

Comments
 (0)