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
This commit was merged in pull request #19.
This commit is contained in:
2026-10-06 07:44:00 +02:00
parent d010135618
commit c4b50e2b55
3 changed files with 63 additions and 1 deletions
+3
View File
@@ -25,6 +25,9 @@ today), `.dockerignore`, `.gitea/workflows/check.yml` and
# Completed Steps
* 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
@@ -214,6 +214,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()