diff --git a/debian/changelog b/debian/changelog
index 6081e3d..12a6d10 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-go-git-go-billy (5.3.1+git20210804.1.7ab80d7-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 05 May 2022 09:20:23 -0000
+
 golang-github-go-git-go-billy (5.3.1-3) unstable; urgency=medium
 
   * Source only upload for migration to testing
diff --git a/util/walk.go b/util/walk.go
new file mode 100644
index 0000000..1531bca
--- /dev/null
+++ b/util/walk.go
@@ -0,0 +1,72 @@
+package util
+
+import (
+	"os"
+	"path/filepath"
+
+	"github.com/go-git/go-billy/v5"
+)
+
+// walk recursively descends path, calling walkFn
+// adapted from https://golang.org/src/path/filepath/path.go
+func walk(fs billy.Filesystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
+	if !info.IsDir() {
+		return walkFn(path, info, nil)
+	}
+
+	names, err := readdirnames(fs, path)
+	err1 := walkFn(path, info, err)
+	// If err != nil, walk can't walk into this directory.
+	// err1 != nil means walkFn want walk to skip this directory or stop walking.
+	// Therefore, if one of err and err1 isn't nil, walk will return.
+	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.
+		// So walk should return whatever walkFn returns.
+		return err1
+	}
+
+	for _, name := range names {
+		filename := filepath.Join(path, name)
+		fileInfo, err := fs.Lstat(filename)
+		if err != nil {
+			if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
+				return err
+			}
+		} else {
+			err = walk(fs, filename, fileInfo, walkFn)
+			if err != nil {
+				if !fileInfo.IsDir() || err != filepath.SkipDir {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// Walk walks the file tree rooted at root, calling fn for each file or 
+// directory in the tree, including root. All errors that arise visiting files
+// and directories are filtered by fn: see the WalkFunc documentation for
+// details.
+//
+// The files are walked in lexical order, which makes the output deterministic
+// but requires Walk to read an entire directory into memory before proceeding
+// to walk that directory. Walk does not follow symbolic links.
+// 
+// Function adapted from https://github.com/golang/go/blob/3b770f2ccb1fa6fecc22ea822a19447b10b70c5c/src/path/filepath/path.go#L500
+func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error {
+	info, err := fs.Lstat(root)
+	if err != nil {
+		err = walkFn(root, nil, err)
+	} else {
+		err = walk(fs, root, info, walkFn)
+	}
+	
+	if err == filepath.SkipDir {
+		return nil
+	}
+	
+	return err
+}
diff --git a/util/walk_test.go b/util/walk_test.go
new file mode 100644
index 0000000..c92cb63
--- /dev/null
+++ b/util/walk_test.go
@@ -0,0 +1,194 @@
+package util_test
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"github.com/go-git/go-billy/v5"
+	"github.com/go-git/go-billy/v5/memfs"
+	"github.com/go-git/go-billy/v5/util"
+
+	. "gopkg.in/check.v1"
+)
+
+type WalkSuite struct{}
+
+func TestWalk(t *testing.T) { TestingT(t) }
+
+var _ = Suite(&WalkSuite{})
+
+func (s *WalkSuite) TestWalkCanSkipTopDirectory(c *C) {
+	filesystem := memfs.New()
+	c.Assert(util.Walk(filesystem, "/root/that/does/not/exist", func(path string, info os.FileInfo, err error) error { return filepath.SkipDir }), IsNil)
+}
+
+func (s *WalkSuite) TestWalkReturnsAnErrorWhenRootDoesNotExist(c *C) {
+	filesystem := memfs.New()
+	c.Assert(util.Walk(filesystem, "/root/that/does/not/exist", func(path string, info os.FileInfo, err error) error { return err }), NotNil)
+}
+
+func (s *WalkSuite) TestWalkOnPlainFile(c *C) {
+	filesystem := memfs.New()
+	createFile(c, filesystem, "./README.md")
+	discoveredPaths := []string{}
+	c.Assert(util.Walk(filesystem, "./README.md", func(path string, info os.FileInfo, err error) error {
+		discoveredPaths = append(discoveredPaths, path)
+		return nil
+	}), IsNil)
+	c.Assert(discoveredPaths, DeepEquals, []string{"./README.md"})
+}
+
+func (s *WalkSuite) TestWalkOnExistingFolder(c *C) {
+	filesystem := memfs.New()
+	createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
+	createFile(c, filesystem, "path/to/some/file")
+	discoveredPaths := []string{}
+	c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error {
+		discoveredPaths = append(discoveredPaths, path)
+		return nil
+	}), IsNil)
+	c.Assert(discoveredPaths, Contains, "path")
+	c.Assert(discoveredPaths, Contains, "path/to")
+	c.Assert(discoveredPaths, Contains, "path/to/some")
+	c.Assert(discoveredPaths, Contains, "path/to/some/file")
+	c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
+	c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that")
+	c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that/contain")
+	c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that/contain/file")
+}
+
+func (s *WalkSuite) TestWalkCanSkipFolder(c *C) {
+	filesystem := memfs.New()
+	createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
+	createFile(c, filesystem, "path/to/some/file")
+	discoveredPaths := []string{}
+	c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error {
+		discoveredPaths = append(discoveredPaths, path)
+		if path == "path/to/some/subfolder" {
+			return filepath.SkipDir
+		}
+		return nil
+	}), IsNil)
+	c.Assert(discoveredPaths, Contains, "path")
+	c.Assert(discoveredPaths, Contains, "path/to")
+	c.Assert(discoveredPaths, Contains, "path/to/some")
+	c.Assert(discoveredPaths, Contains, "path/to/some/file")
+	c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
+}
+
+func (s *WalkSuite) TestWalkStopsOnError(c *C) {
+	filesystem := memfs.New()
+	createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
+	createFile(c, filesystem, "path/to/some/file")
+	discoveredPaths := []string{}
+	c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error {
+		discoveredPaths = append(discoveredPaths, path)
+		if path == "path/to/some/subfolder" {
+			return errors.New("uncaught error")
+		}
+		return nil
+	}), NotNil)
+	c.Assert(discoveredPaths, Contains, "path")
+	c.Assert(discoveredPaths, Contains, "path/to")
+	c.Assert(discoveredPaths, Contains, "path/to/some")
+	c.Assert(discoveredPaths, Contains, "path/to/some/file")
+	c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
+}
+
+func (s *WalkSuite) TestWalkForwardsStatErrors(c *C) {
+	memFilesystem := memfs.New()
+	filesystem := &fnFs{
+		Filesystem: memFilesystem,
+		lstat: func(path string) (os.FileInfo, error) {
+			if path == "path/to/some/subfolder" {
+				return nil, errors.New("uncaught error")
+			}
+			return memFilesystem.Lstat(path)
+		},
+	}
+
+	createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
+	createFile(c, filesystem, "path/to/some/file")
+	discoveredPaths := []string{}
+	c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error {
+		discoveredPaths = append(discoveredPaths, path)
+		if path == "path/to/some/subfolder" {
+			c.Assert(err, NotNil)
+		}
+		return err
+	}), NotNil)
+	c.Assert(discoveredPaths, Contains, "path")
+	c.Assert(discoveredPaths, Contains, "path/to")
+	c.Assert(discoveredPaths, Contains, "path/to/some")
+	c.Assert(discoveredPaths, Contains, "path/to/some/file")
+	c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
+	c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
+}
+
+func createFile(c *C, filesystem billy.Filesystem, path string) {
+	fd, err := filesystem.Create(path)
+	c.Assert(err, IsNil)
+	if err != nil {
+		fd.Close()
+	}
+}
+
+type fnFs struct {
+	billy.Filesystem
+	lstat func(path string) (os.FileInfo, error)
+}
+
+func (f *fnFs) Lstat(path string) (os.FileInfo, error) {
+	if f.lstat != nil {
+		return f.lstat(path)
+	}
+	return nil, errors.New("not implemented")
+}
+
+type containsChecker struct {
+	*CheckerInfo
+}
+
+func (checker *containsChecker) Check(params []interface{}, names []string) (result bool, err string) {
+	defer func() {
+		if v := recover(); v != nil {
+			result = false
+			err = fmt.Sprint(v)
+		}
+	}()
+
+	value := reflect.ValueOf(params[0])
+	result = false
+	err = fmt.Sprintf("%v does not contain %v", params[0], params[1])
+	switch value.Kind() {
+	case reflect.Array, reflect.Slice:
+		for i := 0; i < value.Len(); i++ {
+			r := reflect.DeepEqual(value.Index(i).Interface(), params[1])
+			if r {
+				result = true
+				err = ""
+			}
+		}
+	default:
+		return false, "obtained value type is not iterable"
+	}
+	return
+}
+
+var Contains Checker = &containsChecker{
+	&CheckerInfo{Name: "Contains", Params: []string{"obtained", "expected"}},
+}
+
+var NotContain Checker = Not(Contains)