diff options
Diffstat (limited to 'storage/filesystem')
-rw-r--r-- | storage/filesystem/internal/dotgit/dotgit.go | 99 | ||||
-rw-r--r-- | storage/filesystem/internal/dotgit/dotgit_test.go | 18 | ||||
-rw-r--r-- | storage/filesystem/internal/dotgit/writers.go | 2 | ||||
-rw-r--r-- | storage/filesystem/internal/dotgit/writers_test.go | 21 |
4 files changed, 109 insertions, 31 deletions
diff --git a/storage/filesystem/internal/dotgit/dotgit.go b/storage/filesystem/internal/dotgit/dotgit.go index e2ff51b..2840bc7 100644 --- a/storage/filesystem/internal/dotgit/dotgit.go +++ b/storage/filesystem/internal/dotgit/dotgit.go @@ -8,6 +8,7 @@ import ( stdioutil "io/ioutil" "os" "strings" + "time" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/utils/ioutil" @@ -56,14 +57,16 @@ 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 + fs billy.Filesystem + cachedPackedRefs refCache + packedRefsLastMod time.Time } // 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} + return &DotGit{fs: fs, cachedPackedRefs: make(refCache)} } // Initialize creates all the folder scaffolding. @@ -263,11 +266,12 @@ func (d *DotGit) SetRef(r *plumbing.Reference) error { // Symbolic references are resolved and included in the output. func (d *DotGit) Refs() ([]*plumbing.Reference, error) { var refs []*plumbing.Reference - if err := d.addRefsFromPackedRefs(&refs); err != nil { + var seen = make(map[plumbing.ReferenceName]bool) + if err := d.addRefsFromRefDir(&refs, seen); err != nil { return nil, err } - if err := d.addRefsFromRefDir(&refs); err != nil { + if err := d.addRefsFromPackedRefs(&refs, seen); err != nil { return nil, err } @@ -285,15 +289,57 @@ func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) { return ref, nil } - refs, err := d.Refs() + return d.packedRef(name) +} + +func (d *DotGit) syncPackedRefs() error { + fi, err := d.fs.Stat(packedRefsPath) + if os.IsNotExist(err) { + return nil + } + if err != nil { - return nil, err + return err } - for _, ref := range refs { - if ref.Name() == name { - return ref, nil + if d.packedRefsLastMod.Before(fi.ModTime()) { + d.cachedPackedRefs = make(refCache) + f, err := d.fs.Open(packedRefsPath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return 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 + } + } + + d.packedRefsLastMod = fi.ModTime() + + return s.Err() + } + + return nil +} + +func (d *DotGit) packedRef(name plumbing.ReferenceName) (*plumbing.Reference, error) { + if err := d.syncPackedRefs(); err != nil { + return nil, err + } + + if ref, ok := d.cachedPackedRefs[name]; ok { + return ref, nil } return nil, plumbing.ErrReferenceNotFound @@ -314,29 +360,19 @@ func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error { return d.rewritePackedRefsWithoutRef(name) } -func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference) (err error) { - f, err := d.fs.Open(packedRefsPath) - if err != nil { - if os.IsNotExist(err) { - return nil - } +func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) (err error) { + if err := d.syncPackedRefs(); err != nil { return 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 { + for name, ref := range d.cachedPackedRefs { + if !seen[name] { *refs = append(*refs, ref) + seen[name] = true } } - return s.Err() + return nil } func (d *DotGit) rewritePackedRefsWithoutRef(name plumbing.ReferenceName) (err error) { @@ -416,11 +452,11 @@ func (d *DotGit) processLine(line string) (*plumbing.Reference, error) { } } -func (d *DotGit) addRefsFromRefDir(refs *[]*plumbing.Reference) error { - return d.walkReferencesTree(refs, []string{refsPath}) +func (d *DotGit) addRefsFromRefDir(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) error { + return d.walkReferencesTree(refs, []string{refsPath}, seen) } -func (d *DotGit) walkReferencesTree(refs *[]*plumbing.Reference, relPath []string) error { +func (d *DotGit) walkReferencesTree(refs *[]*plumbing.Reference, relPath []string, seen map[plumbing.ReferenceName]bool) error { files, err := d.fs.ReadDir(d.fs.Join(relPath...)) if err != nil { if os.IsNotExist(err) { @@ -433,7 +469,7 @@ func (d *DotGit) walkReferencesTree(refs *[]*plumbing.Reference, relPath []strin for _, f := range files { newRelPath := append(append([]string(nil), relPath...), f.Name()) if f.IsDir() { - if err = d.walkReferencesTree(refs, newRelPath); err != nil { + if err = d.walkReferencesTree(refs, newRelPath, seen); err != nil { return err } @@ -445,8 +481,9 @@ func (d *DotGit) walkReferencesTree(refs *[]*plumbing.Reference, relPath []strin return err } - if ref != nil { + if ref != nil && !seen[ref.Name()] { *refs = append(*refs, ref) + seen[ref.Name()] = true } } @@ -511,3 +548,5 @@ func isNum(b byte) bool { func isHexAlpha(b byte) bool { return b >= 'a' && b <= 'f' || b >= 'A' && b <= 'F' } + +type refCache map[plumbing.ReferenceName]*plumbing.Reference diff --git a/storage/filesystem/internal/dotgit/dotgit_test.go b/storage/filesystem/internal/dotgit/dotgit_test.go index d935ec5..a7f16f4 100644 --- a/storage/filesystem/internal/dotgit/dotgit_test.go +++ b/storage/filesystem/internal/dotgit/dotgit_test.go @@ -134,6 +134,24 @@ func (s *SuiteDotGit) TestRefsFromReferenceFile(c *C) { } +func BenchmarkRefMultipleTimes(b *testing.B) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + refname := plumbing.ReferenceName("refs/remotes/origin/branch") + + dir := New(fs) + _, err := dir.Ref(refname) + if err != nil { + b.Fatalf("unexpected error: %s", err) + } + + for i := 0; i < b.N; i++ { + _, err := dir.Ref(refname) + if err != nil { + b.Fatalf("unexpected error: %s", err) + } + } +} + func (s *SuiteDotGit) TestRemoveRefFromReferenceFile(c *C) { fs := fixtures.Basic().ByTag(".git").One().DotGit() dir := New(fs) diff --git a/storage/filesystem/internal/dotgit/writers.go b/storage/filesystem/internal/dotgit/writers.go index a7525d4..46d3619 100644 --- a/storage/filesystem/internal/dotgit/writers.go +++ b/storage/filesystem/internal/dotgit/writers.go @@ -92,7 +92,7 @@ func (w *PackWriter) Write(p []byte) (int, error) { // was written, the tempfiles are deleted without writing a packfile. func (w *PackWriter) Close() error { defer func() { - if w.Notify != nil { + if w.Notify != nil && w.index != nil && w.index.Size() > 0 { w.Notify(w.checksum, w.index) } diff --git a/storage/filesystem/internal/dotgit/writers_test.go b/storage/filesystem/internal/dotgit/writers_test.go index 1342396..1544de8 100644 --- a/storage/filesystem/internal/dotgit/writers_test.go +++ b/storage/filesystem/internal/dotgit/writers_test.go @@ -12,6 +12,7 @@ import ( . "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" ) @@ -132,3 +133,23 @@ func (s *SuiteDotGit) TestSyncedReader(c *C) { c.Assert(n, Equals, 3) c.Assert(string(head), Equals, "280") } + +func (s *SuiteDotGit) TestPackWriterUnusedNotify(c *C) { + dir, err := ioutil.TempDir("", "example") + if err != nil { + c.Assert(err, IsNil) + } + + defer os.RemoveAll(dir) + + fs := osfs.New(dir) + + w, err := newPackWrite(fs) + c.Assert(err, IsNil) + + w.Notify = func(h plumbing.Hash, idx *packfile.Index) { + c.Fatal("unexpected call to PackWriter.Notify") + } + + c.Assert(w.Close(), IsNil) +} |