aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSantiago M. Mola <santi@mola.io>2017-02-13 17:04:30 +0100
committerSantiago M. Mola <santi@mola.io>2017-02-16 18:15:20 +0100
commit53385d62c573e0e2ef67587099cce0144cf60b3c (patch)
tree7e4315a2a18a46cf691c19d5ae2e96eb84e96c18
parentf5a9c7ed5ab58afa255c2f405b30f06f88a6aecf (diff)
downloadgo-git-53385d62c573e0e2ef67587099cce0144cf60b3c.tar.gz
plumbing/storer: add RemoveReference
-rw-r--r--plumbing/storer/reference.go1
-rw-r--r--storage/filesystem/internal/dotgit/dotgit.go92
-rw-r--r--storage/filesystem/internal/dotgit/dotgit_test.go91
-rw-r--r--storage/filesystem/reference.go4
-rw-r--r--storage/memory/storage.go5
-rw-r--r--storage/test/storage_suite.go27
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)