aboutsummaryrefslogtreecommitdiffstats
path: root/storage/filesystem/internal
diff options
context:
space:
mode:
Diffstat (limited to 'storage/filesystem/internal')
-rw-r--r--storage/filesystem/internal/dotgit/dotgit.go167
-rw-r--r--storage/filesystem/internal/dotgit/dotgit_test.go68
-rw-r--r--storage/filesystem/internal/dotgit/writers.go2
-rw-r--r--storage/filesystem/internal/dotgit/writers_test.go9
4 files changed, 179 insertions, 67 deletions
diff --git a/storage/filesystem/internal/dotgit/dotgit.go b/storage/filesystem/internal/dotgit/dotgit.go
index 2840bc7..1cb97bd 100644
--- a/storage/filesystem/internal/dotgit/dotgit.go
+++ b/storage/filesystem/internal/dotgit/dotgit.go
@@ -5,15 +5,15 @@ import (
"bufio"
"errors"
"fmt"
+ "io"
stdioutil "io/ioutil"
"os"
"strings"
- "time"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/utils/ioutil"
- "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v4"
)
const (
@@ -57,16 +57,14 @@ var (
// The DotGit type represents a local git repository on disk. This
// type is not zero-value-safe, use the New function to initialize it.
type DotGit struct {
- fs billy.Filesystem
- cachedPackedRefs refCache
- packedRefsLastMod time.Time
+ fs billy.Filesystem
}
// New returns a DotGit value ready to be used. The path argument must
// be the absolute path of a git repository directory (e.g.
// "/foo/bar/.git").
func New(fs billy.Filesystem) *DotGit {
- return &DotGit{fs: fs, cachedPackedRefs: make(refCache)}
+ return &DotGit{fs: fs}
}
// Initialize creates all the folder scaffolding.
@@ -242,7 +240,35 @@ func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
return d.fs.Open(file)
}
-func (d *DotGit) SetRef(r *plumbing.Reference) error {
+func (d *DotGit) readReferenceFrom(rd io.Reader, name string) (ref *plumbing.Reference, err error) {
+ b, err := stdioutil.ReadAll(rd)
+ if err != nil {
+ return nil, err
+ }
+
+ line := strings.TrimSpace(string(b))
+ return plumbing.NewReferenceFromStrings(name, line), nil
+}
+
+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 err != nil {
+ return err
+ }
+ if ref.Hash() != old.Hash() {
+ return fmt.Errorf("reference has changed concurrently")
+ }
+ _, err = f.Seek(0, io.SeekStart)
+ if err != nil {
+ return err
+ }
+ return f.Truncate(0)
+}
+
+func (d *DotGit) SetRef(r, old *plumbing.Reference) error {
var content string
switch r.Type() {
case plumbing.SymbolicReference:
@@ -251,13 +277,34 @@ func (d *DotGit) SetRef(r *plumbing.Reference) error {
content = fmt.Sprintln(r.Hash().String())
}
- f, err := d.fs.Create(r.Name().String())
+ // If we are not checking an old ref, just truncate the file.
+ mode := os.O_RDWR | os.O_CREATE
+ if old == nil {
+ mode |= os.O_TRUNC
+ }
+
+ f, err := d.fs.OpenFile(r.Name().String(), mode, 0666)
if err != nil {
return err
}
defer ioutil.CheckClose(f, &err)
+ // Lock is unlocked by the deferred Close above. This is because Unlock
+ // does not imply a fsync and thus there would be a race between
+ // Unlock+Close and other concurrent writers. Adding Sync to go-billy
+ // could work, but this is better (and avoids superfluous syncs).
+ err = f.Lock()
+ if err != nil {
+ return err
+ }
+
+ // this is a no-op to call even when old is nil.
+ err = d.checkReferenceAndTruncate(f, old)
+ if err != nil {
+ return err
+ }
+
_, err = f.Write([]byte(content))
return err
}
@@ -292,54 +339,43 @@ func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) {
return d.packedRef(name)
}
-func (d *DotGit) syncPackedRefs() error {
- fi, err := d.fs.Stat(packedRefsPath)
- if os.IsNotExist(err) {
- return nil
- }
-
+func (d *DotGit) findPackedRefs() ([]*plumbing.Reference, error) {
+ f, err := d.fs.Open(packedRefsPath)
if err != nil {
- return err
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+ return nil, err
}
- if d.packedRefsLastMod.Before(fi.ModTime()) {
- d.cachedPackedRefs = make(refCache)
- f, err := d.fs.Open(packedRefsPath)
+ defer ioutil.CheckClose(f, &err)
+
+ s := bufio.NewScanner(f)
+ var refs []*plumbing.Reference
+ for s.Scan() {
+ ref, err := d.processLine(s.Text())
if err != nil {
- if os.IsNotExist(err) {
- return nil
- }
- return err
+ return nil, err
}
- defer ioutil.CheckClose(f, &err)
-
- s := bufio.NewScanner(f)
- for s.Scan() {
- ref, err := d.processLine(s.Text())
- if err != nil {
- return err
- }
- if ref != nil {
- d.cachedPackedRefs[ref.Name()] = ref
- }
+ if ref != nil {
+ refs = append(refs, ref)
}
-
- d.packedRefsLastMod = fi.ModTime()
-
- return s.Err()
}
- return nil
+ return refs, s.Err()
}
func (d *DotGit) packedRef(name plumbing.ReferenceName) (*plumbing.Reference, error) {
- if err := d.syncPackedRefs(); err != nil {
+ refs, err := d.findPackedRefs()
+ if err != nil {
return nil, err
}
- if ref, ok := d.cachedPackedRefs[name]; ok {
- return ref, nil
+ for _, ref := range refs {
+ if ref.Name() == name {
+ return ref, nil
+ }
}
return nil, plumbing.ErrReferenceNotFound
@@ -350,7 +386,8 @@ func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error {
path := d.fs.Join(".", name.String())
_, err := d.fs.Stat(path)
if err == nil {
- return d.fs.Remove(path)
+ err = d.fs.Remove(path)
+ // Drop down to remove it from the packed refs file, too.
}
if err != nil && !os.IsNotExist(err) {
@@ -361,17 +398,17 @@ func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error {
}
func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) (err error) {
- if err := d.syncPackedRefs(); err != nil {
+ packedRefs, err := d.findPackedRefs()
+ if err != nil {
return err
}
- for name, ref := range d.cachedPackedRefs {
- if !seen[name] {
+ for _, ref := range packedRefs {
+ if !seen[ref.Name()] {
*refs = append(*refs, ref)
- seen[name] = true
+ seen[ref.Name()] = true
}
}
-
return nil
}
@@ -384,6 +421,17 @@ func (d *DotGit) rewritePackedRefsWithoutRef(name plumbing.ReferenceName) (err e
return err
}
+ doCloseF := true
+ defer func() {
+ if doCloseF {
+ ioutil.CheckClose(f, &err)
+ }
+ }()
+
+ err = f.Lock()
+ if err != nil {
+ return err
+ }
// Creating the temp file in the same directory as the target file
// improves our chances for rename operation to be atomic.
@@ -391,6 +439,12 @@ func (d *DotGit) rewritePackedRefsWithoutRef(name plumbing.ReferenceName) (err e
if err != nil {
return err
}
+ doCloseTmp := true
+ defer func() {
+ if doCloseTmp {
+ ioutil.CheckClose(tmp, &err)
+ }
+ }()
s := bufio.NewScanner(f)
found := false
@@ -416,14 +470,21 @@ func (d *DotGit) rewritePackedRefsWithoutRef(name plumbing.ReferenceName) (err e
}
if !found {
- return nil
+ doCloseTmp = false
+ ioutil.CheckClose(tmp, &err)
+ if err != nil {
+ return err
+ }
+ // Delete the temp file if nothing needed to be removed.
+ return d.fs.Remove(tmp.Name())
}
+ doCloseF = false
if err := f.Close(); err != nil {
- ioutil.CheckClose(tmp, &err)
return err
}
+ doCloseTmp = false
if err := tmp.Close(); err != nil {
return err
}
@@ -512,13 +573,7 @@ func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference,
}
defer ioutil.CheckClose(f, &err)
- b, err := stdioutil.ReadAll(f)
- if err != nil {
- return nil, err
- }
-
- line := strings.TrimSpace(string(b))
- return plumbing.NewReferenceFromStrings(name, line), nil
+ return d.readReferenceFrom(f, name)
}
// Module return a billy.Filesystem poiting to the module folder
diff --git a/storage/filesystem/internal/dotgit/dotgit_test.go b/storage/filesystem/internal/dotgit/dotgit_test.go
index a7f16f4..446a204 100644
--- a/storage/filesystem/internal/dotgit/dotgit_test.go
+++ b/storage/filesystem/internal/dotgit/dotgit_test.go
@@ -8,11 +8,11 @@ import (
"strings"
"testing"
- "github.com/src-d/go-git-fixtures"
"gopkg.in/src-d/go-git.v4/plumbing"
. "gopkg.in/check.v1"
- "gopkg.in/src-d/go-billy.v3/osfs"
+ "gopkg.in/src-d/go-billy.v4/osfs"
+ "gopkg.in/src-d/go-git-fixtures.v3"
)
func Test(t *testing.T) { TestingT(t) }
@@ -55,24 +55,25 @@ func (s *SuiteDotGit) TestSetRefs(c *C) {
fs := osfs.New(tmp)
dir := New(fs)
- err = dir.SetRef(plumbing.NewReferenceFromStrings(
+ firstFoo := plumbing.NewReferenceFromStrings(
"refs/heads/foo",
"e8d3ffab552895c19b9fcf7aa264d277cde33881",
- ))
+ )
+ err = dir.SetRef(firstFoo, nil)
c.Assert(err, IsNil)
err = dir.SetRef(plumbing.NewReferenceFromStrings(
"refs/heads/symbolic",
"ref: refs/heads/foo",
- ))
+ ), nil)
c.Assert(err, IsNil)
err = dir.SetRef(plumbing.NewReferenceFromStrings(
"bar",
"e8d3ffab552895c19b9fcf7aa264d277cde33881",
- ))
+ ), nil)
c.Assert(err, IsNil)
refs, err := dir.Refs()
@@ -105,6 +106,20 @@ func (s *SuiteDotGit) TestSetRefs(c *C) {
c.Assert(ref, NotNil)
c.Assert(ref.Hash().String(), Equals, "e8d3ffab552895c19b9fcf7aa264d277cde33881")
+ // Check that SetRef with a non-nil `old` works.
+ err = dir.SetRef(plumbing.NewReferenceFromStrings(
+ "refs/heads/foo",
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
+ ), firstFoo)
+ c.Assert(err, IsNil)
+
+ // `firstFoo` is no longer the right `old` reference, so this
+ // should fail.
+ err = dir.SetRef(plumbing.NewReferenceFromStrings(
+ "refs/heads/foo",
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
+ ), firstFoo)
+ c.Assert(err, NotNil)
}
func (s *SuiteDotGit) TestRefsFromPackedRefs(c *C) {
@@ -184,6 +199,46 @@ func (s *SuiteDotGit) TestRemoveRefFromPackedRefs(c *C) {
"e8d3ffab552895c19b9fcf7aa264d277cde33881 refs/remotes/origin/branch\n")
}
+func (s *SuiteDotGit) TestRemoveRefFromReferenceFileAndPackedRefs(c *C) {
+ fs := fixtures.Basic().ByTag(".git").One().DotGit()
+ dir := New(fs)
+
+ // Make a ref file for a ref that's already in `packed-refs`.
+ err := dir.SetRef(plumbing.NewReferenceFromStrings(
+ "refs/remotes/origin/branch",
+ "e8d3ffab552895c19b9fcf7aa264d277cde33881",
+ ), nil)
+
+ // Make sure it only appears once in the refs list.
+ refs, err := dir.Refs()
+ c.Assert(err, IsNil)
+ found := false
+ for _, ref := range refs {
+ if ref.Name() == "refs/remotes/origin/branch" {
+ c.Assert(found, Equals, false)
+ found = true
+ }
+ }
+
+ name := plumbing.ReferenceName("refs/remotes/origin/branch")
+ err = dir.RemoveRef(name)
+ c.Assert(err, IsNil)
+
+ b, err := ioutil.ReadFile(filepath.Join(fs.Root(), packedRefsPath))
+ c.Assert(err, IsNil)
+
+ c.Assert(string(b), Equals, ""+
+ "# pack-refs with: peeled fully-peeled \n"+
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 refs/heads/master\n"+
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 refs/remotes/origin/master\n")
+
+ refs, err = dir.Refs()
+ c.Assert(err, IsNil)
+
+ ref := findReference(refs, string(name))
+ c.Assert(ref, IsNil)
+}
+
func (s *SuiteDotGit) TestRemoveRefNonExistent(c *C) {
fs := fixtures.Basic().ByTag(".git").One().DotGit()
dir := New(fs)
@@ -418,6 +473,7 @@ func (s *SuiteDotGit) TestNewObject(c *C) {
c.Assert(err, IsNil)
err = w.WriteHeader(plumbing.BlobObject, 14)
+ c.Assert(err, IsNil)
n, err := w.Write([]byte("this is a test"))
c.Assert(err, IsNil)
c.Assert(n, Equals, 14)
diff --git a/storage/filesystem/internal/dotgit/writers.go b/storage/filesystem/internal/dotgit/writers.go
index 46d3619..c2b420f 100644
--- a/storage/filesystem/internal/dotgit/writers.go
+++ b/storage/filesystem/internal/dotgit/writers.go
@@ -10,7 +10,7 @@ import (
"gopkg.in/src-d/go-git.v4/plumbing/format/objfile"
"gopkg.in/src-d/go-git.v4/plumbing/format/packfile"
- "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v4"
)
// PackWriter is a io.Writer that generates the packfile index simultaneously,
diff --git a/storage/filesystem/internal/dotgit/writers_test.go b/storage/filesystem/internal/dotgit/writers_test.go
index 1544de8..bf00762 100644
--- a/storage/filesystem/internal/dotgit/writers_test.go
+++ b/storage/filesystem/internal/dotgit/writers_test.go
@@ -8,12 +8,12 @@ import (
"os"
"strconv"
- "github.com/src-d/go-git-fixtures"
-
- . "gopkg.in/check.v1"
- "gopkg.in/src-d/go-billy.v3/osfs"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/format/packfile"
+
+ . "gopkg.in/check.v1"
+ "gopkg.in/src-d/go-billy.v4/osfs"
+ "gopkg.in/src-d/go-git-fixtures.v3"
)
func (s *SuiteDotGit) TestNewObjectPack(c *C) {
@@ -85,6 +85,7 @@ func (s *SuiteDotGit) TestNewObjectPackUnused(c *C) {
// check clean up of temporary files
info, err = fs.ReadDir("")
+ c.Assert(err, IsNil)
for _, fi := range info {
c.Assert(fi.IsDir(), Equals, true)
}