aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaulo Gomes <pjbgf@linux.com>2024-06-29 21:33:49 +0000
committerGitHub <noreply@github.com>2024-06-29 21:33:49 +0000
commitb00c68ab714057027594eb8dc9b21059f85d6d3d (patch)
tree306cd4cd24b882243cff0bda190d059aa8382023
parent1e72372fced2c3b5300f1408e6e71b1a50adc6e3 (diff)
parent101c1ab3deb100eb9522c689bc8bdaa0728373fd (diff)
downloadgo-git-b00c68ab714057027594eb8dc9b21059f85d6d3d.tar.gz
Merge pull request #1116 from Javier-varez/ja/FixReferenceUpdatedConcurrently
Fix reference updated concurrently error for the filesystem storer
-rw-r--r--storage/filesystem/dotgit/dotgit.go18
-rw-r--r--storage/filesystem/dotgit/dotgit_test.go61
2 files changed, 79 insertions, 0 deletions
diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go
index ada51eb..72c9ccf 100644
--- a/storage/filesystem/dotgit/dotgit.go
+++ b/storage/filesystem/dotgit/dotgit.go
@@ -72,6 +72,9 @@ var (
// ErrIsDir is returned when a reference file is attempting to be read,
// but the path specified is a directory.
ErrIsDir = errors.New("reference path is a directory")
+ // ErrEmptyRefFile is returned when a reference file is attempted to be read,
+ // but the file is empty
+ ErrEmptyRefFile = errors.New("ref file is empty")
)
// Options holds configuration for the storage.
@@ -661,18 +664,33 @@ func (d *DotGit) readReferenceFrom(rd io.Reader, name string) (ref *plumbing.Ref
return nil, err
}
+ if len(b) == 0 {
+ return nil, ErrEmptyRefFile
+ }
+
line := strings.TrimSpace(string(b))
return plumbing.NewReferenceFromStrings(name, line), nil
}
+// checkReferenceAndTruncate reads the reference from the given file, or the `pack-refs` file if
+// the file was empty. Then it checks that the old reference matches the stored reference and
+// truncates the file.
func (d *DotGit) checkReferenceAndTruncate(f billy.File, old *plumbing.Reference) error {
if old == nil {
return nil
}
+
ref, err := d.readReferenceFrom(f, old.Name().String())
+ if errors.Is(err, ErrEmptyRefFile) {
+ // This may happen if the reference is being read from a newly created file.
+ // In that case, try getting the reference from the packed refs file.
+ ref, err = d.packedRef(old.Name())
+ }
+
if err != nil {
return err
}
+
if ref.Hash() != old.Hash() {
return storage.ErrReferenceHasChanged
}
diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go
index c224397..be66fee 100644
--- a/storage/filesystem/dotgit/dotgit_test.go
+++ b/storage/filesystem/dotgit/dotgit_test.go
@@ -16,6 +16,7 @@ import (
"github.com/go-git/go-billy/v5/util"
fixtures "github.com/go-git/go-git-fixtures/v4"
"github.com/go-git/go-git/v5/plumbing"
+ "github.com/go-git/go-git/v5/storage"
"github.com/stretchr/testify/assert"
. "gopkg.in/check.v1"
)
@@ -1046,3 +1047,63 @@ func (s *SuiteDotGit) TestDeletedRefs(c *C) {
c.Assert(refs, HasLen, 1)
c.Assert(refs[0].Name(), Equals, plumbing.ReferenceName("refs/heads/foo"))
}
+
+// Checks that seting a reference that has been packed and checking its old value is successful
+func (s *SuiteDotGit) TestSetPackedRef(c *C) {
+ fs, clean := s.TemporalFilesystem()
+ defer clean()
+
+ dir := New(fs)
+
+ err := dir.SetRef(plumbing.NewReferenceFromStrings(
+ "refs/heads/foo",
+ "e8d3ffab552895c19b9fcf7aa264d277cde33881",
+ ), nil)
+ c.Assert(err, IsNil)
+
+ refs, err := dir.Refs()
+ c.Assert(err, IsNil)
+ c.Assert(refs, HasLen, 1)
+ looseCount, err := dir.CountLooseRefs()
+ c.Assert(err, IsNil)
+ c.Assert(looseCount, Equals, 1)
+
+ err = dir.PackRefs()
+ c.Assert(err, IsNil)
+
+ // Make sure the refs are still there, but no longer loose.
+ refs, err = dir.Refs()
+ c.Assert(err, IsNil)
+ c.Assert(refs, HasLen, 1)
+ looseCount, err = dir.CountLooseRefs()
+ c.Assert(err, IsNil)
+ c.Assert(looseCount, Equals, 0)
+
+ ref, err := dir.Ref("refs/heads/foo")
+ c.Assert(err, IsNil)
+ c.Assert(ref, NotNil)
+ c.Assert(ref.Hash().String(), Equals, "e8d3ffab552895c19b9fcf7aa264d277cde33881")
+
+ // Attempt to update the reference using an invalid old reference value
+ err = dir.SetRef(plumbing.NewReferenceFromStrings(
+ "refs/heads/foo",
+ "b8d3ffab552895c19b9fcf7aa264d277cde33881",
+ ), plumbing.NewReferenceFromStrings(
+ "refs/heads/foo",
+ "e8d3ffab552895c19b9fcf7aa264d277cde33882",
+ ))
+ c.Assert(err, Equals, storage.ErrReferenceHasChanged)
+
+ // Now update the reference and it should pass
+ err = dir.SetRef(plumbing.NewReferenceFromStrings(
+ "refs/heads/foo",
+ "b8d3ffab552895c19b9fcf7aa264d277cde33881",
+ ), plumbing.NewReferenceFromStrings(
+ "refs/heads/foo",
+ "e8d3ffab552895c19b9fcf7aa264d277cde33881",
+ ))
+ c.Assert(err, IsNil)
+ looseCount, err = dir.CountLooseRefs()
+ c.Assert(err, IsNil)
+ c.Assert(looseCount, Equals, 1)
+}