]> Cypherpunks.ru repositories - gostls13.git/commitdiff
path/filepath, io/fs: add SkipAll
authorPaschalis Tsilias <paschalis.tsilias@gmail.com>
Sat, 13 Nov 2021 00:18:30 +0000 (02:18 +0200)
committerGopher Robot <gobot@golang.org>
Thu, 25 Aug 2022 18:50:37 +0000 (18:50 +0000)
Fixes #47209

Change-Id: If75b0dd38f2c30a23517205d80c7a6683a5c921c
Reviewed-on: https://go-review.googlesource.com/c/go/+/363814
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Bryan Mills <bcmills@google.com>
api/next/47209.txt [new file with mode: 0644]
src/cmd/go/internal/fsys/fsys_test.go
src/io/fs/walk.go
src/path/filepath/path.go
src/path/filepath/path_test.go

diff --git a/api/next/47209.txt b/api/next/47209.txt
new file mode 100644 (file)
index 0000000..fd4969c
--- /dev/null
@@ -0,0 +1,2 @@
+pkg io/fs, var SkipAll error #47209
+pkg path/filepath, var SkipAll error #47209
index 8cfe1d89e693b22c9d33390eae9bec302aad1059..41da4f4b02a1adea23e2b960d1de51f8ea899390 100644 (file)
@@ -760,6 +760,42 @@ func TestWalkSkipDir(t *testing.T) {
        }
 }
 
