Compare commits

..
2 Commits
Author SHA1 Message Date
sneak 3f9f150736 Print the byte unit once in the summary line (closes #17)
check / check (push) Successful in 3m2s
formatBytes already ends in a unit, so the summary line read "9 B
bytes" or "1.0 KiB bytes". The format string in Stats.Print no longer
adds the word "bytes".

Print now takes the io.Writer to print to; its callers pass os.Stderr,
and the new test passes a buffer to read the line for one small and
one large byte count.

Model: opus-5-5
2026-10-06 06:00:11 +00:00
clawbot c4b50e2b55 Skip excluded paths before looking at their errors (closes #16)
check / check (push) Successful in 4m10s
filepath.Walk hands its callback the error for a path it could not
read, such as a directory it cannot list. Both callbacks, in
walkAndProcess and countFiles, returned that error before they checked
the exclusions, so a directory that --exclude or --exclude-dotfiles
excluded still failed the run when it could not be listed. Given an
error, each callback now checks the exclusions first and skips an
excluded path. The new test excludes a directory that cannot be listed
and runs sum add and check over its parent.

Model: opus-5-5
2026-10-06 07:44:00 +02:00
3 changed files with 63 additions and 1 deletions
+3
View File
@@ -27,6 +27,9 @@ today), `.dockerignore`, `.gitea/workflows/check.yml` and
* 2026-10-06: the summary line after `sum`, `check` and `clear` prints
the byte unit once (`1.5 KiB`, not `1.5 KiB bytes`)
* 2026-10-06: a path that `--exclude` or `--exclude-dotfiles` excludes
is skipped even when it cannot be read, so an excluded directory
that cannot be listed no longer fails the run
* 2026-10-06: lint and test run as phases of the `Dockerfile`, and the
build stage depends on both; `script/lint` and `script/test` each
build their phase with `--no-cache`; `script/cibuild` bootstraps,
+12 -1
View File
@@ -613,6 +613,12 @@ func countFiles(opts *options, root string, cont bool) (int64, error) {
root = filepath.Clean(root)
err := filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
// An excluded path is skipped whatever went wrong reading it.
rel, _ := filepath.Rel(root, p)
if err != nil && shouldExclude(opts, rel) {
return nil
}
if err != nil && cont {
return nil
}
@@ -627,7 +633,6 @@ func countFiles(opts *options, root string, cont bool) (int64, error) {
return nil
}
rel, _ := filepath.Rel(root, p)
if shouldExclude(opts, rel) {
if info.IsDir() {
return filepath.SkipDir
@@ -699,6 +704,12 @@ func walkAndProcess(
root = filepath.Clean(root)
return filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
// An excluded path is skipped whatever went wrong reading it.
rel, _ := filepath.Rel(root, p)
if err != nil && shouldExclude(opts, rel) {
return nil
}
if err != nil {
return unreadable(cont, stats, err)
}
+48
View File
@@ -215,6 +215,54 @@ func TestExcludeDotfilesAndPatterns(t *testing.T) {
}
}
func TestExcludeUnreadableDir(t *testing.T) {
t.Parallel()
opts := &options{excludePatterns: []string{"locked"}}
dir := t.TempDir()
skipIfNoXattr(t, dir)
keep := writeFile(t, dir, "keep.txt", "keep")
hidden := writeFile(t, dir, "locked/a.txt", "hidden")
// The excluded directory cannot be listed.
sub := filepath.Join(dir, "locked")
err := os.Chmod(sub, noPerm)
if err != nil {
t.Fatalf("chmod dir: %v", err)
}
defer func() { _ = os.Chmod(sub, dirPerm) }()
err = processSumAdd(opts, dir, newTestStats(), nil)
if err != nil {
t.Fatalf("add: %v", err)
}
_, err = xattr.Get(keep, checksumKey)
if err != nil {
t.Fatalf("expected xattr on keep.txt: %v", err)
}
// Without --quiet, runCheck counts the files for the progress bar
// before it checks any, so this also covers the count.
err = runCheck(opts, []string{dir}, false)
if err != nil {
t.Fatalf("check: %v", err)
}
err = os.Chmod(sub, dirPerm)
if err != nil {
t.Fatalf("chmod dir back: %v", err)
}
_, err = xattr.Get(hidden, checksumKey)
if err == nil {
t.Fatalf("locked/a.txt should have been excluded")
}
}
func TestSkipBrokenSymlink(t *testing.T) {
t.Parallel()