Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plumbing/storer/reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ReferenceStorer interface {
SetReference(*plumbing.Reference) error
Reference(plumbing.ReferenceName) (*plumbing.Reference, error)
IterReferences() (ReferenceIter, error)
RemoveReference(plumbing.ReferenceName) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some documentation indicating at least that could not be thread safe?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being thread-safe or not is implementation-dependent. So far, none of our public implementations is thread-safe for any write operation.

}

// ReferenceIter is a generic closable interface for iterating over references
Expand Down
92 changes: 78 additions & 14 deletions storage/filesystem/internal/dotgit/dotgit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -21,6 +22,8 @@ const (
indexPath = "index"
shallowPath = "shallow"

tmpPackedRefsPrefix = "._packed-refs"

objectsPath = "objects"
packPath = "pack"
refsPath = "refs"
Expand Down Expand Up @@ -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 {
Expand All @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
91 changes: 91 additions & 0 deletions storage/filesystem/internal/dotgit/dotgit_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dotgit

import (
"bufio"
"io/ioutil"
"os"
"path/filepath"
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions storage/filesystem/reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
5 changes: 5 additions & 0 deletions storage/memory/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions storage/test/storage_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down