+func TestWalkSkipAll(t *testing.T) {
+       initOverlay(t, `
+{
+       "Replace": {
+               "dir/subdir1/foo1": "dummy.txt",
+               "dir/subdir1/foo2": "dummy.txt",
+               "dir/subdir1/foo3": "dummy.txt",
+               "dir/subdir2/foo4": "dummy.txt",
+               "dir/zzlast": "dummy.txt"
+       }
+}
+-- dummy.txt --
+`)
+
+       var seen []string
+       Walk("dir", func(path string, info fs.FileInfo, err error) error {
+               seen = append(seen, filepath.ToSlash(path))
+               if info.Name() == "foo2" {
+                       return filepath.SkipAll
+               }
+               return nil
+       })
+
+       wantSeen := []string{"dir", "dir/subdir1", "dir/subdir1/foo1", "dir/subdir1/foo2"}
+
+       if len(seen) != len(wantSeen) {
+               t.Errorf("paths seen in walk: got %v entries; want %v entries", len(seen), len(wantSeen))
+       }
+
+       for i := 0; i < len(seen) && i < len(wantSeen); i++ {
+               if seen[i] != wantSeen[i] {
+                       t.Errorf("path %#v seen walking tree: got %q, want %q", i, seen[i], wantSeen[i])
+               }
+       }
+}
+
 func TestWalkError(t *testing.T) {
        initOverlay(t, "{}")
 
index 37800794a2339d484b8dab59d18eef425e77456b..cff26104f047caed9b7d08dba33c9010dcff79eb 100644 (file)
@@ -14,6 +14,11 @@ import (
 // as an error by any function.
 var SkipDir = errors.New("skip this directory")
 
+// SkipAll is used as a return value from WalkDirFuncs to indicate that
+// all remaining files and directories are to be skipped. It is not returned
+// as an error by any function.
+var SkipAll = errors.New("skip everything and stop the walk")
+
 // WalkDirFunc is the type of the function called by WalkDir to visit
 // each file or directory.
 //
@@ -27,8 +32,10 @@ var SkipDir = errors.New("skip this directory")
 // The error result returned by the function controls how WalkDir
 // continues. If the function returns the special value SkipDir, WalkDir
 // skips the current directory (path if d.IsDir() is true, otherwise
-// path's parent directory). Otherwise, if the function returns a non-nil
-// error, WalkDir stops entirely and returns that error.
+// path's parent directory). If the function returns the special value
+// SkipAll, WalkDir skips all remaining files and directories. Otherwise,
+// if the function returns a non-nil error, WalkDir stops entirely and
+// returns that error.
 //
 // The err argument reports an error related to path, signaling that
 // WalkDir will not walk into that directory. The function can decide how
@@ -47,15 +54,16 @@ var SkipDir = errors.New("skip this directory")
 // ReadDir. In this second case, the function is called twice with the
 // path of the directory: the first call is before the directory read is
 // attempted and has err set to nil, giving the function a chance to
-// return SkipDir and avoid the ReadDir entirely. The second call is
-// after a failed ReadDir and reports the error from ReadDir.
+// return SkipDir or SkipAll and avoid the ReadDir entirely. The second call
+// is after a failed ReadDir and reports the error from ReadDir.
 // (If ReadDir succeeds, there is no second call.)
 //
 // The differences between WalkDirFunc compared to filepath.WalkFunc are:
 //
 //   - The second argument has type fs.DirEntry instead of fs.FileInfo.
 //   - The function is called before reading a directory, to allow SkipDir
-//     to bypass the directory read entirely.
+//     or SkipAll to bypass the directory read entirely or skip all remaining
+//     files and directories respectively.
 //   - If a directory read fails, the function is called a second time
 //     for that directory to report the error.
 type WalkDirFunc func(path string, d DirEntry, err error) error
@@ -113,7 +121,7 @@ func WalkDir(fsys FS, root string, fn WalkDirFunc) error {
        } else {
                err = walkDir(fsys, root, &statDirEntry{info}, fn)
        }
-       if err == SkipDir {
+       if err == SkipDir || err == SkipAll {
                return nil
        }
        return err
index de7a2c758b1cf5d8e1c0b66db90b69c4f0492f2c..c86b0c0ff819b0b48370e9dea0916ee45af9abe6 100644 (file)
@@ -352,6 +352,11 @@ func Rel(basepath, targpath string) (string, error) {
 // as an error by any function.
 var SkipDir error = fs.SkipDir
 
+// SkipAll is used as a return value from WalkFuncs to indicate that
+// all remaining files and directories are to be skipped. It is not returned
+// as an error by any function.
+var SkipAll error = fs.SkipAll
+
 // WalkFunc is the type of the function called by Walk to visit each
 // file or directory.
 //
@@ -370,8 +375,9 @@ var SkipDir error = fs.SkipDir
 // The error result returned by the function controls how Walk continues.
 // If the function returns the special value SkipDir, Walk skips the
 // current directory (path if info.IsDir() is true, otherwise path's
-// parent directory). Otherwise, if the function returns a non-nil error,
-// Walk stops entirely and returns that error.
+// parent directory). If the function returns the special value SkipAll,
+// Walk skips all remaining files and directories. Otherwise, if the function
+// returns a non-nil error, Walk stops entirely and returns that error.
 //
 // The err argument reports an error related to path, signaling that Walk
 // will not walk into that directory. The function can decide how to
@@ -441,7 +447,7 @@ func walk(path string, info fs.FileInfo, walkFn WalkFunc) error {
        if err != nil || err1 != nil {
                // The caller's behavior is controlled by the return value, which is decided
                // by walkFn. walkFn may ignore err and return nil.
-               // If walkFn returns SkipDir, it will be handled by the caller.
+               // If walkFn returns SkipDir or SkipAll, it will be handled by the caller.
                // So walk should return whatever walkFn returns.
                return err1
        }
@@ -483,7 +489,7 @@ func WalkDir(root string, fn fs.WalkDirFunc) error {
        } else {
                err = walkDir(root, &statDirEntry{info}, fn)
        }
-       if err == SkipDir {
+       if err == SkipDir || err == SkipAll {
                return nil
        }
        return err
@@ -519,7 +525,7 @@ func Walk(root string, fn WalkFunc) error {
        } else {
                err = walk(root, info, fn)
        }
-       if err == SkipDir {
+       if err == SkipDir || err == SkipAll {
                return nil
        }
        return err
index a783d6be2838ab7321941bb71958bbe403b253b0..9bdc58ea35802884c2031e80c80a0d270875f15c 100644 (file)
@@ -638,6 +638,64 @@ func TestWalkSkipDirOnFile(t *testing.T) {
        })
 }
 
+func TestWalkSkipAllOnFile(t *testing.T) {
+       td := t.TempDir()
+
+       if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
+               t.Fatal(err)
+       }
+       if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
+               t.Fatal(err)
+       }
+
+       touch(t, filepath.Join(td, "dir", "foo1"))
+       touch(t, filepath.Join(td, "dir", "foo2"))
+       touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
+       touch(t, filepath.Join(td, "dir", "foo4"))
+       touch(t, filepath.Join(td, "dir2", "bar"))
+       touch(t, filepath.Join(td, "last"))
+
+       remainingWereSkipped := true
+       walker := func(path string) error {
+               if strings.HasSuffix(path, "foo2") {
+                       return filepath.SkipAll
+               }
+
+               if strings.HasSuffix(path, "foo3") ||
+                       strings.HasSuffix(path, "foo4") ||
+                       strings.HasSuffix(path, "bar") ||
+                       strings.HasSuffix(path, "last") {
+                       remainingWereSkipped = false
+               }
+               return nil
+       }
+
+       walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
+       walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
+
+       check := func(t *testing.T, walk func(root string) error, root string) {
+               t.Helper()
+               remainingWereSkipped = true
+               if err := walk(root); err != nil {
+                       t.Fatal(err)
+               }
+               if !remainingWereSkipped {
+                       t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
+               }
+       }
+
+       t.Run("Walk", func(t *testing.T) {
+               Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
+               check(t, Walk, td)
+               check(t, Walk, filepath.Join(td, "dir"))
+       })
+       t.Run("WalkDir", func(t *testing.T) {
+               WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
+               check(t, WalkDir, td)
+               check(t, WalkDir, filepath.Join(td, "dir"))
+       })
+}
+
 func TestWalkFileError(t *testing.T) {
        td := t.TempDir()