New Upstream Release - golang-github-go-git-go-billy

Ready changes

Summary

Merged new upstream version: 5.4.1 (was: 5.3.1).

Resulting package

Built on 2023-02-09T06:54 (took 19m35s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-go-git-go-billy-dev

Lintian Result

Diff

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4e9980e..3098fee 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,18 +1,19 @@
 on: [push, pull_request]
 name: Test
+permissions: {}
 jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.14.x, 1.15.x, 1.16.x]
+        go-version: [1.19.x,1.20.x]
         platform: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.platform }}
     steps:
     - name: Install Go
-      uses: actions/setup-go@v1
+      uses: actions/setup-go@v3
       with:
         go-version: ${{ matrix.go-version }}
     - name: Checkout code
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
     - name: Test
-      run: go test ./...
\ No newline at end of file
+      run: make test
diff --git a/.github/workflows/test_js.yml b/.github/workflows/test_js.yml
index c55e6d5..5889a44 100644
--- a/.github/workflows/test_js.yml
+++ b/.github/workflows/test_js.yml
@@ -1,24 +1,25 @@
 on: [push, pull_request]
-name: Test
+name: Test JS
+permissions: {}
 jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.14.x, 1.15.x, 1.16.x]
+        go-version: [1.18.x,1.19.x]
     runs-on: ubuntu-latest
     steps:
     - name: Install Go
-      uses: actions/setup-go@v1
+      uses: actions/setup-go@v3
       with:
         go-version: ${{ matrix.go-version }}
 
     - name: Install wasmbrowsertest
       run: |
-        go get github.com/agnivade/wasmbrowsertest
+        go install github.com/agnivade/wasmbrowsertest@latest
         mv $HOME/go/bin/wasmbrowsertest $HOME/go/bin/go_js_wasm_exec
 
     - name: Checkout code
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
 
     - name: Test
       run: go test -exec="$HOME/go/bin/go_js_wasm_exec" ./...
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..74dad8b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+# Go parameters
+GOCMD = go
+GOTEST = $(GOCMD) test 
+
+.PHONY: test
+test:
+	$(GOTEST) -race ./...
+
+test-coverage:
+	echo "" > $(COVERAGE_REPORT); \
+	$(GOTEST) -coverprofile=$(COVERAGE_REPORT) -coverpkg=./... -covermode=$(COVERAGE_MODE) ./...
diff --git a/debian/changelog b/debian/changelog
index 6081e3d..d8f3293 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-go-git-go-billy (5.4.1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 09 Feb 2023 06:37:29 -0000
+
 golang-github-go-git-go-billy (5.3.1-3) unstable; urgency=medium
 
   * Source only upload for migration to testing
diff --git a/go.mod b/go.mod
index 78ce0af..6c8c200 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module github.com/go-git/go-billy/v5
 require (
 	github.com/kr/text v0.2.0 // indirect
 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
-	golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
-	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
+	golang.org/x/sys v0.3.0
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
 )
 
 go 1.13
diff --git a/go.sum b/go.sum
index cdc052b..66b4216 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,8 @@
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/go-git/go-billy v1.0.0 h1:bXR6Zu3opPSg0R4dDxqaLglY4rxw7ja7wS16qSpOKL4=
 github.com/go-git/go-billy v3.1.0+incompatible h1:dwrJ8G2Jt1srYgIJs+lRjA36qBY68O2Lg5idKG8ef5M=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -10,5 +12,11 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/memfs/memory.go b/memfs/memory.go
index f217693..dab7396 100644
--- a/memfs/memory.go
+++ b/memfs/memory.go
@@ -310,14 +310,14 @@ func (f *file) Duplicate(filename string, mode os.FileMode, flag int) billy.File
 		flag:    flag,
 	}
 
-	if isAppend(flag) {
-		new.position = int64(new.content.Len())
-	}
-
 	if isTruncate(flag) {
 		new.content.Truncate()
 	}
 
+	if isAppend(flag) {
+		new.position = int64(new.content.Len())
+	}
+
 	return new
 }
 
diff --git a/memfs/memory_test.go b/memfs/memory_test.go
index b79ac2f..b23f7ae 100644
--- a/memfs/memory_test.go
+++ b/memfs/memory_test.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/go-git/go-billy/v5"
 	"github.com/go-git/go-billy/v5/test"
+	"github.com/go-git/go-billy/v5/util"
 
 	. "gopkg.in/check.v1"
 )
