Skip to content

Commit

Permalink
remote: fetch, correct behaviour on tags
Browse files Browse the repository at this point in the history
  • Loading branch information
mcuadros committed Jul 17, 2017
1 parent 6b3a6df commit cbdb258
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 109 deletions.
15 changes: 15 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ func (o *PullOptions) Validate() error {
return nil
}

type TagFetchMode int

var (
// TagFollowing any tag that points into the histories being fetched is also
// fetched. TagFollowing requires a server with `include-tag` capability
// in order to fetch the annotated tags objects.
TagFollowing TagFetchMode = 0
// AllTags fetch all tags from the remote (i.e., fetch remote tags
// refs/tags/* into local tags with the same name)
AllTags TagFetchMode = 1
)

// FetchOptions describes how a fetch should be performed
type FetchOptions struct {
// Name of the remote to fetch from. Defaults to origin.
Expand All @@ -117,6 +129,9 @@ type FetchOptions struct {
// stored, if nil nothing is stored and the capability (if supported)
// no-progress, is sent to the server to avoid send this information.
Progress sideband.Progress
// Tags describe how the tags will be fetched from the remote repository,
// by default is TagFollowing.
Tags TagFetchMode
}

// Validate validates the fields and sets the default values.
Expand Down
139 changes: 83 additions & 56 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (r *Remote) Push(o *PushOptions) (err error) {
return rs.Error()
}

func (r *Remote) fetch(o *FetchOptions) (refs storer.ReferenceStorer, err error) {
func (r *Remote) fetch(o *FetchOptions) (storer.ReferenceStorer, error) {
if o.RemoteName == "" {
o.RemoteName = r.c.Name
}
Expand Down Expand Up @@ -169,7 +169,12 @@ func (r *Remote) fetch(o *FetchOptions) (refs storer.ReferenceStorer, err error)
return nil, err
}

req.Wants, err = getWants(o.RefSpecs, r.s, remoteRefs)
refs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
if err != nil {
return nil, err
}

req.Wants, err = getWants(r.s, refs)
if len(req.Wants) > 0 {
req.Haves, err = getHaves(r.s)
if err != nil {
Expand All @@ -181,14 +186,15 @@ func (r *Remote) fetch(o *FetchOptions) (refs storer.ReferenceStorer, err error)
}
}

err = r.updateLocalReferenceStorage(o.RefSpecs, remoteRefs)
if err != nil && err != NoErrAlreadyUpToDate {
updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs)
if err != nil {
return nil, err
}

if len(req.Wants) == 0 {
return remoteRefs, err
if !updated {
return remoteRefs, NoErrAlreadyUpToDate
}

return remoteRefs, nil
}

Expand Down Expand Up @@ -372,56 +378,52 @@ func getHaves(localRefs storer.ReferenceStorer) ([]plumbing.Hash, error) {
return haves, nil
}

func getWants(
spec []config.RefSpec, localStorer storage.Storer, remoteRefs storer.ReferenceStorer,
) ([]plumbing.Hash, error) {
wantTags := true
for _, s := range spec {
if !s.IsWildcard() {
wantTags = false
break
}
}

func calculateRefs(spec []config.RefSpec,
remoteRefs storer.ReferenceStorer,
tags TagFetchMode,
) (memory.ReferenceStorage, error) {
iter, err := remoteRefs.IterReferences()
if err != nil {
return nil, err
}

wants := map[plumbing.Hash]bool{}
err = iter.ForEach(func(ref *plumbing.Reference) error {
refs := make(memory.ReferenceStorage, 0)
return refs, iter.ForEach(func(ref *plumbing.Reference) error {
if !config.MatchAny(spec, ref.Name()) {
if !ref.IsTag() || !wantTags {
if !ref.IsTag() || tags != AllTags {
return nil
}
}

if ref.Type() == plumbing.SymbolicReference {
ref, err = storer.ResolveReference(remoteRefs, ref.Name())
target, err := storer.ResolveReference(remoteRefs, ref.Name())
if err != nil {
return err
}

ref = plumbing.NewHashReference(ref.Name(), target.Hash())
}

if ref.Type() != plumbing.HashReference {
return nil
}

hash := ref.Hash()
return refs.SetReference(ref)
})
}

exists, err := objectExists(localStorer, hash)
func getWants(localStorer storage.Storer, refs memory.ReferenceStorage) ([]plumbing.Hash, error) {
wants := map[plumbing.Hash]bool{}
for _, ref := range refs {
hash := ref.Hash()
exists, err := objectExists(localStorer, ref.Hash())
if err != nil {
return err
return nil, err
}

if !exists {
wants[hash] = true
}

return nil
})
if err != nil {
return nil, err
}

var result []plumbing.Hash
Expand Down Expand Up @@ -503,6 +505,19 @@ func (r *Remote) newUploadPackRequest(o *FetchOptions,
}
}

isWildcard := true
for _, s := range o.RefSpecs {
if !s.IsWildcard() {
isWildcard = false
}
}

if isWildcard && o.Tags == TagFollowing && ar.Capabilities.Supports(capability.IncludeTag) {
if err := req.Capabilities.Set(capability.IncludeTag); err != nil {
return nil, err
}
}

return req, nil
}

Expand All @@ -524,10 +539,17 @@ func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.P
return d
}

func (r *Remote) updateLocalReferenceStorage(specs []config.RefSpec, refs memory.ReferenceStorage) error {
updated := false
func (r *Remote) updateLocalReferenceStorage(
specs []config.RefSpec,
fetchedRefs, remoteRefs memory.ReferenceStorage,
) (updated bool, err error) {
isWildcard := true
for _, spec := range specs {
for _, ref := range refs {
if !spec.IsWildcard() {
isWildcard = false
}

for _, ref := range fetchedRefs {
if !spec.Match(ref.Name()) {
continue
}
Expand All @@ -536,33 +558,36 @@ func (r *Remote) updateLocalReferenceStorage(specs []config.RefSpec, refs memory
continue
}

name := spec.Dst(ref.Name())
sref, err := r.s.Reference(name)
if err != nil && err != plumbing.ErrReferenceNotFound {
return err
new := plumbing.NewHashReference(spec.Dst(ref.Name()), ref.Hash())

refUpdated, err := updateReferenceStorerIfNeeded(r.s, new)
if err != nil {
return updated, err
}
if err == plumbing.ErrReferenceNotFound || sref.Hash() != ref.Hash() {
n := plumbing.NewHashReference(name, ref.Hash())
if err := r.s.SetReference(n); err != nil {
return err
}

if refUpdated {
updated = true
}
}
}

if err := r.buildFetchedTags(refs); err != nil {
return err
tags := fetchedRefs
if isWildcard {
tags = remoteRefs
}
tagUpdated, err := r.buildFetchedTags(tags)
if err != nil {
return updated, err
}

if !updated {
return NoErrAlreadyUpToDate
if tagUpdated {
updated = true
}
return nil

return
}

func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) error {
updated := false
func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) (updated bool, err error) {
for _, ref := range refs {
if !ref.IsTag() {
continue
Expand All @@ -574,18 +599,20 @@ func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) error {
}

if err != nil {
return err
return false, err
}

if err = r.s.SetReference(ref); err != nil {
return err
refUpdated, err := updateReferenceStorerIfNeeded(r.s, ref)
if err != nil {
return updated, err
}

if refUpdated {
updated = true
}
updated = true
}
if !updated {
return NoErrAlreadyUpToDate
}
return nil

return
}

func objectsToPush(commands []*packp.Command) ([]plumbing.Hash, error) {
Expand Down
Loading

0 comments on commit cbdb258

Please sign in to comment.