diff options
-rw-r--r-- | config/refspec.go | 7 | ||||
-rw-r--r-- | config/refspec_test.go | 9 | ||||
-rw-r--r-- | options.go | 3 | ||||
-rw-r--r-- | remote.go | 31 | ||||
-rw-r--r-- | remote_test.go | 59 |
5 files changed, 103 insertions, 6 deletions
diff --git a/config/refspec.go b/config/refspec.go index 391705c..b2b3203 100644 --- a/config/refspec.go +++ b/config/refspec.go @@ -127,6 +127,13 @@ func (s RefSpec) Dst(n plumbing.ReferenceName) plumbing.ReferenceName { return plumbing.ReferenceName(dst[0:wd] + match + dst[wd+1:]) } +func (s RefSpec) Reverse() RefSpec { + spec := string(s) + separator := strings.Index(spec, refSpecSeparator) + + return RefSpec(spec[separator+1:] + refSpecSeparator + spec[:separator]) +} + func (s RefSpec) String() string { return string(s) } diff --git a/config/refspec_test.go b/config/refspec_test.go index 675e075..b925cba 100644 --- a/config/refspec_test.go +++ b/config/refspec_test.go @@ -116,6 +116,15 @@ func (s *RefSpecSuite) TestRefSpecDstBlob(c *C) { "refs/remotes/origin/foo", ) } + +func (s *RefSpecSuite) TestRefSpecReverse(c *C) { + spec := RefSpec("refs/heads/*:refs/remotes/origin/*") + c.Assert( + spec.Reverse(), Equals, + RefSpec("refs/remotes/origin/*:refs/heads/*"), + ) +} + func (s *RefSpecSuite) TestMatchAny(c *C) { specs := []RefSpec{ "refs/heads/bar:refs/remotes/origin/foo", @@ -186,6 +186,9 @@ type PushOptions struct { // Progress is where the human readable information sent by the server is // stored, if nil nothing is stored. Progress sideband.Progress + // Prune specify that remote refs that match given RefSpecs and that do + // not exist locally will be removed. + Prune bool } // Validate validates the fields and sets the default values. @@ -201,7 +201,7 @@ func (r *Remote) newReferenceUpdateRequest( } } - if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req); err != nil { + if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune); err != nil { return nil, err } @@ -389,6 +389,7 @@ func (r *Remote) addReferencesToUpdate( localRefs []*plumbing.Reference, remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest, + prune bool, ) error { // This references dictionary will be used to search references by name. refsDict := make(map[string]*plumbing.Reference) @@ -398,7 +399,7 @@ func (r *Remote) addReferencesToUpdate( for _, rs := range refspecs { if rs.IsDelete() { - if err := r.deleteReferences(rs, remoteRefs, req); err != nil { + if err := r.deleteReferences(rs, remoteRefs, refsDict, req, false); err != nil { return err } } else { @@ -406,6 +407,12 @@ func (r *Remote) addReferencesToUpdate( if err != nil { return err } + + if prune { + if err := r.deleteReferences(rs, remoteRefs, refsDict, req, true); err != nil { + return err + } + } } } @@ -441,7 +448,10 @@ func (r *Remote) addOrUpdateReferences( } func (r *Remote) deleteReferences(rs config.RefSpec, - remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error { + remoteRefs storer.ReferenceStorer, + refsDict map[string]*plumbing.Reference, + req *packp.ReferenceUpdateRequest, + prune bool) error { iter, err := remoteRefs.IterReferences() if err != nil { return err @@ -452,8 +462,19 @@ func (r *Remote) deleteReferences(rs config.RefSpec, return nil } - if rs.Dst("") != ref.Name() { - return nil + if prune { + rs := rs.Reverse() + if !rs.Match(ref.Name()) { + return nil + } + + if _, ok := refsDict[rs.Dst(ref.Name()).String()]; ok { + return nil + } + } else { + if rs.Dst("") != ref.Name() { + return nil + } } cmd := &packp.Command{ diff --git a/remote_test.go b/remote_test.go index 58a0598..290b574 100644 --- a/remote_test.go +++ b/remote_test.go @@ -21,7 +21,7 @@ import ( . "gopkg.in/check.v1" "gopkg.in/src-d/go-billy.v4/osfs" - "gopkg.in/src-d/go-git-fixtures.v3" + fixtures "gopkg.in/src-d/go-git-fixtures.v3" ) type RemoteSuite struct { @@ -583,6 +583,63 @@ func (s *RemoteSuite) TestPushForce(c *C) { c.Assert(newRef, Not(DeepEquals), oldRef) } +func (s *RemoteSuite) TestPushPrune(c *C) { + fs := fixtures.Basic().One().DotGit() + url := c.MkDir() + server, err := PlainClone(url, true, &CloneOptions{ + URL: fs.Root(), + }) + c.Assert(err, IsNil) + + r, err := PlainClone(c.MkDir(), true, &CloneOptions{ + URL: url, + }) + c.Assert(err, IsNil) + + tag, err := r.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true) + c.Assert(err, IsNil) + + err = r.DeleteTag("v1.0.0") + c.Assert(err, IsNil) + + remote, err := r.Remote(DefaultRemoteName) + c.Assert(err, IsNil) + + ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true) + c.Assert(err, IsNil) + + err = remote.Push(&PushOptions{ + RefSpecs: []config.RefSpec{ + config.RefSpec("refs/heads/*:refs/heads/*"), + }, + Prune: true, + }) + c.Assert(err, Equals, NoErrAlreadyUpToDate) + + AssertReferences(c, server, map[string]string{ + "refs/tags/v1.0.0": tag.Hash().String(), + }) + + err = remote.Push(&PushOptions{ + RefSpecs: []config.RefSpec{ + config.RefSpec("*:*"), + }, + Prune: true, + }) + c.Assert(err, IsNil) + + AssertReferences(c, server, map[string]string{ + "refs/remotes/origin/master": ref.Hash().String(), + }) + + AssertReferences(c, server, map[string]string{ + "refs/remotes/origin/master": ref.Hash().String(), + }) + + ref, err = server.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true) + c.Assert(err, Equals, plumbing.ErrReferenceNotFound) +} + func (s *RemoteSuite) TestPushNewReference(c *C) { fs := fixtures.Basic().One().DotGit() url := c.MkDir() |