Skip to content

Commit

Permalink
remote: fetch, adds the prune option. Fixes #316
Browse files Browse the repository at this point in the history
Co-authored-by: Ludovic Fernandez <ldez@users.noreply.github.com>
  • Loading branch information
juliens and ldez committed Feb 22, 2024
1 parent 686a0f7 commit f87e168
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 2 deletions.
5 changes: 4 additions & 1 deletion options.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const (
// AllTags fetch all tags from the remote (i.e., fetch remote tags
// refs/tags/* into local tags with the same name)
AllTags
//NoTags fetch no tags from the remote at all
// NoTags fetch no tags from the remote at all
NoTags
)

Expand Down Expand Up @@ -198,6 +198,9 @@ type FetchOptions struct {
CABundle []byte
// ProxyOptions provides info required for connecting to a proxy.
ProxyOptions transport.ProxyOptions
// Prune specify that local refs that match given RefSpecs and that do
// not exist remotely will be removed.
Prune bool
}

// Validate validates the fields and sets the default values.
Expand Down
31 changes: 30 additions & 1 deletion remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,14 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
}
}

var updatedPrune bool
if o.Prune {
updatedPrune, err = r.pruneRemotes(o.RefSpecs, localRefs, remoteRefs)
if err != nil {
return nil, err
}
}

updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, specToRefs, o.Tags, o.Force)
if err != nil {
return nil, err
Expand All @@ -482,7 +490,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
}
}

if !updated {
if !updated && !updatedPrune {
return remoteRefs, NoErrAlreadyUpToDate
}

Expand Down Expand Up @@ -574,6 +582,27 @@ func (r *Remote) fetchPack(ctx context.Context, o *FetchOptions, s transport.Upl
return err
}

func (r *Remote) pruneRemotes(specs []config.RefSpec, localRefs []*plumbing.Reference, remoteRefs memory.ReferenceStorage) (bool, error) {
var updatedPrune bool
for _, spec := range specs {
rev := spec.Reverse()
for _, ref := range localRefs {
if !rev.Match(ref.Name()) {
continue
}
_, err := remoteRefs.Reference(rev.Dst(ref.Name()))
if errors.Is(err, plumbing.ErrReferenceNotFound) {
updatedPrune = true
err := r.s.RemoveReference(ref.Name())
if err != nil {
return false, err
}
}
}
}
return updatedPrune, nil
}

func (r *Remote) addReferencesToUpdate(
refspecs []config.RefSpec,
localRefs []*plumbing.Reference,
Expand Down
113 changes: 113 additions & 0 deletions remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,119 @@ func (s *RemoteSuite) TestPushRequireRemoteRefs(c *C) {
c.Assert(newRef, Not(DeepEquals), oldRef)
}

func (s *RemoteSuite) TestFetchPrune(c *C) {
fs := fixtures.Basic().One().DotGit()

url, clean := s.TemporalDir()
defer clean()

_, err := PlainClone(url, true, &CloneOptions{
URL: fs.Root(),
})
c.Assert(err, IsNil)

dir, clean := s.TemporalDir()
defer clean()

r, err := PlainClone(dir, true, &CloneOptions{
URL: url,
})
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{
"refs/heads/master:refs/heads/branch",
}})
c.Assert(err, IsNil)

dirSave, clean := s.TemporalDir()
defer clean()

rSave, err := PlainClone(dirSave, true, &CloneOptions{
URL: url,
})
c.Assert(err, IsNil)

AssertReferences(c, rSave, map[string]string{
"refs/remotes/origin/branch": ref.Hash().String(),
})

err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
":refs/heads/branch",
}})

AssertReferences(c, rSave, map[string]string{
"refs/remotes/origin/branch": ref.Hash().String(),
})

err = rSave.Fetch(&FetchOptions{Prune: true})
c.Assert(err, IsNil)

_, err = rSave.Reference("refs/remotes/origin/branch", true)
c.Assert(err, ErrorMatches, "reference not found")
}

func (s *RemoteSuite) TestFetchPruneTags(c *C) {
fs := fixtures.Basic().One().DotGit()

url, clean := s.TemporalDir()
defer clean()

_, err := PlainClone(url, true, &CloneOptions{
URL: fs.Root(),
})
c.Assert(err, IsNil)

dir, clean := s.TemporalDir()
defer clean()

r, err := PlainClone(dir, true, &CloneOptions{
URL: url,
})
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{
"refs/heads/master:refs/tags/v1",
}})
c.Assert(err, IsNil)

dirSave, clean := s.TemporalDir()
defer clean()

rSave, err := PlainClone(dirSave, true, &CloneOptions{
URL: url,
})
c.Assert(err, IsNil)

AssertReferences(c, rSave, map[string]string{
"refs/tags/v1": ref.Hash().String(),
})

err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
":refs/tags/v1",
}})

AssertReferences(c, rSave, map[string]string{
"refs/tags/v1": ref.Hash().String(),
})

err = rSave.Fetch(&FetchOptions{Prune: true, RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"}})
c.Assert(err, IsNil)

_, err = rSave.Reference("refs/tags/v1", true)
c.Assert(err, ErrorMatches, "reference not found")
}
func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
d, err := os.MkdirTemp("", "TestCanPushShasToReference")
c.Assert(err, IsNil)
Expand Down

0 comments on commit f87e168

Please sign in to comment.