Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Remote: add Prune option to PushOptions #1165

Merged
merged 2 commits into from
Jul 26, 2019
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
7 changes: 7 additions & 0 deletions config/refspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
9 changes: 9 additions & 0 deletions config/refspec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
67 changes: 52 additions & 15 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,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
}
Expand Down Expand Up @@ -201,7 +211,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
}

Expand Down Expand Up @@ -389,6 +399,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)
Expand All @@ -398,14 +409,20 @@ 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 {
err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req)
if err != nil {
return err
}

if prune {
if err := r.deleteReferences(rs, remoteRefs, refsDict, req, true); err != nil {
return err
}
}
}
}

Expand Down Expand Up @@ -441,7 +458,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
Expand All @@ -452,8 +472,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{
Expand Down Expand Up @@ -1012,10 +1043,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
Expand All @@ -1026,15 +1058,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 {
Expand Down
59 changes: 58 additions & 1 deletion remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down