diff options
author | Jeremy Stribling <strib@alum.mit.edu> | 2017-10-31 15:15:58 -0700 |
---|---|---|
committer | Jeremy Stribling <strib@alum.mit.edu> | 2017-11-29 10:32:55 -0800 |
commit | 026d7c48163a9d246820c84693673a13f42f9145 (patch) | |
tree | 7cf86e1095be31a205c53224d7a648d71cf76a1c /storage/filesystem | |
parent | 7ced03216a47327d64f68c750114a96cfcbae38b (diff) | |
download | go-git-026d7c48163a9d246820c84693673a13f42f9145.tar.gz |
filesystem: implement PackRefs()
Currently this implementation is only valid for kbfsgit, since it
assumes some things about the filesystem not being updated during the
packing, and about conflict resolution rules. In the future, it would
be nice to replace this with a more general one, and move this
kbfsgit-optimized implementation into kbfsgit.
Issue: KBFS-2517
Diffstat (limited to 'storage/filesystem')
-rw-r--r-- | storage/filesystem/internal/dotgit/dotgit.go | 115 | ||||
-rw-r--r-- | storage/filesystem/internal/dotgit/dotgit_test.go | 72 | ||||
-rw-r--r-- | storage/filesystem/reference.go | 8 |
3 files changed, 195 insertions, 0 deletions
diff --git a/storage/filesystem/internal/dotgit/dotgit.go b/storage/filesystem/internal/dotgit/dotgit.go index 1cb97bd..29e2525 100644 --- a/storage/filesystem/internal/dotgit/dotgit.go +++ b/storage/filesystem/internal/dotgit/dotgit.go @@ -576,6 +576,121 @@ func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference, return d.readReferenceFrom(f, name) } +func (d *DotGit) CountLooseRefs() (int, error) { + var refs []*plumbing.Reference + var seen = make(map[plumbing.ReferenceName]bool) + if err := d.addRefsFromRefDir(&refs, seen); err != nil { + return 0, err + } + + return len(refs), nil +} + +// PackRefs packs all loose refs into the packed-refs file. +// +// This implementation only works under the assumption that the view +// of the file system won't be updated during this operation, which is +// true for kbfsgit after the Lock() operation is complete (and before +// the Unlock()/Close() of the locked file). If another process +// concurrently updates one of the loose refs we delete, then KBFS +// conflict resolution would just end up ignoring our delete. Also +// note that deleting a ref requires locking packed-refs, so a ref +// deleted by the user shouldn't be revived by ref-packing. +// +// The strategy would not work on a general file system though, +// without locking each loose reference and checking it again before +// deleting the file, because otherwise an updated reference could +// sneak in and then be deleted by the packed-refs process. +// Alternatively, every ref update could also lock packed-refs, so +// only one lock is required during ref-packing. But that would +// worsen performance in the common case. +// +// TODO: before trying to get this merged upstream, move it into a +// custom kbfsgit Storer implementation, and rewrite this function to +// work correctly on a general filesystem. +func (d *DotGit) PackRefs() (err error) { + // Lock packed-refs, and create it if it doesn't exist yet. + f, err := d.fs.Open(packedRefsPath) + if err != nil { + if os.IsNotExist(err) { + f, err = d.fs.Create(packedRefsPath) + if err != nil { + return err + } + } else { + return err + } + } + defer ioutil.CheckClose(f, &err) + + err = f.Lock() + if err != nil { + return err + } + + // Gather all refs using addRefsFromRefDir and addRefsFromPackedRefs. + var refs []*plumbing.Reference + var seen = make(map[plumbing.ReferenceName]bool) + if err := d.addRefsFromRefDir(&refs, seen); err != nil { + return err + } + if len(refs) == 0 { + // Nothing to do! + return nil + } + numLooseRefs := len(refs) + if err := d.addRefsFromPackedRefs(&refs, seen); err != nil { + return err + } + + // Write them all to a new temp packed-refs file. + tmp, err := d.fs.TempFile("", tmpPackedRefsPrefix) + if err != nil { + return err + } + doCloseTmp := true + defer func() { + if doCloseTmp { + ioutil.CheckClose(tmp, &err) + } + }() + for _, ref := range refs { + _, err := tmp.Write([]byte(ref.String() + "\n")) + if err != nil { + return err + } + } + + // Rename the temp packed-refs file. + doCloseTmp = false + if err := tmp.Close(); err != nil { + return err + } + err = d.fs.Rename(tmp.Name(), packedRefsPath) + if err != nil { + return err + } + + // Delete all the loose refs, while still holding the packed-refs + // lock. + for _, ref := range refs[:numLooseRefs] { + path := d.fs.Join(".", ref.Name().String()) + err = d.fs.Remove(path) + if err != nil && !os.IsNotExist(err) { + return err + } + } + + // Update packed-refs cache. + d.cachedPackedRefs = make(refCache) + for _, ref := range refs { + d.cachedPackedRefs[ref.Name()] = ref + } + d.packedRefsLastMod = time.Now() + + return nil +} + // Module return a billy.Filesystem poiting to the module folder func (d *DotGit) Module(name string) (billy.Filesystem, error) { return d.fs.Chroot(d.fs.Join(modulePath, name)) diff --git a/storage/filesystem/internal/dotgit/dotgit_test.go b/storage/filesystem/internal/dotgit/dotgit_test.go index 446a204..0ed9ec5 100644 --- a/storage/filesystem/internal/dotgit/dotgit_test.go +++ b/storage/filesystem/internal/dotgit/dotgit_test.go @@ -543,3 +543,75 @@ func (s *SuiteDotGit) TestSubmodules(c *C) { c.Assert(err, IsNil) c.Assert(strings.HasSuffix(m.Root(), m.Join(".git", "modules", "basic")), Equals, true) } + +func (s *SuiteDotGit) TestPackRefs(c *C) { + tmp, err := ioutil.TempDir("", "dot-git") + c.Assert(err, IsNil) + defer os.RemoveAll(tmp) + + fs := osfs.New(tmp) + dir := New(fs) + + err = dir.SetRef(plumbing.NewReferenceFromStrings( + "refs/heads/foo", + "e8d3ffab552895c19b9fcf7aa264d277cde33881", + ), nil) + c.Assert(err, IsNil) + err = dir.SetRef(plumbing.NewReferenceFromStrings( + "refs/heads/bar", + "a8d3ffab552895c19b9fcf7aa264d277cde33881", + ), nil) + c.Assert(err, IsNil) + + refs, err := dir.Refs() + c.Assert(err, IsNil) + c.Assert(refs, HasLen, 2) + looseCount, err := dir.CountLooseRefs() + c.Assert(err, IsNil) + c.Assert(looseCount, Equals, 2) + + 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, 2) + 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") + ref, err = dir.Ref("refs/heads/bar") + c.Assert(err, IsNil) + c.Assert(ref, NotNil) + c.Assert(ref.Hash().String(), Equals, "a8d3ffab552895c19b9fcf7aa264d277cde33881") + + // Now update one of them, re-pack, and check again. + err = dir.SetRef(plumbing.NewReferenceFromStrings( + "refs/heads/foo", + "b8d3ffab552895c19b9fcf7aa264d277cde33881", + ), nil) + c.Assert(err, IsNil) + 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, 2) + 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, "b8d3ffab552895c19b9fcf7aa264d277cde33881") +} diff --git a/storage/filesystem/reference.go b/storage/filesystem/reference.go index 54cdf56..7313f05 100644 --- a/storage/filesystem/reference.go +++ b/storage/filesystem/reference.go @@ -34,3 +34,11 @@ func (r *ReferenceStorage) IterReferences() (storer.ReferenceIter, error) { func (r *ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error { return r.dir.RemoveRef(n) } + +func (r *ReferenceStorage) CountLooseRefs() (int, error) { + return r.dir.CountLooseRefs() +} + +func (r *ReferenceStorage) PackRefs() error { + return r.dir.PackRefs() +} |