@@ -47,7 +48,6 @@ func (s *MemorySuite) TestNegativeOffsets(c *C) {
 	c.Assert(err, ErrorMatches, "writeat negative: negative offset")
 }
 
-
 func (s *MemorySuite) TestExclusive(c *C) {
 	f, err := s.FS.OpenFile("exclusive", os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666)
 	c.Assert(err, IsNil)
@@ -74,8 +74,8 @@ func (s *MemorySuite) TestOrder(c *C) {
 		c.Assert(err, IsNil)
 	}
 
-	attemps := 30
-	for n := 0; n < attemps; n++ {
+	attempts := 30
+	for n := 0; n < attempts; n++ {
 		actual, err := s.FS.ReadDir("")
 		c.Assert(err, IsNil)
 
@@ -84,3 +84,22 @@ func (s *MemorySuite) TestOrder(c *C) {
 		}
 	}
 }
+
+func (s *MemorySuite) TestTruncateAppend(c *C) {
+	err := util.WriteFile(s.FS, "truncate_append", []byte("file-content"), 0666)
+	c.Assert(err, IsNil)
+
+	f, err := s.FS.OpenFile("truncate_append", os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
+	c.Assert(err, IsNil)
+
+	n, err := f.Write([]byte("replace"))
+	c.Assert(err, IsNil)
+	c.Assert(n, Equals, len("replace"))
+
+	err = f.Close()
+	c.Assert(err, IsNil)
+
+	data, err := util.ReadFile(s.FS, "truncate_append")
+	c.Assert(err, IsNil)
+	c.Assert(string(data), Equals, "replace")
+}
diff --git a/memfs/storage.go b/memfs/storage.go
index d3ff5a2..e3c4e38 100644
--- a/memfs/storage.go
+++ b/memfs/storage.go
@@ -6,6 +6,7 @@ import (
 	"io"
 	"os"
 	"path/filepath"
+	"sync"
 )
 
 type storage struct {
@@ -174,6 +175,8 @@ func clean(path string) string {
 type content struct {
 	name  string
 	bytes []byte
+
+	m sync.RWMutex
 }
 
 func (c *content) WriteAt(p []byte, off int64) (int, error) {
@@ -185,6 +188,7 @@ func (c *content) WriteAt(p []byte, off int64) (int, error) {
 		}
 	}
 
+	c.m.Lock()
 	prev := len(c.bytes)
 
 	diff := int(off) - prev
@@ -196,6 +200,7 @@ func (c *content) WriteAt(p []byte, off int64) (int, error) {
 	if len(c.bytes) < prev {
 		c.bytes = c.bytes[:prev]
 	}
+	c.m.Unlock()
 
 	return len(p), nil
 }
@@ -209,8 +214,10 @@ func (c *content) ReadAt(b []byte, off int64) (n int, err error) {
 		}
 	}
 
+	c.m.RLock()
 	size := int64(len(c.bytes))
 	if off >= size {
+		c.m.RUnlock()
 		return 0, io.EOF
 	}
 
@@ -220,10 +227,12 @@ func (c *content) ReadAt(b []byte, off int64) (n int, err error) {
 	}
 
 	btr := c.bytes[off : off+l]
+	n = copy(b, btr)
+
 	if len(btr) < len(b) {
 		err = io.EOF
 	}
-	n = copy(b, btr)
+	c.m.RUnlock()
 
 	return
 }
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..7cd80d8
--- /dev/null
+++ b/util/walk_test.go
@@ -0,0 +1,195 @@
+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{})
+var targetSubfolder = filepath.FromSlash("path/to/some/subfolder")
+
+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, filepath.FromSlash("path/to"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/file"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder/that"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder/that/contain"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("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 == targetSubfolder {
+			return filepath.SkipDir
+		}
+		return nil
+	}), IsNil)
+	c.Assert(discoveredPaths, Contains, "path")
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/file"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("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 == targetSubfolder {
+			return errors.New("uncaught error")
+		}
+		return nil
+	}), NotNil)
+	c.Assert(discoveredPaths, Contains, "path")
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/file"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("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 == targetSubfolder {
+				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 == targetSubfolder {
+			c.Assert(err, NotNil)
+		}
+		return err
+	}), NotNil)
+	c.Assert(discoveredPaths, Contains, "path")
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/file"))
+	c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain"))
+	c.Assert(discoveredPaths, NotContain, filepath.FromSlash("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)

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/go-git/go-billy/v5/util/walk.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/go-git/go-billy/v5/util/walk_test.go

No differences were encountered in the control files

More details

Full run details