aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config/refspec.go7
-rw-r--r--config/refspec_test.go9
-rw-r--r--options.go3
-rw-r--r--remote.go67
-rw-r--r--remote_test.go59
5 files changed, 129 insertions, 16 deletions
diff --git a/config/refspec.go b/config/refspec.go
index d32a2e4..14bb400 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 2d2b057..aaeac73 100644
--- a/config/refspec_test.go
+++ b/config/refspec_test.go
@@ -167,6 +167,15 @@ func (s *RefSpecSuite) TestRefSpecDstBlob(c *C) {
)
}
}
+
+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",
diff --git a/options.go b/options.go
index a3b14fe..0f728e7 100644
--- a/options.go
+++ b/options.go
@@ -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.
diff --git a/remote.go b/remote.go
index bcc4c4c..e229ef1 100644
--- a/remote.go
+++ b/remote.go
@@ -171,7 +171,17 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
}
}
- rs, err := pushHashes(ctx, s, r.s, req, hashesToPush, r.useRefDeltas(ar))
+ if len(hashesToPush) == 0 {
+ allDelete = true
+ for _, command := range req.Commands {
+ if command.Action() != packp.Delete {
+ allDelete = false
+ break
+ }
+ }
+ }
+
+ rs, err := pushHashes(ctx, s, r.s, req, hashesToPush, r.useRefDeltas(ar), allDelete)
if err != nil {
return err
}
@@ -204,7 +214,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
}
@@ -392,6 +402,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)
@@ -401,7 +412,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 {
@@ -409,6 +420,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
+ }
+ }
}
}
@@ -444,7 +461,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
@@ -455,8 +475,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{
@@ -1015,10 +1046,11 @@ func pushHashes(
req *packp.ReferenceUpdateRequest,
hs []plumbing.Hash,
useRefDeltas bool,
+ allDelete bool,
) (*packp.ReportStatus, error) {
rd, wr := io.Pipe()
- req.Packfile = rd
+
config, err := s.Config()
if err != nil {
return nil, err
@@ -1029,15 +1061,20 @@ func pushHashes(
// to the channel.
done := make(chan error, 1)
- go func() {
- e := packfile.NewEncoder(wr, s, useRefDeltas)
- if _, err := e.Encode(hs, config.Pack.Window); err != nil {
- done <- wr.CloseWithError(err)
- return
- }
+ if !allDelete {
+ req.Packfile = rd
+ go func() {
+ e := packfile.NewEncoder(wr, s, useRefDeltas)
+ if _, err := e.Encode(hs, config.Pack.Window); err != nil {
+ done <- wr.CloseWithError(err)
+ return
+ }
- done <- wr.Close()
- }()
+ done <- wr.Close()
+ }()
+ } else {
+ close(done)
+ }
rs, err := sess.ReceivePack(ctx, req)
if err != nil {
diff --git a/remote_test.go b/remote_test.go
index 70a14e9..a45d814 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()