From 53385d62c573e0e2ef67587099cce0144cf60b3c Mon Sep 17 00:00:00 2001 From: "Santiago M. Mola" Date: Mon, 13 Feb 2017 17:04:30 +0100 Subject: plumbing/storer: add RemoveReference --- plumbing/storer/reference.go | 1 + storage/filesystem/internal/dotgit/dotgit.go | 92 +++++++++++++++++++---- storage/filesystem/internal/dotgit/dotgit_test.go | 91 ++++++++++++++++++++++ storage/filesystem/reference.go | 4 + storage/memory/storage.go | 5 ++ storage/test/storage_suite.go | 27 +++++++ 6 files changed, 206 insertions(+), 14 deletions(-) diff --git a/plumbing/storer/reference.go b/plumbing/storer/reference.go index 40474f9..692fe88 100644 --- a/plumbing/storer/reference.go +++ b/plumbing/storer/reference.go @@ -18,6 +18,7 @@ type ReferenceStorer interface { SetReference(*plumbing.Reference) error Reference(plumbing.ReferenceName) (*plumbing.Reference, error) IterReferences() (ReferenceIter, error) + RemoveReference(plumbing.ReferenceName) error } // ReferenceIter is a generic closable interface for iterating over references diff --git a/storage/filesystem/internal/dotgit/dotgit.go b/storage/filesystem/internal/dotgit/dotgit.go index accf9ca..6646e18 100644 --- a/storage/filesystem/internal/dotgit/dotgit.go +++ b/storage/filesystem/internal/dotgit/dotgit.go @@ -5,11 +5,12 @@ import ( "bufio" "errors" "fmt" - "io/ioutil" + stdioutil "io/ioutil" "os" "strings" "srcd.works/go-git.v4/plumbing" + "srcd.works/go-git.v4/utils/ioutil" "srcd.works/go-billy.v1" ) @@ -21,6 +22,8 @@ const ( indexPath = "index" shallowPath = "shallow" + tmpPackedRefsPrefix = "._packed-refs" + objectsPath = "objects" packPath = "pack" refsPath = "refs" @@ -269,6 +272,21 @@ func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) { return nil, plumbing.ErrReferenceNotFound } +// RemoveRef removes a reference by name. +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) + } + + if err != nil && !os.IsNotExist(err) { + return err + } + + return d.rewritePackedRefsWithoutRef(name) +} + func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference) (err error) { f, err := d.fs.Open(packedRefsPath) if err != nil { @@ -277,12 +295,7 @@ func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference) (err error) } return err } - - defer func() { - if errClose := f.Close(); err == nil { - err = errClose - } - }() + defer ioutil.CheckClose(f, &err) s := bufio.NewScanner(f) for s.Scan() { @@ -299,8 +312,64 @@ func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference) (err error) return s.Err() } +func (d *DotGit) rewritePackedRefsWithoutRef(name plumbing.ReferenceName) (err error) { + f, err := d.fs.Open(packedRefsPath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + + return err + } + defer ioutil.CheckClose(f, &err) + + // Creating the temp file in the same directory as the target file + // improves our chances for rename operation to be atomic. + tmp, err := d.fs.TempFile("", tmpPackedRefsPrefix) + if err != nil { + return err + } + + tmpPath := tmp.Filename() + defer ioutil.CheckClose(tmp, &err) + defer d.fs.Remove(tmpPath) + + s := bufio.NewScanner(f) + found := false + for s.Scan() { + line := s.Text() + ref, err := d.processLine(line) + if err != nil { + return err + } + + if ref != nil && ref.Name() == name { + found = true + continue + } + + if _, err := fmt.Fprintln(tmp, line); err != nil { + return err + } + } + + if err := s.Err(); err != nil { + return err + } + + if !found { + return nil + } + + return d.fs.Rename(tmpPath, packedRefsPath) +} + // process lines from a packed-refs file func (d *DotGit) processLine(line string) (*plumbing.Reference, error) { + if len(line) == 0 { + return nil, nil + } + switch line[0] { case '#': // comment - ignore return nil, nil @@ -374,14 +443,9 @@ func (d *DotGit) readReferenceFile(refsPath, refFile string) (ref *plumbing.Refe if err != nil { return nil, err } + defer ioutil.CheckClose(f, &err) - defer func() { - if errClose := f.Close(); err == nil { - err = errClose - } - }() - - b, err := ioutil.ReadAll(f) + b, err := stdioutil.ReadAll(f) if err != nil { return nil, err } diff --git a/storage/filesystem/internal/dotgit/dotgit_test.go b/storage/filesystem/internal/dotgit/dotgit_test.go index a335e5f..226b299 100644 --- a/storage/filesystem/internal/dotgit/dotgit_test.go +++ b/storage/filesystem/internal/dotgit/dotgit_test.go @@ -1,6 +1,7 @@ package dotgit import ( + "bufio" "io/ioutil" "os" "path/filepath" @@ -108,6 +109,96 @@ func (s *SuiteDotGit) TestRefsFromReferenceFile(c *C) { } +func (s *SuiteDotGit) TestRemoveRefFromReferenceFile(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + name := plumbing.ReferenceName("refs/remotes/origin/HEAD") + err := dir.RemoveRef(name) + c.Assert(err, IsNil) + + refs, err := dir.Refs() + c.Assert(err, IsNil) + + ref := findReference(refs, string(name)) + c.Assert(ref, IsNil) +} + +func (s *SuiteDotGit) TestRemoveRefFromPackedRefs(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + name := plumbing.ReferenceName("refs/remotes/origin/master") + err := dir.RemoveRef(name) + c.Assert(err, IsNil) + + b, err := ioutil.ReadFile(filepath.Join(fs.Base(), packedRefsPath)) + c.Assert(err, IsNil) + + c.Assert(string(b), Equals, ""+ + "# pack-refs with: peeled fully-peeled \n"+ + "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 refs/heads/master\n"+ + "e8d3ffab552895c19b9fcf7aa264d277cde33881 refs/remotes/origin/branch\n") +} + +func (s *SuiteDotGit) TestRemoveRefNonExistent(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + packedRefs := filepath.Join(fs.Base(), packedRefsPath) + before, err := ioutil.ReadFile(packedRefs) + c.Assert(err, IsNil) + + name := plumbing.ReferenceName("refs/heads/nonexistent") + err = dir.RemoveRef(name) + c.Assert(err, IsNil) + + after, err := ioutil.ReadFile(packedRefs) + c.Assert(err, IsNil) + + c.Assert(string(before), Equals, string(after)) +} + +func (s *SuiteDotGit) TestRemoveRefInvalidPackedRefs(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + packedRefs := filepath.Join(fs.Base(), packedRefsPath) + brokenContent := "BROKEN STUFF REALLY BROKEN" + + err := ioutil.WriteFile(packedRefs, []byte(brokenContent), os.FileMode(0755)) + c.Assert(err, IsNil) + + name := plumbing.ReferenceName("refs/heads/nonexistent") + err = dir.RemoveRef(name) + c.Assert(err, NotNil) + + after, err := ioutil.ReadFile(filepath.Join(fs.Base(), packedRefsPath)) + c.Assert(err, IsNil) + + c.Assert(brokenContent, Equals, string(after)) +} + +func (s *SuiteDotGit) TestRemoveRefInvalidPackedRefs2(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + packedRefs := filepath.Join(fs.Base(), packedRefsPath) + brokenContent := strings.Repeat("a", bufio.MaxScanTokenSize*2) + + err := ioutil.WriteFile(packedRefs, []byte(brokenContent), os.FileMode(0755)) + c.Assert(err, IsNil) + + name := plumbing.ReferenceName("refs/heads/nonexistent") + err = dir.RemoveRef(name) + c.Assert(err, NotNil) + + after, err := ioutil.ReadFile(filepath.Join(fs.Base(), packedRefsPath)) + c.Assert(err, IsNil) + + c.Assert(brokenContent, Equals, string(after)) +} + func (s *SuiteDotGit) TestRefsFromHEADFile(c *C) { fs := fixtures.Basic().ByTag(".git").One().DotGit() dir := New(fs) diff --git a/storage/filesystem/reference.go b/storage/filesystem/reference.go index ee87830..cff00c9 100644 --- a/storage/filesystem/reference.go +++ b/storage/filesystem/reference.go @@ -26,3 +26,7 @@ func (r *ReferenceStorage) IterReferences() (storer.ReferenceIter, error) { return storer.NewReferenceSliceIter(refs), nil } + +func (r *ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error { + return r.dir.RemoveRef(n) +} diff --git a/storage/memory/storage.go b/storage/memory/storage.go index 6cec47b..9b55b1f 100644 --- a/storage/memory/storage.go +++ b/storage/memory/storage.go @@ -217,6 +217,11 @@ func (r ReferenceStorage) IterReferences() (storer.ReferenceIter, error) { return storer.NewReferenceSliceIter(refs), nil } +func (r ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error { + delete(r, n) + return nil +} + type ShallowStorage []plumbing.Hash func (s *ShallowStorage) SetShallow(commits []plumbing.Hash) error { diff --git a/storage/test/storage_suite.go b/storage/test/storage_suite.go index 6fc2937..e09a673 100644 --- a/storage/test/storage_suite.go +++ b/storage/test/storage_suite.go @@ -242,6 +242,33 @@ func (s *BaseStorageSuite) TestSetReferenceAndGetReference(c *C) { c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") } +func (s *BaseStorageSuite) TestRemoveReference(c *C) { + err := s.Storer.SetReference( + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), + ) + c.Assert(err, IsNil) + + err = s.Storer.RemoveReference(plumbing.ReferenceName("foo")) + c.Assert(err, IsNil) + + _, err = s.Storer.Reference(plumbing.ReferenceName("foo")) + c.Assert(err, Equals, plumbing.ErrReferenceNotFound) +} + +func (s *BaseStorageSuite) TestRemoveReferenceNonExistent(c *C) { + err := s.Storer.SetReference( + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), + ) + c.Assert(err, IsNil) + + err = s.Storer.RemoveReference(plumbing.ReferenceName("nonexistent")) + c.Assert(err, IsNil) + + e, err := s.Storer.Reference(plumbing.ReferenceName("foo")) + c.Assert(err, IsNil) + c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") +} + func (s *BaseStorageSuite) TestGetReferenceNotFound(c *C) { r, err := s.Storer.Reference(plumbing.ReferenceName("bar")) c.Assert(err, Equals, plumbing.ErrReferenceNotFound) -- cgit